Project the board card behind one board_view seam
app/dash.py:1025–1076 (api_board) · app/dash.py:2274–4293 (embedded JS) · app/flows.py (deal dict)
Before — implicit projection, leaky both sides
flowchart TB DD["deal dict
50+ internal fields"] AB["api_board
inline {…} literal"] JS["embedded JS board
2000 lines"] DD -. "raw .get() ×50" .-> AB AB -. "renames + 3 date
field fallbacks" .-> JS JS -. "callAt / sales_call_date
/ next_followup_date" .-> JS classDef leak fill:#fff,stroke:#dc2626,stroke-width:2px,color:#7f1d1d; classDef plain fill:#f8fafc,stroke:#94a3b8,color:#334155; class DD,JS plain class AB leak
The deal→card shape exists only as an inline dict literal; the JS re-derives "next action date" from three field names. Contract is unwritten — only a browser proves it.
After — one deep projection, one contract
three consumers · one interface
Problem
The deal→card projection is an inline literal in api_board; the JS re-derives the next-action date from three field names — the contract is testable only in a browser.
Solution
Move the projection into a board_view(deal) → BoardCard module that owns the renames, the single date rule, and the stage→column map; api_board, the JS, and tests all read that one shape.
Wins
- locality: card bugs concentrate in one module
- leverage: one interface, three consumers
- interface shrinks; api_board absorbs nothing new
- kills the 3-name "next action date" fallback
- the JS↔server contract becomes a named shape
- most board logic leaves the browser-only test path