Nothing changed: no mount, no row edit, no data threading. Docs only. 1. THE RATIONALE DOES NOT REACH THE ROW. StripProp carries stat/line/side/grade/ gradedAt/delta/awaiting/outcome/movement/revisedFrom/book/bestBook/dead/ history — no reasoning, no kill_conditions_triggered — and buildPlayerStripsFromProps never threads them. Mounting the hover needs a new field on the strip contract threaded through the slate adapter: additive, but a data-path change rather than a mount. 2. THE 0.3 PREMISE INVERTS — THE RATIONALE IS ALREADY PUBLIC. Verified live and anonymously against prod: /api/snapshot/wnba returns reasoning.summary with no locked flag plus kill_conditions_triggered. stripModelPrice removes model_odds/p_win/ev_pct/value/takeable but NOT reasoning. So the full model rationale already ships to every anonymous browser on the main board, while the same content IS tier-gated on the scan path (tierGating.js). Mounting the hover would leak nothing new, but would surface content that is currently shipped-but-unrendered, and the product gates it in one place while serving it openly in another. That is a monetization/consistency decision, so it is reported with three options rather than resolved unilaterally. 3. ROW-GRAMMAR IS LAW AND LOCKS StatStrip's SOURCE ORDER. rowGrammar.test.js asserts element order via src.indexOf on the component source; adding a rationale affordance or a team chip moves those offsets, so specs/ROW-GRAMMAR.md and the test must be amended in the same commit. That makes this spec-amending work needing its own slot decisions, not an additive mount. Safely mountable with no blockers: reveal.js (wraps the row list, no StatStrip internals, no new data, no grammar slot). teamChips needs a grammar slot; rowRationale needs the data threading AND the gating decision AND a slot. Recommends splitting D1-close into: mount reveal now; a ROW-GRAMMAR amendment order for the chip + rationale slots; then the rationale mount once the gating decision is made. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
4.1 KiB
D1 CLOSE — REVIEW ZERO FINDINGS (report; mount NOT performed)
2026-07-31. Nothing changed: no mount, no row edit, no data threading.
git diff = docs only.
Why this stopped at Review Zero
The order scopes this as "mount only — NO row refactor." Reading the real components shows the mount is not a mount, and one finding needs a decision before anything is surfaced.
0.1/0.2 — THE RATIONALE DOES NOT REACH THE ROW (VERIFIED)
StripProp (components/vyndr/StatStrip.tsx) carries stat · line · side · grade · gradedAt ·
delta · awaiting · outcome · movement · revisedFrom · book · bestBook · dead · history —
no reasoning, no kill_conditions_triggered. buildPlayerStripsFromProps never threads
them either. So mounting the hover requires adding a field to the strip contract and threading
it through the slate adapter — additive, but a data-path change, not a mount.
0.3 — THE PREMISE INVERTS: THE RATIONALE IS ALREADY PUBLIC (VERIFIED LIVE)
The order asks me to ensure "a free-tier user's hover shows nothing, not the paid reasoning." Measured against prod, anonymously:
GET https://api.vyndr.app/api/snapshot/wnba (no auth)
reasoning.summary present : True
reasoning.locked : None
kill_conditions_triggered : 1
stripModelPrice strips model_odds/p_win/ev_pct/value/takeable — it does NOT strip
reasoning or kill_conditions_triggered. So the full model rationale is already shipped to
every anonymous browser on the main board endpoint.
Two consequences, and they pull in opposite directions:
- Mounting the hover leaks nothing new — it renders bytes the browser already has.
- But it surfaces something currently shipped-but-unrendered, and the same content IS
tier-gated on the scan path (
utils/tierGating.jsredactsreasoningfor free tier). So the product currently gates the reasoning in one place and serves it openly in another.
This is a monetization/consistency decision, not an implementation detail — which is why it is reported rather than resolved unilaterally. Three coherent options:
- (a) Gate
reasoningon/api/snapshotthe way scan does, then mount the hover for entitled viewers only. Consistent, but removes content free users already receive. - (b) Accept it as intentionally free (board reasoning is the funnel) and mount for everyone. Consistent the other way; makes the scan-path gating the odd one out.
- (c) Leave the payload alone and don't surface it. Status quo.
THE THIRD BLOCKER — ROW-GRAMMAR IS LAW AND LOCKS StatStrip's SOURCE ORDER (VERIFIED)
tests/unit/rowGrammar.test.js asserts StatStrip's element order by src.indexOf(...) on the
component source (slots: identity → viability → stat+line → market context → model output →
outcome → actions → provenance). Adding a rationale affordance or a team chip moves those
offsets, so specs/ROW-GRAMMAR.md and the test must be amended in the same commit — per
CLAUDE.md, that spec is LAW. That makes this a spec-amending change, not an additive mount, and
the amendment needs its own slot decision (where does a rationale affordance sit in the grammar?
where does a team chip sit relative to identity?).
WHAT IS SAFELY MOUNTABLE WITHOUT ANY OF THE ABOVE
reveal.js— wraps the row LIST, touches no StatStrip internals, no new data, no grammar slot. This one is genuinely a mount.teamChips.js— needs a grammar slot (it sits before the team abbr, inside identity), so it is small but spec-touching.rowRationale.js— needs the data threading AND the gating decision AND a grammar slot.
RECOMMENDATION
Split D1-close into: (1) mount reveal now (no blockers), (2) a ROW-GRAMMAR amendment
order that adds the team-chip slot + the rationale-affordance slot to the spec and test, and
(3) the rationale mount, after the gating decision above is made.
TAGS
VERIFIED: strip contract lacks reasoning; anon snapshot serves reasoning + kills; ROW-GRAMMAR locks source order. CANNOT DETERMINE: none. BLOCKED: the rationale mount — on a gating decision (a/b/c) and a ROW-GRAMMAR amendment.