phase: 154_sync_status_freshness
Build and Push Containers / build-and-push-app (push) Successful in 2m26s
Build and Push Containers / build-and-push-db (push) Successful in 16s

All 4 task files exist in `complete/`, and only `00_phase.md` remains in `todo/`. This is the final verification pass. I'll read through the task files and check the current state of the code.
This commit is contained in:
2026-10-04 14:24:33 -04:00
parent 966c4b0540
commit c275735db4
36 changed files with 2526 additions and 99 deletions
+91 -18
View File
@@ -75,8 +75,12 @@
* "Processing…" label for the WHOLE run (phase 90, A2 — the run
* is unpack + register only: no file, no "(n/m)" counts, no
* title) and settles it: success → the ready-for-sync line
* ("Uploaded <name> — press Sync sources to import it.",
* fmtUploadResult off the status's {"message": "uploaded"}
* ("Uploaded <name> (<stamp>) — press Sync sources to import
* it." — the phase-154 completion stamp, fmtUploadStamp off the
* status's finished_at, the A1 date-qualified form cited in
* sources.js's fmtSyncTime, not restated; a running / partial
* payload without timestamps renders the byte-identical pre-phase
* line — fmtUploadResult off the status's {"message": "uploaded"}
* detail — the role=status result line) + the "Archive uploaded
* — press Sync sources to import it." announce + loadSources
* (the new/updated row lands with the Local badge; a re-upload
@@ -93,8 +97,10 @@
* (a reload mid-run re-attaches — no second upload), a terminal
* run re-renders its result line / error banner (the safe name
* was page-local — lastUploadName is null after a reload — so the
* re-rendered line is the nameless "Uploaded — press Sync sources
* to import it.").
* re-rendered line is the nameless "Uploaded <stamp> — press
* Sync sources to import it." — the stamp comes from the SERVER
* payload, so the line is self-dating even with the name
* unknown, phase 154).
* • remove — a row's Remove button opens the page-local
* confirmation modal (#remove-confirm-dialog, a real
* role="alertdialog" — the native confirm() retired, phase 69):
@@ -1096,7 +1102,10 @@ export async function mount(root) {
* "Processing…" label for the WHOLE run (phase 90, A2 — the unpack
* has no file-level progress: no file, no "(n/m)" counts, no title)
* and settles it: success → the ready-for-sync line ("Uploaded
* <name> — press Sync sources to import it.", fmtUploadResult off
* <name> (<stamp>) — press Sync sources to import it." — the
* phase-154 completion stamp, fmtUploadStamp off the status's
* finished_at; the A1 date-qualified form is cited in
* fmtUploadResult's comment, not restated — fmtUploadResult off
* the status's {"message": "uploaded"} detail) in the role=status
* result line + the "Archive uploaded — press Sync sources to
* import it." announce + loadSources (NO second toast — it already
@@ -1117,23 +1126,75 @@ export async function mount(root) {
* (The phase-49 synchronous 200 paragraph is superseded by phase 64;
* the phase-64 scan counts are superseded by phase 90.) */
/* The result line's text (phase 90, A2 — the no-count contract): the
/* The result line's text (phase 90, A2 — the no-count contract,
extended by phase 154 task 02 with the completion stamp): the
status success detail is exactly {"message": "uploaded"} — the
sync-style counts the phase-64 line rendered are gone (the scan —
and its counts — belong to the Sync button, which renders them on
the RAG page). `name` is the accepted 202's safe source name
(lastUploadName) when the run started on this page; it is null
after a reload or on the 409 re-attach (the line still points at
the next step, only without the name). */
function fmtUploadResult(detail, name) {
the next step, only without the name). `when` is the PRECOMPUTED
completion stamp (fmtUploadStamp(status) — the phase-154 A1
date-qualified form, the rule cited in sources.js's fmtSyncTime,
not restated); "" (a running / legacy / partial payload without
timestamps) renders the byte-identical pre-phase-154 strings. */
function fmtUploadResult(detail, name, when) {
if (detail && detail.message === "uploaded") {
return name
? `Uploaded ${name} — press Sync sources to import it.`
if (name) {
return when
? `Uploaded ${name} (${when}) — press Sync sources to import it.`
: `Uploaded ${name} — press Sync sources to import it.`;
}
return when
? `Uploaded ${when} — press Sync sources to import it.`
: "Uploaded — press Sync sources to import it.";
}
return "The upload finished.";
}
/* Phase 154 (task 02): the upload line's completion stamp —
* PRECOMPUTED from the /upload/status payload (the per-page
* duplication idiom: the SAME date-qualifier as sources.js's
* fmtSyncTime, duplicated, not shared — the A1 rule — same local
* day `HH:MM`, same year `M/D, h:MM AM/PM`, across years
* `M/D/YYYY, h:MM AM/PM` — is cited there, not restated). Reads
* status.finished_at first (the success state always has it on the
* server), falls back to status.started_at (the defensive guard
* for a legacy / partial payload — a corrupt finished_at degrades
* to the started_at stamp rather than to no stamp), and yields
* "" when both are missing/absent or every ISO is unparseable
* (the pre-phase-154 byte-identical fallback — the line renders
* the pre-stamp copy). */
function fmtUploadStamp(status) {
const s = status || {};
let d = null;
for (const iso of [s.finished_at, s.started_at]) {
if (!iso) continue;
const t = new Date(iso);
if (!Number.isNaN(t.getTime())) {
d = t;
break;
}
}
if (d === null) return "";
const now = new Date();
const pad = (n) => String(n).padStart(2, "0");
if (
d.getFullYear() === now.getFullYear() &&
d.getMonth() === now.getMonth() &&
d.getDate() === now.getDate()
)
return `${pad(d.getHours())}:${pad(d.getMinutes())}`;
const h12 = d.getHours() % 12 || 12;
const ampm = d.getHours() < 12 ? "AM" : "PM";
const date =
d.getFullYear() === now.getFullYear()
? `${d.getMonth() + 1}/${d.getDate()}`
: `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`;
return `${date}, ${h12}:${pad(d.getMinutes())} ${ampm}`;
}
/* Upload-success toast (phase 64 task 05, A2 — owner-locked): the
* "successfully uploaded" confirmation, the phase-55 share-toast
* pattern (frontend/assets/app.js) made page-local. A SINGLE node —
@@ -1237,11 +1298,16 @@ export async function mount(root) {
if (status.state === "success") {
// The run finished (unpack + register only — phase 90): the
// ready-for-sync line (fmtUploadResult reads the no-count
// detail), the announcement, the row lands. NO toast here — it
// detail + fmtUploadStamp's completion stamp — phase 154),
// the announcement, the row lands. NO toast here — it
// already fired at the 202 (A2).
const detail = status.detail || {};
if (uploadResult) {
uploadResult.textContent = fmtUploadResult(detail, lastUploadName);
uploadResult.textContent = fmtUploadResult(
detail,
lastUploadName,
fmtUploadStamp(status)
);
uploadResult.hidden = false;
}
announce("Archive uploaded — press Sync sources to import it.");
@@ -1276,10 +1342,11 @@ export async function mount(root) {
* no second upload, no error); a finished run re-renders its result
* line ONLY (no announce, no toast — the toast fired at the 202, A2;
* the name is unknown after a reload — lastUploadName is null — so
* the line is the nameless "Uploaded — press Sync sources to import
* it.", phase 90); a failed run re-renders its error banner; idle
* does nothing (and a blip is a no-op — the page boots honest
* either way). */
* the line is the nameless "Uploaded <stamp> — press Sync sources
* to import it." — the stamp from the SERVER payload, so the line
* is self-dating even with the name unknown, phase 154; phase 90);
* a failed run re-renders its error banner; idle does nothing (and a
* blip is a no-op — the page boots honest either way). */
async function initUploadStatus() {
if (!uploadBtn) return;
let status;
@@ -1299,9 +1366,15 @@ export async function mount(root) {
if (status.state === "success") {
// The last run's result line only — no announce, no toast (A2).
// The safe name was page-local (lastUploadName is null after a
// reload) — the line still points at the next step (phase 90).
// reload) — the line still points at the next step (phase 90),
// self-dating through fmtUploadStamp's completion stamp
// (phase 154 — the stamp comes from the server payload).
if (uploadResult) {
uploadResult.textContent = fmtUploadResult(status.detail, lastUploadName);
uploadResult.textContent = fmtUploadResult(
status.detail,
lastUploadName,
fmtUploadStamp(status)
);
uploadResult.hidden = false;
}
return;