Operational usability + visualization improvements for /admin/board (web/src/pages/admin/AdminBoardPage.tsx, 3,172 lines).
Read-only analysis; every idea below is grounded in the actual code and notes whether it reuses data the board already fetches or needs new API work.
?focus= preserved), tight edits happen from the card via the Actions dropdown without losing board context, and compact cards must never make an edit feel farther away than it is today.Rollout consequence: all three phases are greenlit; the Actions dropdown moves UP into the Phase 1/compact-card work rather than waiting for Phase 2. Ops queue AFTER op-admin-edit-flow-2026-07-20 + op-admin-notes-panel-2026-07-20 (same files).
The board is a 3-column pipeline (Inquiry → Event → Closeout, ?archived=1 adds Archive) that classifies parties + inquiries by classifyToColumn (web/src/lib/lifecycles.ts), fetched in full via queryPipelineEntities (web/src/api/admin.ts:1181 — /admin/query/party + /admin/query/inquiry, limit 200, complete entities incl. tags, details, connectors, _caps). It does a LOT well: drag-and-drop transitions with optimistic update + revert, per-card quick actions (+Prize/+Sponsor/Beneficiary/Image/Preview), herodraw Run Draw + Winners/Top-raisers steppers persisted on details.settings, completeness checklist dots, closeout checklists backed by note-tasks, QR codes, ?focus=<id> gold-pulse deep links with copy-link buttons, and sidebar Tasks/Lessons-Learned views. The pain points visible in the code: money is invisible — party.tags.total_raised is already in memory on every card but never rendered; cards are enormous (QR at 88px, 5+ ghost buttons, steppers, email, UUID-prefix owner, checklist — so 4 live events + inquiries means heavy scrolling and no at-a-glance scan); no summary strip (you can't see total live $, closing-soon, or draw-pending without opening things); no dates/countdown despite tags.end_time / details.settings.draw_date being fetched; no category theming despite $accentHeropicks/$accentHerodraw/$accentHerofund tokens existing (web/src/tamagui.config.ts:264-266); duplicate cards for the same event (converted inquiry + active party both land in Event column — the ?focus comment at line ~1824 admits this confuses even the team); plus several dead/rough affordances (pipeline-mode checkboxes select things but the bulk bar only renders when !pipelineMode at line ~2922; Reassign asks for a raw user UUID; owner renders as assigned_to.slice(0,8)…; the Image checklist reads details.banner_url top-level while real branding lives at details.branding.banner_url).
Legend: S ≈ hours (one dev-mission slice), M ≈ a day-ish, L ≈ multi-day / needs API design.
"Cheap" = uses data queryPipelineEntities already returns; "API" = needs new/changed backend.
$ raised big and bold (Cinzel, Victory Gold or category accent) on every party card, and add a summary strip above the columns: Total live $ (sum of tags.total_raised for status='active'), Active events, Pending inquiries (inquiry-column count), Closing soon (count with end/draw date within 7d), Draws pending (herodraw + past close + no draw run).party.tags.total_raised is an atomically-maintained counter (sdk/api/parties.js:406-426, incremented on every donation) that the board fetches and silently drops. Zero extra requests.BoardCard (AdminBoardPage.tsx ~line 819) + a new BoardSummaryStrip component in web/src/components/admin/. ⚠️ total_raised is cents (donations tag_amount is cents; there's a known cents-display bug precedent at PartyDetailPage.tsx:2478) — format with /100 and a shared formatCurrency (copy from AdminEventMetricsPage.tsx:28).closeIso (draw_date ‖ details.end_date ‖ tags.end_date, line ~705) and isDone — but only for herodraw and only to gate Top Raisers. Generalize to all parties (tags.end_time is on every party per AdminParty interface, admin.ts:222) and render it. Feeds the "Closing soon" summary tile and attention badges (#3).⚠ Draw pending (herodraw + isDone + no completed draw), ⏰ Closing soon, 🐌 Stalled Nd (staleDays already computed, line ~692, currently buried as gray microtext), 🖼 No image, 💳 No processor, 🎯 No challenge (see note). Sort cards within each column: attention-flagged first, then by $ raised desc, replacing pure -created_at.BoardCard / a deriveAttention(entity) helper next to classifyToColumn in lifecycles.ts; sort in entitiesByColumn memo (line ~1909). "No challenge" needs challenge presence — one batched /admin/query/challenge?limit=200 at board load, group client-side by connectors.party (memory rule: "every fundraising Party needs ≥1 Challenge or donate/predict UI is dead" — this badge catches exactly that failure). "Draw pending" can start as isDone && no drawWinners rendered; exact truth needs a draw-record lookup (Phase 3).det.banner_url/hero_url/logo_url top-level (line 1003) but branding is persisted at details.branding.banner_url (see PartyDetailPage.tsx:1086, InlineBrandingEditor → /api/parties/:id/branding/:asset). Check both paths — today the dot is likely a false "missing" for parties.HeroDraw etc.) on each party card, and tint the summary tiles the same way.$accentHeropicks #0F6A6F, $accentHerodraw #F2B705, $accentHerofund #0A4F53 — tamagui.config.ts:163-168) and the board already imports classifyFundraiser (fundraiser-taxonomy.ts) for the herodraw gate.classifyFundraiser(entity) in BoardCard; labels/taglines come from TAXONOMY (single source of truth per its header comment). Caveat: without round data the classifier can't split heropicks from herofund (hasPredictions defaults false) — ship herodraw-vs-rest first; correct heropicks with a batched /admin/query/round?limit=200 (or challenge-based proxy) in Phase 2.isExpanded / onToggleExpand plumbing already exists for task expansion, lines 1277-1321) or lives on the full edit page (the in-flight op makes party stickies open full-edit directly, so per-card button farms matter less now).BoardCard into CompactBody + ExpandedBody; persist preference in localStorage + ?dense=1 URL param (pattern: existing archived/test params). No API.connectors.converted_party points at a party that's also on the board, hide the inquiry card and show a small "from inquiry ↗" chip on the party card (linking to /admin/board/inquiry/:id).entitiesByColumn memo: build a set of party ids, filter inquiries whose connectors.converted_party is present (the QR block already reads that connector, line ~972). Keep a "show origin inquiries" escape hatch under the existing toggle row.StatusBoardPage.tsx (sortable AdminTable over manageQuery) and AdminTable/AdminTableRow components — this is assembly, not invention.?view=table alongside existing view values; reuse AdminTable (web/src/components/admin/AdminTable.tsx), share the derive helpers from #1-#3. Also the natural home for CSV export later.assigneeFilter/priorityFilter params are parsed but only applied in sidebar mode (the filtered memo returns [] in pipelineMode, line 1858). And the selection checkboxes on every pipeline card do nothing — the bulk bar requires !pipelineMode (line 2922). Dead affordances erode trust in the tool.filteredPipelineEntities memo; for the bulk bar reuse getPipelineTransitions intersection across selected entities.assigned_to.slice(0,8)… (line 690). Show created_by too (standing feedback: "Board cards need created_by").fetchAdminSalesUsers already exists (admin.ts:506); fetch once at board load, build id → {name, email} map, feed both the modal (as HeroSelect) and an AvatarCell-style chip (web/src/components/admin/AvatarCell.tsx exists). Also fixes the odd double-call in handleConfirmReassign (it currently calls updateEntityStatus pointlessly before the real PUT, line ~2241).expandedColumns initialized empty, line 1596) and show the #1 summary strip (tiles wrap 2-up).useState(new Set(['event'])) change plus the strip = usable phone dashboard.details.compliance_checked_at flag set from the edit page or a card action; warn (non-blocking) on drag/Move to active when absent. Later: an API endpoint running the same checks as scripts/compliance-check.mjs so the badge is automatic.status='active', pipelineColumnToStatus line 468).details field (no schema change — D1 column cap respected). Automated phase = new compute route reusing the script's logic.document.visibilityState).fetchEntities; setInterval + visibility listener. Note fetchEntities fans out closeout-task queries per completed party (line 1741) — keep the poll to pipeline queries only./ focuses search; Esc clears; n opens the search field pre-scoped. Card hover shows the focus-link icon persistently (it's already tiny at 13px).useEffect keydown listener on the page; skip when a modal/input is focused.GET /api/admin/parties/:id/metrics exists (AdminEventMetricsPage/mdt-01) but is per-party; N calls per board load is too many. New /admin/board/metrics-summary (window rollups per active party, reusing PartyMetricsWindowRollups from web/src/api/analytics.ts). L, API.goal_amount exists on teams (parties.ts:263-265), not parties — needs a party-level goal decision (a details.goal_amount field) before it can render. M, needs product decision.ActivityFeed component exists (AdminDashboard uses it) — a collapsible right rail on the board answering "what changed since yesterday". M.fetchDrawResults-backed check + link to the verified record (e.g. Merrill 864c4bdb). M, cheap-ish (endpoint exists, admin.ts:443).Scope for op-planner: AdminBoardPage information density + summary layer; no new API endpoints; no entity-schema changes.
$ raised (cents→USD) prominent on party cards — #1.closeIso) — #2.classifyFundraiser + existing accent tokens (herodraw vs rest) — #4.details.branding.*) — #3 bundled fix./admin/board (desktop + 390px), assert strip totals equal sum of card amounts, badge presence on a seeded closing-soon/stale party, accent colors per category. (Board ops must follow the op-video standard.)Scope: interaction correctness + findability; ≤1 extra batched query per load; still no schema changes.
?dense=1, localStorage) — #5.fetchAdminSalesUsers map + AvatarCell — #9./ search focus — #13.Scope: new API surface; product decisions from (d) resolved first.
AdminTable + shared derive helpers, sortable by $, close date, attention — #7 (can be pulled into Phase 2 if it's hot)./admin/board/metrics-summary endpoint + 7-day velocity/sparkline on expanded cards + real draws-pending from draw records — #14.lifecycles.ts mirrors SDK — server is source of truth).tag_*/connector_* columns (D1 100-column cap); any new field (compliance flag, party goal) goes in details.BoardCard.details.goal_amount on parties (enables progress bars + "% to goal" in the summary strip)? Today goals only exist per-team.