## Summary

The playground transcript drew the same citation twice, as numbered badges on the figure card and as a card per citation below it carrying a provenance block, and printed the raw status code. Now the figure card is only the answer, and one ledger under it lists each source on a row: number, page, what it is, the served value, a status dot. A flagged source turns only its value and dot red and prints the status's plain-language line beneath the row; a reconciled source prints nothing more.

The status registry's fourteen labels are rewritten for a reader, and the references and flags routes serve that label as a new `reason` field; a misread's line names both the printed and the served figure. The batch card's expanded rows and the review tab show the same line in place of the code. Everything else in the transcript renders as before.

Design: https://claude.ai/code/artifact/0a4e1e69-a662-40ec-958a-28bda029d20d

## Related Jira Issue

**Jira**: [QUE-368](https://mandeng.atlassian.net/browse/QUE-368)

## Acceptance Criteria - Functional Evidence

### AC1: An answer with one figure cited in two places renders one figure card without badges and a ledger beneath it; clicking a row highlights the cited region

**Real Example: Playwright screenshot of the branch playground against the hosted RDS**

Local branch playground on port 8104, hosted RDS, document 294 "Q4 2025 Earnings Supplement", question "what is the total principal of risk rating 5 loans on the watch list".

**Results:**

- Figure card "$572 million" with no badges
- Ledger with two rows: `1  p.12  Table cell | Total Principal: $572 million | green dot` and `2  p.12  Chart value | $572 million | red dot`
- Row click drives the document pane through the same `onSelectRef` handler the reference cards used (unchanged in `app.jsx`)

**Before (main branch on port 8105, same question):** badges 1 and 2 on the card, two reference cards with VALUE/STATUS/NOTE blocks, status printed as `value_unreconciled`.

**Production Data Tested:**

- Document 294 "Q4 2025 Earnings Supplement" (hosted RDS) -> after: `after-flagged.png`; before: `before-flagged.png`

---

### AC2: A flagged source renders its value and dot in red, row text in the standard ink, and its plain-language line beneath; a reconciled source renders no line

**Real Example: The flagged and the reconciled answer on document 294**

**Results:**

- `after-flagged.png` row 2: "$572 million" and the dot in red; "Chart value" and "p.12" in the standard ink; beneath the row: "Read from the chart once, without a second check. Worth a manual look."
- `after-flagged.png` row 1 (reconciled): no line
- `after-reconciled.png` (question "what is the total unfunded commitment"): one row `1  p.24  Chart value | $419 | green dot`, no line

**Production Data Tested:**

- Document 294, two questions -> `after-flagged.png`, `after-reconciled.png`

---

### AC3: The transcript never shows a status code; each of the fourteen codes renders its plain line, value_misread with both figures

**Real Example: The flags route on the branch server**

```
GET /api/document/294/flags
40 flags, every one with a non-empty `reason`
value_misread       -> "The page prints 5% here, not 6%. Check which is right."
value_unreconciled  -> "Read from the chart once, without a second check. Worth a manual look."
```

**Results:**

- 40 of 40 flags carry a non-empty `reason`
- A misread's line names both the printed and the served figure
- Unit tests in `tests/playground/test_references.py` cover: every registry label is a sentence with no code-style underscores; the reason equals the registry label; a misread names both figures for both note shapes the reader writes; an unparsable misread note falls back to the label; no status gives no reason; the label never carries a code

**Production Data Tested:**

- Document 294 flags route -> 40 flags, 0 without a reason

---

### AC4: A cell citation whose row line item is also cited renders once

**Real Example: The AC1 question, whose LangSmith run cites the cell, its row and the chart value**

**Results:**

- Already the behaviour of `_resolve_refs` in `app.py` (`covered_lines`), unchanged
- `after-flagged.png`: the run cites three references, the ledger shows two rows

**Production Data Tested:**

- Document 294, "what is the total principal of risk rating 5 loans on the watch list" -> 3 citations, 2 ledger rows

---

### AC5: Screenshots before and after for a reconciled and a flagged answer

**Results:**

- `before-flagged.png`, `after-flagged.png`, `after-reconciled.png` as above
- All three attached to QUE-368

---

## Test Evidence

**Command**: `uv run pytest -q -p no:cacheprovider`
**Result**: PASS
**Output**: `700 passed, 12 warnings in 19.34s` (before the test file was added)

**Command**: `uv run pytest tests/playground -q -p no:cacheprovider`
**Result**: PASS
**Output**: `116 passed, 5 warnings in 12.89s`

**Command**: `uv run pytest tests/playground/test_references.py -q`
**Result**: PASS
**Output**: `7 passed, 4 warnings in 4.43s`

**Command**: `uv run pre-commit run --files <all changed files>`
**Result**: PASS
**Output**: ruff Passed, ruff-format Passed, pyright type checking Passed, oxlint (playground ui) Passed, prettier check (playground ui) Passed, stylesheet token check (playground ui) Passed, compiled output up to date (playground ui) Passed

## Changes Made

- `src/quber/core/extractors/base.py`: the fourteen `CELL_STATUS_REFERENCE` labels rewritten in plain language (codes and inspect flags unchanged)
- `src/quber/playground/app.py`: `Reference` gains `reason` and `printed`; `Flag` gains `reason`; `_reason()` builds the line from the registry label, naming both figures on a misread; `_label()` names what a source is without the status code
- `src/quber/playground/ui/src/references.jsx`, `references.css`: the ledger replaces the reference cards and provenance blocks
- `src/quber/playground/ui/src/answer.jsx`, `answer.css`: badges removed from the figure card and prose bubble
- `src/quber/playground/ui/src/chat.jsx`, `batch.jsx`, `review.jsx`: wiring; batch rows and the review tab show the reason
- `src/quber/playground/ui/src/playground-tokens.css`: one spacing token for the page column
- `src/quber/playground/static/app.js`, `app.css`: rebuilt
- `tests/playground/test_references.py`: new, 7 tests

Commits: 5945690, 8146444, f0f114d

https://claude.ai/code/session_012MrxUTZDhTxkbPXnUu5ZXN
