Files
vyndr/specs/d1-close-review-zero.md
T
builtbykev 43281bb885 report: D1-close Review Zero — mount not performed, three findings
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
2026-07-31 06:25:05 -04:00

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:

  1. Mounting the hover leaks nothing new — it renders bytes the browser already has.
  2. But it surfaces something currently shipped-but-unrendered, and the same content IS tier-gated on the scan path (utils/tierGating.js redacts reasoning for 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 reasoning on /api/snapshot the 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.