Comparing against the upstream skill repo overturned v1's premise. The
three methodology files are byte-identical across both repos, and the
production v5 scorer imports the same upstream event engine the v4 ranker
does, so swapping engines is off the table and v1's three-way bake-off is
dropped.
What productization actually dropped is the stopping mechanism: upstream
terminates on a fixed 8-question bank and a one-shot adjudication, while
the production decision path discriminates as long as a probe exists. The
round budget is computed but never read by that path, and the "exhausted"
branch it needs already exists. Tasks now wire that budget in, restore the
upstream next-step summary, and keep the report readable from turn zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
Audit found three unrelated rectification scoring paths: the local-agent
methodology references, the production v5 server scorer, and the v4 fact
ranker that only the eval scripts import. The sealed holdout numbers
measure the third one, so neither the production path nor the local path
has ever been evaluated.
Task 0 is therefore a blind three-way bake-off on one case set, gating
every later task. The remaining tasks cover a single convergence function
with property tests, moving question authority fully to the server, making
the deliverable exist from turn zero, and the calibration data shortage.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
Records the billing gaps found by reading the live code paths: report.full
has no authorizeUsage call at all, rectification charges model.creditCost
once per case, and pricing is bound to the model rather than the feature.
Seven tasks, with task 0 as a measurement gate so no price lands in the
database from an estimate. Task 6 adds an admin pricing simulator next to
the model config page so margins stay computable against live data.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
.personal-report-reader declared --report-paper, --report-rule and
--report-accent on itself. A custom property resolves from the nearest
element that declares it, so an element-local declaration beats :root
regardless of media queries or source order — the dark override added
with the theme was dead the moment it was written.
The inks did follow the theme, because they live on :root. So dark mode
put --color-ink (#f2f0ea) on paper that stayed #f8f5ee: 1.05:1 for
headings and 1.22:1 for body. The report was effectively invisible.
Printing had the same defect from the other side: the print block forces
white backgrounds but never reset the inks, so printing from a dark
screen produced a blank page. It now pins the palette back to light —
paper is always paper.
Every report ink now clears AA on report paper in both themes, and the
new contract test forbids re-declaring the report palette anywhere but
:root, which is the trap that caused this.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
When coverage still blocks adopt and no renderable discriminator remains, narrate the numeric range and persist a spoken collect instead of promising a time card that cannot be clicked. Recompute probe information gain on active candidates and drop zero-split probes explicitly.
Co-authored-by: Cursor <cursoragent@cursor.com>
Choice path already wrote spokenFollowupForUser into the body; free-text
dropped that stem and only filled empty answers, so a new collect_spoken
focus stayed invisible after “记下了”.
Co-authored-by: Cursor <cursoragent@cursor.com>
Distinguish-stage reverse-verify questions were exhausting after 1–3
dated probes. Union boundary windows across representative pairs, keep
multiple years per domain, raise the public cap, and allow activation
fallback without relaxing MIN_BOUNDARY_DAYS or scoring.
Co-authored-by: Cursor <cursoragent@cursor.com>
Dark muted surfaces missed WCAG AA; action and tertiary tokens plus a 32-pair contract close that. Hit targets, remaining-count, and Enter-to-send follow the interaction audit without changing visual sizes.
Co-authored-by: Cursor <cursoragent@cursor.com>
Only sign-bound varga_style questions may omit a concrete period. Remaining-layer existence and quality probes now drop as yearless_ungrounded_contrast instead of scoring by group order.
Co-authored-by: Cursor <cursoragent@cursor.com>
Choice cards were showing type-table tokens as options, dating long-term style questions, and stacking the stem on the first option because legend is outside the fieldset grid.
Co-authored-by: Cursor <cursoragent@cursor.com>
The standalone collect prompt bar reused the choice-card chrome and
duplicated the question. Visibility now comes from the spoken reply,
with an empty-body fallback that posts the persisted focus prompt.
Co-authored-by: Cursor <cursoragent@cursor.com>
Occupation answers were stored as career, so coverage never closed, yearless
cards never fired, and the decision layer suppressed an engine-allowed range.
Normalize occupation-collect writes, allow yearless cards once training is
open, offer a range when no distinguish card remains, and align the public
house table to the representative minute.
Co-authored-by: Cursor <cursoragent@cursor.com>
Spoken collect prompts lived only in GET current_question. The chat never
parsed that field, Agent projections returned null after evidence writes,
and active_focus followups collapsed the questionId. Render the parsed
prompt, keep choiceReady on real cards, and give collect focuses a stable
domain-scoped id.
Co-authored-by: Cursor <cursoragent@cursor.com>
The choice and collect fast paths applied the answer then returned,
so a dated event in the same utterance never reached the evidence
ledger. Idle persist also prechecked follow-up with a hardcoded
collect_evidence outcome instead of the dossier decision.
Co-authored-by: Cursor <cursoragent@cursor.com>
A spoken no neither scored nor declined coverage, so relatives never closed and free-text turns left current_question null. Prefer a same-domain yearless scoring card before an unscoreable collect, resolve explicit collect denials as declined, and persist the next followup after an idle agent turn.
Co-authored-by: Cursor <cursoragent@cursor.com>
Appearance is a radio group inside the avatar menu rather than a loose
button, so arrow keys reach it and the current choice is announced.
Picking one keeps the menu open, so the change is visible where it was
made.
"跟随系统" removes data-theme instead of writing a third value — the
media query has nothing to match otherwise. A synchronous script at the
top of <head> re-applies a pinned choice before the first paint; going
through next/script with any strategy would defer it and bring the flash
straight back, so the test asserts a plain script tag. Blocked storage
degrades to following the OS instead of throwing.
The stored value is browser state, so it is read through
useSyncExternalStore rather than synced into React state in an effect,
which also trips the cascading-render lint rule. localStorage only fires
`storage` in other tabs, so a same-tab write notifies its own listeners
and every open tab stays in step.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
The palette is restated for dark rather than inverted: elevation reads
through lightness on dark and through darkness on light, so the floor is
the darkest surface here and the second-lightest there. The clay hue is
kept and lifted, because #85432f is 2.1:1 on a dark ground. All 37
themeable tokens are covered, in an OS-preference block and a data-theme
block that a contract test keeps identical, and ink, action, danger,
success and warning are asserted at 4.5:1 against the dark canvas.
Four raw colors that would have stayed light-theme values are tokenised
(the avatar hairline, the sheen sweep, a one-off shadow, a literal
warning hex). The QR keeps literal white in both themes, since scanners
need light modules to be light, and print keeps white paper.
The four root boundary pages cannot read a token, so they restate the
handful they need in both themes. forbidden.tsx also stops painting a
bespoke near-black page in four colours that appear nowhere in the
palette, which broke the rule that dark ink is never a page-scale
surface.
Also fixes what the audit found in DESIGN.md itself: two ink values that
had drifted from the code, a motion tier documented at 360ms that was
never implemented, a breakpoint section claiming three tiers where the
stylesheet has eleven, an undocumented report-paper palette, and an admin
section describing a bespoke panel that antd + Refine replaced. Five
zero-reference admin rules go with it.
The sidebar gets the accent, opaque drawer, heading rank and empty-state
guidance settled earlier, and fenced code blocks finally get a container.
BUG-439.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
The nav buttons defaulted to centered for the desktop icon rail and were
flipped back by `[data-state="expanded"]`. That attribute tracks the
desktop open state, while the mobile drawer runs off `openMobile`, so on
a phone the labels rendered but the override never matched: 新建对话 and
我的报告 floated mid-drawer while every other row sat flush left.
Left alignment is the base layer now, correct for both the drawer and the
expanded desktop sidebar, and centering moved into the >=768px collapsed
block beside the rest of the rail rules. That was the only top-level
`[data-state=...]` selector left in the stylesheet.
personal-report-entry asserted `justify-content: center` on the report
button, which is the defect itself; the value is updated and the rest of
that assertion stands.
BUG-438.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
Scoring scoped the contrast packet to the previous round's candidate set while
minting probes for the new one, so every later read re-prefixed the stored hash
and the persisted focus schema could never match. Mint one prefix per set and
compare splits by probe identity.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVapmh2oGNyr6ECHKjPJY8
The onboarding paywall wrote `dialog-header`, a class no stylesheet
defines, so its close button stacked under the title instead of sitting
beside it. The session-delete confirm buttons had the same problem, and a
`message-entry` wrapper added in August had quietly killed the
`.message + .message` spacing rule in the main chat.
Fixes all three against the patterns already in the codebase, then adds a
contract test so the next undefined class fails instead of shipping: it
scopes itself to our own class families so Tailwind utilities stay out,
strips CSS comments before deciding what counts as defined, and carries
an allowlist of the fifteen deliberate no-op modifiers that must shrink
rather than grow.
Also collapses the paywall's duplicate display heading into the intro
sentence, leaving one heading in the dialog.
BUG-434, BUG-435, BUG-436.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
The post-choice decision used a looser plan than the followup builder.
Load compute once in persistApplied and feed the same birthDate to both.
Co-authored-by: Cursor <cursoragent@cursor.com>