Skip to content

Library: a rolled pick stands until the owner rolls again, and the AI accuracy ledger says what its numbers are - #229

Merged
manager merged 3 commits into
devfrom
feat/library-batch-2026-09-11
Sep 11, 2026
Merged

Library: a rolled pick stands until the owner rolls again, and the AI accuracy ledger says what its numbers are#229
manager merged 3 commits into
devfrom
feat/library-batch-2026-09-11

Conversation

@manager

@manager manager commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

What changes

The magic book keeps its pick. It was keyed to a digest of the shelf, so rating a book, writing a note, adding a tag or renaming the shelf reported the standing pick as stale and emptied the slot. A roll is the only path that spends the engine, so ordinary library work was buying model calls the owner never asked for. The fingerprint is gone from the digest, the store and the route. A pick now stands until the owner rolls it away, and gives up its place for two free reasons only: the owner owns that book now, or has banned it. The AI shelf already worked this way through settle plus the bench.

The AI accuracy ledger explains itself. It drew six bare ratios (20 / 25) with no unit and hid the counts behind a hover. Now a legend names the hundred points once, every row carries its own unit, the count it was read from is drawn under its meter, and the hover keeps only the next step. Points are whole numbers (11.2 of 20 was false precision), Rated volume is Sample size, and every detail string reads as a sentence.

One review nit from #224, whose code is already on dev: the tag-filter comment named a URL and a date, so it now states the invariant instead.

Checks

  • tsc --noEmit: clean.
  • lint-staged (eslint, prettier, internal-data guard) ran on both commits.
  • No schema change, no CMS change, no migration. Frontend only.
  • Prod state before this ships: all seven book shelves stand idle, so every magic slot there costs a roll today.

…he accuracy ledger says what its numbers are

The magic book kept its pick keyed to a digest of the shelf, so rating a
book, writing a note, adding a tag or renaming the shelf emptied the slot
and pushed the owner into another roll. A roll is the only path that
spends the engine, so ordinary library work was buying model calls nobody
asked for. The fingerprint is gone. A pick now stands until the owner
rolls it away and gives up its place for two free reasons only: the owner
owns that book now, or has banned it.

The AI accuracy ledger drew six bare ratios and hid the counts behind a
hover, so "20 / 25" said nothing about what a point was. A legend names
the hundred points once, every row carries its own unit, the count it was
read from is drawn under its meter, and the hover keeps only the next
step. Points are whole numbers; "Rated volume" is "Sample size".
…n incident report

The review on #224 was right: the comment named a URL and a date, which is
PR-description material and rots in the source. What matters is the rule
itself, that only a switch between two libraries clears the filter.
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

Claude finished @manager's task in 2m 34s —— View job


Review complete

Summary

Magic-book pick persistence. Removing the digest fingerprint and keying a stored pick's survival only to "owned now" / "banned now" (via the new gone() helper in magic-book.ts) is a clean fix for the stated bug — rating, tagging, or renaming a shelf no longer empties the slot. digest.ts and store.ts had every fingerprint reference cleanly removed (verified no leftover references elsewhere in src/), and LIBRARY.md was updated to match.

One correctness gap: the daily-cap branch in magic-book.ts (~line 164–174, the fallback taken when MAGIC_DAILY_CALL_CAP is hit during a roll) still returns current?.pick as 'ready' unconditionally, without running it through the new gone() check that fromStore uses. So a roll that hits the cap on a shelf whose standing pick has since been acquired or banned would still report it as 'ready' — the exact case this PR says should empty the slot. Left an inline comment with a suggested fix.

AI accuracy ledger. The redesign matches the PR description well: a legend states the 100-point/six-signal split once, each row shows earned/max points as whole numbers (Math.round for display, exact fraction still drives the meter width), the count is drawn under the meter instead of hidden behind hover, and the hover now only carries the next step. Verified the six ACCURACY_WEIGHTS still sum to 100, so the legend's claim is accurate. Sample size / Rated volume rename and the sentence-cased detail strings read as described. Font sizes used (12px/13px/14px) are all on the passport's allowed list.

Comment cleanup (#224 follow-up). DashboardContext.tsx's comment now states the invariant instead of the URL+date, consistent with the "repository is public" rule in CLAUDE.md.

Minor, non-blocking: store.ts's updated doc comment has an awkward mid-sentence line break ("the AI shelf's board, and the\n * library's banned titles") left over from the edit — cosmetic only.

No App Router/Tailwind/state-library/global-CSS violations, no any introduced, no UX Core data touched, no dangerouslySetInnerHTML or unsanitized input in this diff. Did not run tsc/lint locally (relying on the PR's stated clean tsc --noEmit and lint-staged run); flagging that gap in case tool access needs adjusting.

// that take a book off the shelf without a roll.
const owned = new Set(digest.ownedTitles.map(normaliseTitle));
const bannedTitles = new Set(stored.banned.map(b => normaliseTitle(b.title)));
const gone = (title: string) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gone() is defined here but only wired into fromStore (the load path). It isn't applied in the daily-cap branch below (~line 164-174), which still returns current?.pick as 'ready' unconditionally when the cap is hit during a roll:

current?.pick
  ? { shelfId: shelf.id, status: 'ready', pick: current.pick }
  : { ... }

That means if a roll hits MAGIC_DAILY_CALL_CAP and the shelf's standing pick has since been acquired or banned, the response still reports it ready — contradicting this PR's own invariant that a pick gives up its place only for those two reasons. Worth reusing gone(current.pick.title) in that branch too.

Fix this →

A roll that hit the cap reported the standing pick as ready without asking
whether the owner has since acquired or banned it, so the one path that
skips the engine was also the one path that ignored the invariant.
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

Claude finished @manager's task in 44s —— View job


Review complete

  • Gather context (new commit 307e172 pushed since last review)
  • Verify whether the previously flagged daily-cap gone() gap was fixed
  • Re-check full diff for new issues
  • Post summary

Summary

The new commit (307e172, "the daily cap answers with the same rule as a load") fixes exactly the correctness gap flagged in the previous pass: src/pages/api/library/magic-book.ts:167 now reads

current?.pick && !gone(current.pick.title)
  ? { shelfId: shelf.id, status: 'ready', pick: current.pick }
  : { shelfId: shelf.id, status: 'empty', note: '...' }

so a roll that hits MAGIC_DAILY_CALL_CAP now runs the standing pick through the same gone() check (owned-now / banned-now) that fromStore already applies on the load path. Both code paths — load (line 68-71) and the capped-roll branch (line 164-175) — now agree on the invariant: a pick only loses its place for those two reasons. This closes the gap; no remaining inconsistency between the two branches.

No other files changed in this push — the rest of the diff (digest.ts, store.ts, AiAccuracyStatus.tsx/.scss, aiAccuracy.ts, DashboardContext.tsx, LIBRARY.md) is unchanged from the previous review pass and those findings still stand as previously reported (no new issues, one non-blocking cosmetic note on a doc-comment line break in store.ts).

Did not have shell access to re-run tsc --noEmit or lint-staged in this pass (relying on the PR description's stated clean run); flagging that gap in case tool access needs adjusting for future runs.

@manager
manager merged commit 190119f into dev Sep 11, 2026
2 checks passed
@manager
manager deleted the feat/library-batch-2026-09-11 branch September 11, 2026 08:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant