Surface display names instead of UUID fragments #107

Closed
opened 2026-07-28 06:03:16 +00:00 by claude-bot · 2 comments

Severity: MEDIUM

The problem

Attribution data exists but is not surfaced, so collaboration is effectively invisible:

  • Comments are attributed to a UUID fragment: c.created_by.slice(0, 8)
    "3f2a9c1e…" (ReviewWorkspacePage.tsx:313)
  • Decision history omits the author entirely (HistoryTab:348-363)
  • The toolbar shows Album: {uuid8}… · SHA: {uuid8}… (:469-472)

There is also no API to resolve a user id to a name — only /auth/me exists.

docs/circa-spec.md §7.4 requires "who changed this" to be answerable in the workspace, and §11.2
requires recent editor identity.

Why this is separable from #22

#22 carries the full role model, user management, and permission matrix, and sits in v0.4.0. The
display name slice is small, independently valuable, and needed as soon as a second person
touches the collection. It also removes the UUID disclosure that the security milestone requires
anyway (#57) — the two fixes are the same change viewed from different angles.

Scope

  • Include a display name in comment, decision, evidence, and job serializers via a join.
  • Render the author on comments, decision history, notes edits, and manual evidence.
  • Replace the toolbar SHA and album UUID with the filename and a human album name.

Done when

  • Every user-attributable item shows a human name
  • Decision history shows who decided what
  • No raw UUID appears in reviewer-facing UI
  • Grandma's comment is signed with her name

References

  • frontend/src/pages/ReviewWorkspacePage.tsx:313,348-363,469-472
  • docs/circa-spec.md §7.4, §11.2

Related: #22 (full role model), #57 (UUID disclosure).

## Severity: MEDIUM ## The problem Attribution data exists but is not surfaced, so collaboration is effectively invisible: - Comments are attributed to a UUID fragment: `c.created_by.slice(0, 8)` → "3f2a9c1e…" (`ReviewWorkspacePage.tsx:313`) - Decision history omits the author **entirely** (`HistoryTab:348-363`) - The toolbar shows `Album: {uuid8}… · SHA: {uuid8}…` (`:469-472`) There is also no API to resolve a user id to a name — only `/auth/me` exists. `docs/circa-spec.md` §7.4 requires "who changed this" to be answerable in the workspace, and §11.2 requires recent editor identity. ## Why this is separable from #22 #22 carries the full role model, user management, and permission matrix, and sits in v0.4.0. The *display name* slice is small, independently valuable, and needed as soon as a second person touches the collection. It also removes the UUID disclosure that the security milestone requires anyway (#57) — the two fixes are the same change viewed from different angles. ## Scope - Include a display name in comment, decision, evidence, and job serializers via a join. - Render the author on comments, decision history, notes edits, and manual evidence. - Replace the toolbar SHA and album UUID with the filename and a human album name. ## Done when - [ ] Every user-attributable item shows a human name - [ ] Decision history shows who decided what - [ ] No raw UUID appears in reviewer-facing UI - [ ] Grandma's comment is signed with her name ## References - `frontend/src/pages/ReviewWorkspacePage.tsx:313,348-363,469-472` - `docs/circa-spec.md` §7.4, §11.2 Related: #22 (full role model), #57 (UUID disclosure).
claude-bot added this to the v0.3.0 milestone 2026-07-28 06:03:16 +00:00
Author

Picking this up. Probed first, and most of this issue is already done — by #57/#64, which landed the serializer half while this was open.

What is already true

  • Every serializer that has an author already carries created_by_display_name and is_own, resolved in one batched query per response (app/api/attribution.py). That covers comments, decisions, evidence, note revisions and jobs — the whole list in "Scope", bullet one.
  • Comments are not attributed to a UUID fragment. ReviewWorkspacePage.tsx renders c.created_by_display_name; the c.created_by.slice(0, 8) this issue quotes is gone.
  • The toolbar's Album: {uuid8}… · SHA: {uuid8}… is gone, removed under #104 as developer debris.
  • "There is also no API to resolve a user id to a name — only /auth/me" is no longer a gap: names arrive inline on the rows that have authors, which is better than a resolve endpoint since it costs no extra round trip and exposes no id.

So one of the four "Done when" boxes was already ticked and a second was mostly ticked.

What was actually left

  1. The decision history omitted the author entirely. The API has carried the name since #57 and nothing read it, so the workspace said a date had been approved and not by whom — which docs/circa-spec.md §7.4 names explicitly. Fixed.
  2. Evidence rows showed no author. Added on manual rows only, and that is a decision rather than an oversight: every source carries an author and on the machine ones it is System, which SOURCE_LABELS already says better in the first column. Printing "System" on every EXIF and OCR row is noise repeating what the row says. A manual row is the one where a person is the origin, and there the name is part of the provenance — "Mom wrote 1972 on the back" means something different depending on who wrote that down.
  3. Duplicate of #3f2a91c0…, which this issue was asked to absorb. Now the other scan's filename, as a link. The other photograph is fetched rather than joined into PhotoOut: a filename on the serializer would have to be resolved for every row of every listing — a join on the hot path to serve one line on one screen — whereas this is one request, on the only screen that shows it, usually already cached because the queue has been through it. Being a duplicate is still stated when that fetch fails, because "this is a duplicate" is established and "of that one" is a second fact a 404 or a permission boundary may not supply; collapsing them would let a failed request quietly unsay something true (#78).
  4. The Jobs page rendered photo: 164b0909-c06b… — a raw UUID fragment in reviewer-facing UI, which "Done when" forbids and nothing had noticed. A queue of work was a column of hex a reviewer could neither tell apart nor navigate to, and the commonest reason to open that page is to find out what happened to a rerun queued a minute ago. JobOut gains entity_label, resolved in batch and scoped to the collection (#70), and the page links to the photograph by name. entity_id stays on the wire because the UI needs it to build the link — #57's rule is about user ids, which are what a forged session cookie is built from; a photo id is not a credential.

Not done, and why

No attribution on PhotoOut for who excluded a scan (#106). _photo_out has no batched name resolution and is called per row in every listing, so adding one there is a join on the hot path — the same objection as the duplicate filename. The audit ledger records the actor, which is what §6.4 asks for. Worth its own issue if the excluded queue turns out to need it.

Picking this up. **Probed first, and most of this issue is already done** — by #57/#64, which landed the serializer half while this was open. ## What is already true - **Every serializer that has an author already carries `created_by_display_name` and `is_own`**, resolved in one batched query per response (`app/api/attribution.py`). That covers comments, decisions, evidence, note revisions and jobs — the whole list in "Scope", bullet one. - **Comments are not attributed to a UUID fragment.** `ReviewWorkspacePage.tsx` renders `c.created_by_display_name`; the `c.created_by.slice(0, 8)` this issue quotes is gone. - **The toolbar's `Album: {uuid8}… · SHA: {uuid8}…` is gone**, removed under #104 as developer debris. - **"There is also no API to resolve a user id to a name — only `/auth/me`"** is no longer a gap: names arrive inline on the rows that have authors, which is better than a resolve endpoint since it costs no extra round trip and exposes no id. So one of the four "Done when" boxes was already ticked and a second was mostly ticked. ## What was actually left 1. **The decision history omitted the author entirely.** The API has carried the name since #57 and nothing read it, so the workspace said a date had been approved and not by whom — which `docs/circa-spec.md` §7.4 names explicitly. Fixed. 2. **Evidence rows showed no author.** Added on **manual rows only**, and that is a decision rather than an oversight: every source carries an author and on the machine ones it is `System`, which `SOURCE_LABELS` already says better in the first column. Printing "System" on every EXIF and OCR row is noise repeating what the row says. A manual row is the one where a *person* is the origin, and there the name is part of the provenance — "Mom wrote 1972 on the back" means something different depending on who wrote that down. 3. **`Duplicate of #3f2a91c0…`**, which this issue was asked to absorb. Now the other scan's filename, as a link. The other photograph is fetched rather than joined into `PhotoOut`: a filename on the serializer would have to be resolved for every row of every listing — a join on the hot path to serve one line on one screen — whereas this is one request, on the only screen that shows it, usually already cached because the queue has been through it. **Being a duplicate is still stated when that fetch fails**, because "this is a duplicate" is established and "of that one" is a second fact a 404 or a permission boundary may not supply; collapsing them would let a failed request quietly unsay something true (#78). 4. **The Jobs page rendered `photo: 164b0909-c06b…`** — a raw UUID fragment in reviewer-facing UI, which "Done when" forbids and nothing had noticed. A queue of work was a column of hex a reviewer could neither tell apart nor navigate to, and the commonest reason to open that page is to find out what happened to a rerun queued a minute ago. `JobOut` gains `entity_label`, resolved in batch and scoped to the collection (#70), and the page links to the photograph by name. `entity_id` stays on the wire because the UI needs it to build the link — #57's rule is about *user* ids, which are what a forged session cookie is built from; a photo id is not a credential. ## Not done, and why **No attribution on `PhotoOut` for who excluded a scan (#106).** `_photo_out` has no batched name resolution and is called per row in every listing, so adding one there is a join on the hot path — the same objection as the duplicate filename. The audit ledger records the actor, which is what §6.4 asks for. Worth its own issue if the excluded queue turns out to need it.
Author

Done in 8c4298a, CI green (run 65).

All four boxes, with the probe corrections noted in my previous comment:

  • Every user-attributable item shows a human name — comments, note revisions and the conflict banner already did (#57/#64); the decision history and manual evidence now do
  • Decision history shows who decided what
  • No raw UUID appears in reviewer-facing UI — this needed the Jobs page, which the issue did not mention and which was the last one left
  • Grandma's comment is signed with her name

Mutation-tested: dropping the author from the history fails one test, showing it on machine evidence rows fails one, rendering nothing when the duplicate cannot be read fails one, and printing the raw id on the jobs page fails one.

One thing worth recording, because it is the change proving itself. The exclusion end-to-end test started failing on getByText("1979") resolving to two elements. Every ingest in that suite uploads the same fixture bytes, so every photograph after the first is an exact duplicate of redbk42_010_1979.jpg — and the Current State card now names that scan instead of printing eight hex characters, which put a second "1979" on the card. The assertion was tightened to say which one it means. Before this change the same card said Duplicate of #a1b2c3d4…, which is precisely the uselessness the issue is about: it could not have collided with anything, because it told the reviewer nothing.

Frontend tests 202 → 212 (a new JobsPage.test.tsx, which had none).

Done in `8c4298a`, CI green (run 65). All four boxes, with the probe corrections noted in my previous comment: - [x] Every user-attributable item shows a human name — comments, note revisions and the conflict banner already did (#57/#64); the decision history and manual evidence now do - [x] Decision history shows who decided what - [x] No raw UUID appears in reviewer-facing UI — this needed the Jobs page, which the issue did not mention and which was the last one left - [x] Grandma's comment is signed with her name Mutation-tested: dropping the author from the history fails one test, showing it on machine evidence rows fails one, rendering nothing when the duplicate cannot be read fails one, and printing the raw id on the jobs page fails one. **One thing worth recording, because it is the change proving itself.** The exclusion end-to-end test started failing on `getByText("1979")` resolving to two elements. Every ingest in that suite uploads the same fixture bytes, so every photograph after the first is an exact duplicate of `redbk42_010_1979.jpg` — and the Current State card now *names* that scan instead of printing eight hex characters, which put a second "1979" on the card. The assertion was tightened to say which one it means. Before this change the same card said `Duplicate of #a1b2c3d4…`, which is precisely the uselessness the issue is about: it could not have collided with anything, because it told the reviewer nothing. Frontend tests 202 → 212 (a new `JobsPage.test.tsx`, which had none).
Sign in to join this conversation.
No description provided.