Compare commits

...
3 Commits
Author SHA1 Message Date
Qiufeng a070ad0434 fix: move notifications to bottom right
TallyNote release / linux-x64 (push) Successful in 6m55s
2026-09-05 17:06:23 +08:00
Qiufeng 3ab3e5e180 fix: sync update status after checks
TallyNote release / linux-x64 (push) Successful in 6m54s
2026-09-05 16:41:18 +08:00
Qiufeng 6c96cddd4e fix: reconcile stale staged updates
TallyNote release / linux-x64 (push) Successful in 6m50s
2026-09-05 16:31:53 +08:00
6 changed files with 24 additions and 12 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "tallynote",
"version": "1.2.5",
"version": "1.2.8",
"private": true,
"type": "module",
"packageManager": "pnpm@9.0.6",
+3 -1
View File
@@ -58,6 +58,7 @@ import { isNewerVersion } from "./update.js";
import {
ACTIVE_UPDATE_STATUSES,
checkForUpdate,
currentReleaseVersion,
publicCheckFromCache,
publicUpdateJob,
reconcileOrphanedUpdateJobs,
@@ -1016,7 +1017,8 @@ export async function buildApp(database: DatabaseContext, config: AppConfig) {
// A package may have been downloaded before the host was upgraded by
// another path. Never apply a staged archive that is no longer newer
// than the release currently serving traffic.
if (!isNewerVersion(config.appVersion, staged.version)) {
const effectiveCurrentVersion = currentReleaseVersion(config) ?? config.appVersion;
if (!isNewerVersion(effectiveCurrentVersion, staged.version)) {
const now = Date.now();
database.sqlite.prepare("UPDATE update_jobs SET status='failed', error_message=?, completed_at=?, updated_at=? WHERE id=? AND status='staged'").run("暂存更新已过期,当前版本无需再次升级", now, now, stagedJobId);
throw new AppError(409, "UPDATE_NOT_AVAILABLE", "暂存更新已过期,请重新检查更新");
+3 -2
View File
@@ -439,7 +439,7 @@ function recoveryStateJobId(filePath: string): string | null {
}
}
function currentReleaseVersion(config: AppConfig): string | null {
export function currentReleaseVersion(config: AppConfig): string | null {
try {
const target = realpathSync(config.currentLink);
const releases = realpathSync(config.releasesDir);
@@ -498,7 +498,8 @@ export function reconcileOrphanedUpdateJobs(database: Database.Database, config:
// completes the same release) before returning to this page. Treat the
// archive as an expired terminal task so it cannot keep blocking the
// queue or appear as an "apply" action for the current version.
if (row.status === "staged" && !isNewerVersion(config.appVersion, row.version) && !matchingFreshRequest && !matchingFreshState) {
const effectiveCurrentVersion = releaseVersion ?? config.appVersion;
if (row.status === "staged" && !isNewerVersion(effectiveCurrentVersion, row.version) && !matchingFreshRequest && !matchingFreshState) {
const changed = database.transaction(() => {
const result = database.prepare(`
UPDATE update_jobs
+3 -3
View File
@@ -32,9 +32,9 @@ function App() {
const logoutInFlight = useRef(false);
useDialogAccessibility();
const notify = useCallback((message: string, kind: "success" | "error" | "info" = "info") => {
// The placement container owns the responsive right inset. Keeping the
// item offset at zero avoids pushing narrow-screen notices off canvas.
const options = { content: message, duration: 4200, placement: "top-right" as const, offset: [24, 76] as [number, number], zIndex: 6000 };
// Keep notices in the lower-right safe area so they do not compete with
// the header controls or obscure the page title.
const options = { content: message, duration: 4200, placement: "bottom-right" as const, offset: [24, 24] as [number, number], zIndex: 6000 };
const show = kind === "success" ? NotificationPlugin.success : kind === "error" ? NotificationPlugin.error : NotificationPlugin.info;
void show(options);
}, []);
+10 -1
View File
@@ -451,7 +451,16 @@ export default function UpdatePage({
setError("");
try {
const result = await api<UpdateInfo>("/api/update/check", { method: "POST" });
mergeInfo(result, true);
// The check endpoint returns release metadata but not task state. Read
// the status endpoint once more so reconciliation performed before the
// check is authoritative: an expired staged task must disappear from
// this page immediately instead of surviving until a full refresh.
const status = await api<UpdateInfo>("/api/update/status");
setLiveInfo((current) => ({
...(current ?? result),
...result,
job: status.job,
}));
notify?.("版本检查完成", "info");
} catch (e) {
if (e instanceof ApiError && e.status === 429) {
+4 -4
View File
@@ -115,14 +115,14 @@ input:focus-visible, textarea:focus-visible, select:focus-visible {
box-sizing: border-box !important;
transition: all 0.22s cubic-bezier(0.16, 1, 0.3, 1) !important;
}
.t-notification__show--top-right {
top: 76px !important;
.t-notification__show--bottom-right {
bottom: 24px !important;
right: 24px !important;
z-index: 6000 !important;
}
@media (max-width: 768px) {
.t-notification__show--top-right {
top: 16px !important;
.t-notification__show--bottom-right {
bottom: 16px !important;
right: 16px !important;
left: 16px !important;
width: auto !important;