# SPEC — Fix the grade-board sort (display ordering only) **Status:** built 2026-07-29. Report-first found three premise breaks — see §2. **Scope:** display ORDERING only. No grade, ledger row, lock_line, scoring, model, or edge_pct scale/display change. Push scoring untouched. ## 1. The two defects (wrong at ANY scale, independent of edge_pct's retirement) `web/src/lib/slateAdapter.js selectTopGrades` ranked on `edge: Math.abs(numOr(g.edge, -Infinity))`: 1. **abs() on an already-signed value.** `edge` is signed BY DIRECTION upstream (`edgePctFor`: `over ? proj−line : line−proj`), so **positive = the model AGREES with the graded side**. Taking `|edge|` ranked the model's strongest DISAGREEMENTS equal to its strongest agreements. 177 public ledger rows carry a negative edge (58 B / 118 C / 1 F, worst −86.7). 2. **`Math.abs(-Infinity) === Infinity`** → a row with NO edge sorted **FIRST**. Absent data presented as the top pick — the `Number(null)` fabrication class. `src/services/intelligence/analyzeViaEngine1.js:506` sorted the Desk alt-line ladder by `edge_pct` desc — the same price-free 0.5-line artifact. ## 2. REPORT-FIRST — three premise breaks found before building - **B1. Site 1's board is fed by a 404.** `/api/props/top-graded` **does not exist** in `src/` (only three axios *callers* reference it) and returns **404 in prod**. The Next proxy catches → `{props: []}` → `topGrades = []` → `selectTopGrades([])` → the dashboard renders `proofMode` (yesterday's receipts) or honest empty copy. **The edge sort orders nothing on that surface today.** CORRECTION to the prior order: its "97.3% of rows tie → the edge key decides the board" was a LEDGER population measurement extrapolated to this board; the board has no rows to order. The fix is still correct-in-itself and lands the moment the feed is restored. - **B2. p_win CANNOT be the client-side sort key.** `src/utils/snapshotGating.js` (Session 67) strips `p_win`/`ev_pct`/`model_odds`/`value`/`takeable` for unentitled tiers, with the explicit rationale *"shipping p_win is shipping the price in a different base."* `selectTopGrades` runs in the BROWSER. Ranking there by p_win for all tiers would REVERSE that gate. Implemented instead: **p_win is used WHEN THE ROW CARRIES IT** (entitled tiers / server callers), takeable-gated identically to the hero; unentitled tiers fall through to the signed edge uniformly. Scales are NEVER mixed in one comparator. - **B3. The ladder cannot take the takeable gate, and p_win order there is ANALYTICALLY the line order.** Ladder rungs carry **no per-rung price** (books price each line differently; we do not fetch them), and `isTakeable` is a property of price alone → inapplicable per rung. And `P(stat ≥ k)` is monotone non-increasing in `k`, so **p_win-desc ≡ line-asc for an over, line-desc for an under**. Implemented as the direction-aware line order = exactly p_win-desc, at zero added compute. (`deskShowcaseService.rungsOf:40` already re-sorted rungs by line, discarding the edge order — so only `GradeResultCard` ever showed it.) ## 3. What ships - `selectTopGrades`: `grade → confidence → takeable-gated p_win (nulls last) → SIGNED edge (nulls last) → input order`. No `abs()`. Missing signal ranks LAST, rows are never dropped. - Alt-line ladder: ordered by highest p_win first, computed via the monotonic line equivalence (direction-aware). No `edge_pct` in the sort. - The takeable band is `web/src/lib/valueState.isTakeable` (−160..+200), which mirrors `src/config/valueEngine.isTakeable` — the hero's exact definition. ## 4. Acceptance criteria 1. A disagreement (negative-edge) prop does NOT outrank an agreement at equal grade+confidence. 2. A missing-signal row sorts LAST and is still PRESENT. 3. A takeable p_win row outranks a higher-p_win UNTAKEABLE (chalk) row. 4. p_win and edge are never compared against each other. 5. Ladder rungs are ordered highest-p_win-first for both over and under. 6. No grade/ledger/lock_line/scoring write. Full suite green, web build exit 0. ## 5. Held (NOT this order) edge_pct rescale or display retirement (Order B) · building the missing `/api/props/top-graded` server selector · exposing p_win to unentitled tiers.