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.
7.4 KiB
Phase 154 — sync + upload stamps say WHEN (date-qualified freshness)
Source: owner UX audit 2026-10-03 (live interactive-browser pass): "RAG 'Synced 16:06' badge is time-only — ambiguous the next day" + "Sources 'Uploaded — press Sync sources to import it.' has no timestamp — it's server-persisted and re-rendered on every mount; after that archive is imported by a sync, the line can sit there indefinitely and mislead."
Story: n/a (audit-derived phase)
Context: sources.js::fmtSyncTime(iso) (L814–819) — HH:MM only; its consumer applySyncSuccess (L1195–1196) settles the RAG sync button as Synced ${time} (or bare "Synced" when the ISO is unparseable — the app-restart case: the sync status is in-memory, A12). The label parts writer (L821 onward) carries the same text in the button title + the running aria-label — the qualified stamp rides all three (the phase-128 parts-writer idiom: write once, three surfaces). git-sources.js::fmtUploadResult(detail, name) (L1128–1133) — the no-count contract (phase 90): Uploaded <name> — press Sync sources to import it. (fresh 202 settle, the page-local lastUploadName) or the nameless Uploaded — press Sync sources to import it. (the boot re-render, initUploadStatus L1279–1304, where the name is unknown after a reload). GET /api/git-sources/upload/status (git_sources.py L704–729) ALREADY exposes started_at / finished_at (ISO) + detail — the server change is ZERO; this phase is frontend-only. The per-page duplication idiom is established (history.js L301's copyText, sources.js vs app.js) — the date-qualifier helper is duplicated into the two modules, not shared.
Objective
Both freshness stamps tell the owner when: a same-day stamp stays HH:MM (byte-identical to today's behavior); a non-same-day stamp carries the date (M/D, h:MM AM/PM — the table-date family); the Sources upload line carries its completion stamp on every render path (fresh settle AND boot re-render), so a stale "Uploaded" line is self-dating.
Dependencies
150_drop_sync_result(complete) — the settled sync button is the sole "Synced" surface (the phase-149 box is the running label; the button settles it).64_sync_upload_progress(complete) — the upload 202/poll/re-attach state machine this phase's line change rides on.90_upload_no_scan(complete) — the no-count result-line contract (phase 90, A2) this phase EXTENDS (a stamp is added; no counts return).
Design (shared by all tasks — the executor reads this, not the chat)
- The date-qualifier rule (ASSUMPTION A1, owner-locked): the stamp is computed from the ISO's LOCAL date (the page's clock — the table dates already use local time). Same local year as now AND same calendar day →
HH:MM(byte-identical to the currentfmtSyncTimeoutput — a same-day badge is unchanged). Otherwise, same year →M/D, h:MM AM/PM(e.g.10/2, 3:14 PM); different year →M/D/YYYY, h:MM AM/PM(e.g.1/3/2025, 9:12 PM). An unparseable/missing ISO → the existing fallback (sync: bare "Synced"; upload: no stamp — the pre-phase strings).h:MMuses the locale 12-hour form WITHOUT leading zero on the hour (matching the table dates'5:34:06 PMfamily — drop the seconds), month/day without leading zero (the tables'M/D/YYYYshape). - RAG sync badge (task 01):
fmtSyncTimebecomes the date-qualifier (keep the name — its contract widens; or addfmtStampand delegate — the executor's choice, the pins fix the output).applySyncSuccessis unchanged in shape (settleSyncButton(time ? \Synced ${time}` : "Synced")— the qualifier lives inside the formatter). The parts writer's comment block (L821 onward) names the qualified stamp in the label parts where theHH:MM` contract is cited (cite, don't restate). - Sources upload line (task 02):
fmtUploadResult(detail, name, when)—whenis the precomputed stamp string (the caller computes it fromstatus.finished_at, falling back tostatus.started_at; the success state always has afinished_aton the server, the fallback is the defensive guard for a legacy/partial payload). Shapes (the phase-90 contract extended, name present/absent):Uploaded <name> (<stamp>) — press Sync sources to import it./Uploaded <stamp> — press Sync sources to import it.;whenempty (running state, legacy payload) → BYTE-IDENTICAL to the current strings (the no-stamp fallback). Call sites: the poll settle (L1239–1244, the page-locallastUploadName) andinitUploadStatus's success branch (L1279–1304, nameless) — both readstatus.finished_atfrom the same/upload/statuspayload. The L78–96 and L1125–1133 comment blocks name the stamp in the contract (cite, don't restate). - A11y: the stamps are text in existing
role="status"/ button-label surfaces — no new nodes, no color-alone state (B5).
Tasks
01_rag_sync_stamp.md— the date-qualifier insources.js(fmtSyncTime's contract widens; same-day byte-identical).02_upload_line_stamp.md— thewhenparam onfmtUploadResult+ both call sites readingfinished_at/started_at.03_unit_pins.md— the qualifier's boundary pins + both stamp shapes (with/without name, with/without stamp).04_e2e_status_freshness.md— the dedicated Playwright suite.
Testing & Quality
- Unit: source-level pins — the qualifier (same-day
HH:MMbyte-identical; yesterdayM/D, h:MM AM/PM; last-yearM/D/YYYY, h:MM AM/PM; garbage/missing ISO → the pre-phase fallbacks — a fixed clock in the test, not wall-clock math onnew Date());fmtUploadResult's four shapes (name×stamp, name×no-stamp byte-identical, no-name×stamp, no-name×no-stamp byte-identical); theapplySyncSuccesssettle string for a non-same-dayfinished_at. - Integration: ZERO server change — the
/upload/statuspayload is untouched (regression guard: the full-dict pin, if one exists, stays byte-identical). - Coverage: >90% on the modified JS (source-level pins + E2E).
- The phase is "Complete" only when the entire project test suite passes 100% (every test in the project, not just this phase's).
- E2E:
uv run pytest tests/e2e/test_sync_status_freshness.py -v --no-covin isolation (DB up:podman compose up -d db).
Documentation & Consistency
- The final pass reviews
README.mdandAGENTS.mdin full against the project as it stands after this phase — fixing every inconsistency found, including pre-existing ones not caused by the phase. - Tasks update
README.md/AGENTS.mdimmediately when their own changes are user-visible (the stamp format is a visible UI change — name it where the sync/upload surfaces are described).
Completion Criteria
- a same-day sync settles as
Synced HH:MM— byte-identical to pre-phase (the pin + the E2E) - a non-same-day
finished_at(the unit pins; the E2E asserts the same-day shape on a real mock sync — the date branch is unit-pinned with a fixed clock, the E2E never depends on the wall clock) - the upload line carries the stamp on BOTH render paths (the fresh 202 settle with the name; the boot re-render without it) — the E2E seeds a prior upload via the API and reloads the page
- a running/legacy upload payload renders the pre-phase strings byte-identically (no partial-state regression)
uv run pytestgreen; the dedicated E2E green in isolation;uv run ruff check . && uv run pyrightclean;app/coverage >90%- one atomic commit per AGENTS.md rule 8 (
--no-gpg-sign)