Missing chrome: no logout, no upload UI, manual evidence unreachable #101
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: MEDIUM
The problems
Three pieces of basic chrome are missing, each with the backend already in place.
No logout button.
frontend/src/components/AppShell.tsx:21-27shows the user but offers nosign-out, while
POST /api/auth/logoutexists atbackend/app/api/routes/auth.py:76. On a sharedfamily machine you cannot sign out.
No upload UI.
frontend/src/api/client.ts:61hasapi.uploadwith no caller. Ingest requirescurl. This is legitimately deferrable if a watch-directory worker is the real plan — but the README
should say so, and the bulk ingest issue covers the larger version.
Manual evidence is unreachable.
frontend/src/api/photos.ts:51-66implementscreateManualEvidenceandsupersedeEvidence; no UI calls either. So the evidence model's"manual" source — how a reviewer records "Mom wrote 1972 on the back" as evidence rather than
burying it in a decision rationale — has plumbing on both sides and no interface.
Scope
precision, note).
superseded_byhas landed.Done when
References
frontend/src/components/AppShell.tsx:21-27frontend/src/api/photos.ts:51-66;client.ts:61backend/app/api/routes/auth.py:76Picking this up.
Probe first. All three claims hold, which is worth saying given how many issues turned out to
be stale last session:
AppShell.tsxshows the user and offers no sign-out.POST /api/auth/logoutis live (it hasmoved to
auth.py:158since this was filed) and revokes the session server-side, not just thecookie.
createManualEvidenceandsupersedeEvidenceinapi/photos.tshave zero callers infrontend/src.api.uploadinapi/client.ts:92likewise has zero callers.Two corrections to the issue text:
The form has no "source" field to offer. The scope here asks for "source, date range,
precision, note", but
create_manual_evidencehard-codessource=EvidenceSource.manualandreliability=EvidenceReliability.high. A source picker would be a control whose value theserver ignores. The form is date low, date high, precision, note.
The
superseded_byprecondition has already landed. This issue defers the supersedeaffordance until "the backend issue that fixes
superseded_by" is done — that is #80, and itis in:
EvidenceRepository.supersedeis called from both routes, andPOST /photos/{id}/evidence/manualaccepts asupersedesid so that retiring one row andrecording its replacement happen in one transaction. The frontend wrappers do not expose
either parameter yet, so that atomicity is currently unreachable — worth fixing here, since the
route's own comment says splitting it into two requests "leaves a window where the photo has
neither or both".
Splitting the upload UI out as #142, per the plan for this milestone. It is the browser's first
write path into the archive, its failures are per-file rather than per-request, and there is a real
undecided question behind it (browser upload vs. a watch-directory worker) that should not be
settled as the third bullet of a chrome ticket.
So this issue is now: a logout control, and manual evidence made reachable — including
supersede/replace. Remaining scope box:
A minimal upload page, or a README note→ #142Done in
4849694, CI green (run 64).Logout. In the nav rail footer. Three things beyond issuing the request: it empties the entire query cache rather than just the identity — this issue's own scenario is a shared family machine, and invalidating
["auth","me"]alone would return the next person to the sign-in page with every photograph, comment and note the previous reviewer opened still in memory behind it; it treats a 401 as success, because the session had already ended and reporting that as a failure would tell someone standing up from a shared machine that they are still signed in; and it says so loudly when the server refuses, because the dangerous failure is the silent one.Manual evidence. A native
<dialog>behind "Edit evidence…" in the evidence panel. Add, replace, retire. The shape was chosen rather than defaulted: the panel is 420px and an evidence row is already the control that adopts a date (#96), so hanging Replace and Retire off the same row would put three actions on one target, two destructive, in the width of a date.The frontend wrappers were missing the two parameters that make the atomic replace reachable —
supersedeson the manual route andsuperseded_byon supersede — so the one-transaction path #80 built had no way to be called. Replacing is now a single request, asserted end to end against the database.A third thing no issue named: nothing rendered
DateEvidence.notesanywhere, so a manual row whose whole content is the sentence had no dates and noraw_valueand drew as—— an empty answer for evidence that says something. The panel now shows the note.Modality is held in a real browser. jsdom 30 implements none of
<dialog>— notshowModal, notclose, not inertness — sotest/setup.tsstubs the opening with its limits written down ande2e/tests/evidence-dialog.spec.tsasserts the focus trap, Escape, and that Approve behind the backdrop can neither take focus nor be clicked. Writing that corrected a wrong belief: a programmaticelement.click()fires regardless of inertness, because the platform blocks user interaction and not script, so the first version of the assertion failed against a correctly modal dialog.Two tests were worthless until mutation testing said so, which is the strongest argument for the practice this project has produced. The cache-clearing assertions passed whether the code cleared anything or not —
renderWithProviderssetsgcTime: 0, which collects an unobserved query the instant it is written, sosetQueryDatavanished before the next line ran. And the keying test passed withkey={photo.id}deleted, because without the next photograph in cache the page renders "Loading photo…" for a tick and that early return unmounts the workspace, resetting state by remount rather than by key. Both now reproduce production and both fail when broken.Frontend tests 158 → 185, end-to-end 7 → 10.
Two side effects worth recording. The upload UI is #142. And this exposed a pre-existing race in
signInThroughTheUi(fixed inffcf069): it returned after the click rather than the response, so a caller'spage.gotocould cancel the login browser-side — the server logged a 200 and the browser never processed theSet-Cookie, and the test then waited out its full timeout on a page about HTTP caching.Remaining known gap, not in scope here: nothing gates write controls by role, so a viewer is offered buttons the API will refuse. That is #22's territory.