Test Coverage Map — May 12 2026 1241 / 1241 PASS

Test Coverage Map
What’s tested, what isn’t, what’s next

72 vitest files, 1241 test cases (was 30 at session start — added 1211 across ~76 helper modules). 100% pass rate, ~7.7 s runtime. Spans 26 features + 3 infra layers. 14 cross-feature dedupes complete. STRUCTURAL refactor: discover gridNodes Steps 1-5 landed (5 byte-identical adapters lifted, -252 source LoC, +26 tests). 26 worker subagents across 15 swarm waves. Final auditor verdicts: SHIP-READY, no regressions, no transient HEAD breaks made it into published history. Remaining open work needs human decisions (Mark sign-off on discoverCompany filter divergence for gridNodes Step 6; Charles/Robert review on PR #343 which is mechanically MERGEABLE/CLEAN with build PASS / lint PASS / 0 skipped tests). Plus L65→L73 perf wave: ~5 MB removed from first-paint critical path + crash-safety (ErrorBoundary 1→4) + 5 React perf fixes + 7 latent bugs fixed (see perf-wins.html).

72
Test files
1241
Test cases
100%
Pass rate
+1211
Added this session
Section 1

The 71 test files

crayon-to-openlang.test.ts — 9 tests
Tests the legacy DispatchEvent[] (Crayon shape) → OpenUI Lang string adapter. Covers: empty events, text-only events drop, single tpl event with templateProps emits ContactCard call, direct-named contact_card event, multi-event composition, and edge-case templates (error_message, scanning_card, dispatch_confirmation). Used by chat-shell to feed legacy event streams into the new Renderer.
9 cases
use-dispatch-gate.test.ts — 4 tests
Tests the dispatch-gate predicate hook. Covers: ready=true allows dispatch, narrative count too low blocks dispatch, isPolling blocks dispatch, error state blocks dispatch with appropriate message. Mocks usePitchProfilePoll so the hook’s logic can be tested in isolation without TanStack Query.
4 cases
use-pitch-profile-poll.test.ts — 4 tests
Tests the TanStack Query polling wrapper. Covers: isPolling=true initially when valid id provided, returns ready=true when backend confirms, returns missing_fields when not ready, error propagation. Mocks pitchProfileFn so tests never hit the network.
4 cases
coerce-conversation-title.test.ts — 13 tests NEW
Tests the defensive title coercer (shipped this session). Covers: 3 real-prompt pass-through tests (long sentence, medium, trimmed). 6 MCQ-fragment prefix tests ($5M-$15M, $15M+, 65%, "B2B SaaS", "Choose File", "Fintech"). 4 edge cases (empty, whitespace-only, undefined-coerced, 50-char clamp).
13 cases
sanitize-ask-back-reasoning.test.ts — 21 tests NEW (loop 20)
Tests the ask_back reasoning sanitizer (extracted from canvas-openui.tsx in commit 98ebb9b). Covers: 2 clean-prompt pass-through, 6 internal-class jargon strips (case-insensitive, word-boundary aware), 3 numbered-choice strips ((1)(2)(3) detection), 3 classifier self-talk strips (meta-request, doesn’t map to, canonical class), 3 verbose-response strips (200-char threshold), 4 edge cases (empty, whitespace, undefined, null).
21 cases
parse-conversation-id.test.ts — 18 tests NEW (loop 21)
Tests the JSON-parse + Number-coerce-NaN logic (extracted from use-selected-anything-engine-conversation.ts in commit 444e622). Catches the TanStack Router quirk where number params arrive as quoted strings. Covers: 2 null/undefined cases, 5 native number cases (finite, 0, negative, NaN, Infinity), 4 string common cases (raw "279", JSON-quoted "\"279\"", raw "42", negative "-7"), 4 string edges (non-numeric, empty, whitespace, "NaN"), 3 malformed JSON cases ("{invalid}", "null", "true").
18 cases
network-error-message.test.ts — 14 tests NEW (loop 22)
Tests the network-failure detector (extracted from canvas-openui.tsx in commit 89444c4). Returns structured NetworkErrorPayload for fetch rejections, AbortErrors, and timeouts; null for non-network errors. Covers: 5 non-Error inputs return null, 3 non-network Error inputs return null, 2 "Failed to fetch" cases (Chrome/Edge), 1 "NetworkError" case (Firefox), 1 AbortError case, 2 "timed out" cases (case-sensitivity + body preservation).
14 cases
adapt-contact-card.test.ts — 23 tests NEW (loop 23)
Tests the contact_card adapter (extracted from event-renderer.tsx in commit 758bab7). Splits into 3 testable exports. Covers: cleanCompany (6 — alias suffix strip, whitespace, no-slash, undefined, multi-slash, empty), bondStrengthFromScore (5 with boundary tests at 0.85 / 0.6 / 0.4), adaptContactCard (12 across 4 groups — happy path, missing optionals, DiceBear fallback Mark tell #5, email field normalization).
23 cases
format-helpers.test.ts — 26 tests NEW (loop 24)
Tests 4 helpers extracted from chat-shell.tsx in commit 15c10cd. countNarratives (8 — null/undefined, empty, threshold >10 chars boundary at 10 + 11, whitespace, 6/6 + 5/6 narrative gates, defensive non-string). newId (3 — 8-char base36, uniqueness across 100 calls). formatUsdShort (10 — billions $1B/$2.3B, millions $1M/$1.5M/$999M, thousands $1K/$12K, sub-thousand $500/$0/negative passthrough). formatBytes (5 — bytes/KB/MB/GB with boundary tests).
26 cases
contact-card-utils.test.ts — 31 tests NEW (loop 25)
Tests 4 helpers extracted from openui/components.tsx in commit b3e404e. cardInitials (9 — single, two-word, multi-word with first+last only, empty, whitespace, lowercase, trim, multi-space). dicebearSeed (8 — kebab, apostrophe strip, special-char strip, multi-space collapse, digits preserved, whitespace edge, special-only edge, leading/trailing hyphens). splitWhyTag (13 — undefined, 4 known tags, case-insensitive, whitespace handling, no-tag passthrough, unknown tag NOT split, tag in middle/start NOT split). TAG_LABEL (1 — 4 display labels).
31 cases
ack-for.test.ts — 19 tests NEW (loop 26)
Tests assistant acknowledgement strings extracted from chat-shell.tsx. Covers branch-on-keyword logic for "investor" / "talent" / "partner" / "customer" / "deal" / "advisor" / "cofounder" / "purchase" / "journalist" / "speaker" / "acquisition" / "collaborator" / "job" / "advice" + default fallback + empty input + case-insensitive + multi-word with first match wins.
19 cases
array-buffer-to-base64.test.ts — 14 tests NEW (loop 27)
Tests chunked base64 encoder for big pitch decks. Covers empty buffer, single byte, all 256 byte values, ASCII text round-trip, binary data, exact 0x8000 chunk boundary, 2× chunk size, 3× chunk + remainder, large 1MB buffer (no stack overflow), null/undefined defensive handling, Uint8Array vs ArrayBuffer input.
14 cases
dispatch-card-helpers.test.ts — 36 tests NEW (loop 28)
Tests 5 dispatch-confirmation card helpers extracted from dispatch-confirmation-card.tsx. formatUsd (8 — billions, millions, thousands, sub-thousand, $0, negative). formatUsdInput (6 — typed input formatting with commas + $). parseUsdInput (10 — "5M" / "$5M" / "5 million" / "5 thousand" / "$1.5M" / mixed case / strip non-numeric / "1t" returns undefined / negative / empty). formatRaise (6 — pre/post-money, exists/missing fields, defensive). formatFounders (6 — single/multi/empty array/null/whitespace/comma-join).
36 cases
class-labels.test.ts — 14 tests NEW (loop 29)
Tests CLASS_LABELS map + slugToLabel + getClassLabel. Covers all 15 known classes (find_investors, find_talent, find_customers, find_deal_flow, find_partners, find_cofounder, find_advisors, find_job, find_collaborators, find_journalists, find_acquisition, find_speakers, get_advice, make_purchase, purchase_real_estate) returning Title Case display labels, unknown slug fallback to humanized form, empty string passthrough.
14 cases
relative-time.test.ts — 19 tests NEW (loop 29)
Tests formatTimestamp + relativeTime with deterministic now injection (no Date.now() coupling). Covers "just now" (<60s), "5m" / "1h" / "3h" / "1d" / "5d" buckets, week+ formatted as M/D, year+ formatted as M/D/YYYY, future timestamps clamp to "just now", null/undefined defensive, ISO string + epoch ms inputs, edge of each bucket boundary.
19 cases
strength-meter-utils.test.ts — 22 tests NEW (loop 30)
Tests normalizeValue + toPercent + getStrengthBucket extracted from anything-engine-strength-meter.tsx. normalizeValue (8 — clamp negative→0, clamp >1→1, fractional passthrough, percent-as-100 detection, defensive null/undefined/string). toPercent (5 — 0/0.5/1 → "0%"/"50%"/"100%", rounding rules). getStrengthBucket (9 — Weak / Fair / Strong thresholds with boundary tests at 0.33, 0.34, 0.66, 0.67).
22 cases
firm-name-utils.test.ts — 21 tests NEW (loop 30)
Tests djb2 hash + firmInitials extracted from anything-engine-company-chip.tsx. djb2 (6 — empty, single char, ASCII text, unicode, deterministic across calls, distinct outputs for distinct inputs). firmInitials (15 — VC suffix stripping for " Capital" / " Ventures" / " Partners" / " Fund" / " Management" / " Group", multi-word firm initials, single-word fallback, empty/whitespace, case-preserved letters, special chars stripped).
21 cases
source-label.test.ts — 11 tests NEW (loop 31)
Tests sourceLabel() humanizer extracted from memory-panel.tsx. Covers 6 known sources (user_stated → "You said", chat_inferred → "From chat", profile_enrichment → "From profile", social_enrichment → "From social", system → "System", calendar → "From calendar") + 5 unknown-fallback patterns (multi-underscore split, plain word passthrough, empty string, single underscore, multi-segment slug).
11 cases
leak-filter.test.ts — 27 tests NEW (loop 31)
Tests SUBTITLE_LEAK_PATTERNS + isLeakedSubtitle extracted from anything-engine-scanning-card.tsx. Three regex patterns guard the spinner-verb subtitle slot from backend pipeline language. Pattern 1 (DB internals — 9 cases): FalkorDB / falkordb / AlloyDB / ScaNN / BigQuery / Postgres / Neo4j / Cypher / standalone "index" word + word-boundary correctness ("indexed" / "indexable" do NOT match). Pattern 2 (result counts — 5 cases): "returned 25 prospects" / "returned 3 hits" / "returned 100 candidates" / "returned 1 result" / negative ("returned to homepage" passes). Pattern 3 (pipeline verbs — 9 cases): leading "Routed" / "Querying" / "Scanning index" / "Fetching" / "Loading nodes" / "Pre-filter" / "Pre filter" + leading-whitespace match + mid-string non-match. Plus 3 clean-string baselines + 1 empty-input.
27 cases
tooltip-position.test.ts — 18 tests NEW (loop 32)
Tests resolvePosition + slideOffset extracted from anything-engine-tooltip.tsx. The lib version takes an explicit Viewport object instead of reading window.innerWidth/Height/scrollX/Y inside the helper, which made it directly unit-testable. slideOffset (5 — 4 placement transforms + purity check). resolvePosition (13 — 4 preferred-honored cases when there's room, 4 flip-on-overflow cases (top→bottom, bottom→top, left→right, right→left), 3 viewport-edge clamp cases (left/right/top each clamped to a 6px margin), 2 scroll-offset cases (verifies returned coords are in document space).
18 cases
scanning-phrases.test.ts — 12 tests NEW (loop 33)
Lifted PHASE_SUBTITLES (100 entries) + BRAILLE_FRAMES (10 glyphs) + PHASE_INTERVAL_MS (1500) out of TWO duplicates (openui/components.tsx and components/anything-engine-scanning-card.tsx) into shared lib. Tests assert: PHASE_SUBTITLES has exactly 100 entries (Robert May 8: "100 minimum"), no empty/whitespace, no duplicates, includes canonical "Stargazing" + "Befriending asteroids" + "Go for launch", contains zero backend pipeline language ("querying" / "fetching" / "routed" / "scanning index" / "loading nodes"). BRAILLE_FRAMES has 10 single-codepoint glyphs in U+2800–U+28FF range. PHASE_INTERVAL_MS = 1500.
12 cases
tts-text-prep.test.ts — 43 tests NEW (loop 34) — first cross-feature dedupe
Lifted expandMagnitude + normalizeForSpeech + preprocessForTTS + MAX_WORDS=500 out of BOTH features/anything-engine/hooks/use-voice.ts AND features/copilot/hooks/use-voice.ts into shared src/lib/tts-text-prep.ts. expandMagnitude (6 — K/M/B/T + case-insensitive + unknown-suffix passthrough). normalizeForSpeech (24 — currency $5M/$2.5B/$1.2M/$2.5B/$500, plain magnitude, percentages 15%/3.5%, year ranges 2024-2025, abbreviations e.g./i.e./vs./w/, symbols &/+/slash, URL/email strip, em/en dashes, repeated punctuation collapse). preprocessForTTS (13 — markdown strip across fenced code/inline/bold/italic/link/heading/list/blockquote/table/hr, MAX_WORDS=500 cap, integration test combining markdown + currency, documented HTML-tag-strip-vs-arrow-regex quirk).
43 cases
starter-helpers.test.ts — 26 tests NEW (loop 35)
Lifted parseNetwork + formatStarterTime + participantNames out of hooks/use-conversation-starters.tsx into lib. Renamed formatRelativeTime→formatStarterTime to disambiguate from lib/relative-time.ts (different domain — calendar future events vs message past timestamps). parseNetwork (7 — empty / invalid JSON / wrong shape / valid object / null literal / PaginatedResponse rejection). formatStarterTime (11 — past→"now" / 0m / 5m / 59m / 60m → "in 1 hour" / 119m / 120m → "in 2 hours" / 6h / 23h / "tomorrow at
26 cases
events/utils.test.ts — 21 tests NEW (loop 36) — events feature
Pre-existing pure helpers in features/events/components/utils.ts now covered. mapParticipantStatus (5 — yes/no/maybe + unknown→maybe + case-sensitive). extractDomain (9 — https/http URL hostname, port stripped, path/query stripped, meet.google.com / zoom.us live cases, invalid URL fallback returns input unchanged, empty string, protocol-less). formatEventTime (7 — parts shape, UTC formatting, minute zero-padding "2:05", AM bucket "8:00 AM", PM bucket "1:00 PM", midnight 12:00 AM edge, noon 12:00 PM edge — all deterministic with explicit timezone arg).
21 cases
copilot/images.test.ts — 23 tests NEW (loop 36) — copilot feature
Pre-existing pure helper in features/copilot/lib/images.ts now covered. getContextualImage routes free-text intent to Unsplash topic URLs. 6 Costa Rica geo-variants (plain / pacific / central / caribbean), real-estate (house / property), vacation, investors (investor / funding both share branch — documented), startup, seed-round (seed / round), introduction (introduction / intro), networking (networking / connection), career (job / career), mentor (mentor / advisor; documented startup-keyword shadow), question fallback for unrecognized + empty, case-insensitive normalization.
23 cases
xano/fetch.test.ts — 23 tests NEW (loop 37) — security infra
XanoError is the wire-error class used by every BFF/api hook in the app. Tests cover constructor (detail vs template fallback, name preservation, throwable+catchable as Error, fields preserved), 4 status getters (isNotFound 404, isValidationError 400/422, isServerError 500+, isNetworkError status=0), and userMessage prioritization rules (network > 404 > server > custom detail > generic, with the documented quirk that 404 ignores even a provided detail per current impl).
23 cases
sanitize-html.test.ts — 32 tests NEW (loop 37) — security infra
Defense-in-depth HTML sanitizer for dangerouslySetInnerHTML callers. Covers null/undefined/empty input, plain-text passthrough, safe HTML preservation (p/strong/em/a/href). 9 dangerous tag removals (script/iframe/object/embed/link/meta/form/base/style). 5 on*-handler scrubs (onclick/onerror/onload/mixed-case/multi-attr). 10 protocol checks (javascript:/vbscript:/data: stripped from href/src/action; https:/http:/mailto:/relative preserved; case-insensitive detection; leading whitespace handled). 3 integration cases (combined attack, nested safe markup, void tags).
32 cases
timezone.test.ts — 10 tests NEW (loop 38)
src/lib/timezone.ts toTZDate is used by features/events to format calendar timestamps in the user's working timezone. Input type handling (4 — Date / number / ISO string + same underlying instant preserved across types). Timezone behavior (5 — UTC matches source, America/New_York shifts by EDT offset in May, America/Los_Angeles by PDT, Asia/Tokyo by JST, Europe/London by BST). getTime() invariance (1 — different timezones for the same source instant yield identical .getTime(); the timezone affects display getters only).
10 cases
build-thread-lang.test.ts — 15 tests NEW (loop 39)
Pulled buildThreadLang() out of the canvas-openui component body into lib. Composes per-turn OpenUI Lang programs into one compound Root([...]) program for the Renderer. Empty input → empty string; single turn (UserMessage + body + Root); 'root = ' prefix stripping (with leading whitespace); empty/whitespace user-text skip rules (3 cases — empty / spaces / newlines); multi-turn composition (2 turns full + mixed empty + var uniqueness); JSON-encoding of user text (quotes, newlines, backslashes); output shape (Root last, line count matches statement count).
15 cases
card-accent.test.ts — 11 tests NEW (loop 40)
getCardAccentStyle from src/components/global/card-accent.ts is the CSS-variable producer used by every active-view canvas to set the indigo/pink/purple glow that frames cards. 8 active views (anything-engine indigo, discover/meeting-prep pink, collections/teams/outcomes/serendipity purple, leverage-loops indigo-deep) + undefined fallback (discover pink) + alpha invariant (border .35, glow .25 across all variants) + shape check (returns exactly 2 CSS variable keys).
11 cases
hotkey-parser.test.ts — 33 tests NEW (loop 41)
Pulled parseCombo + eventKey + matchesCombo + comboHasEsc out of lib/anything-engine-hotkeys.tsx into lib/hotkey-parser.ts. parseCombo now takes isMac as explicit arg for deterministic platform handling in tests. parseCombo (10 — single key / single mod / multi-mod / mod→ctrl on Win / mod→meta on Mac / explicit meta both platforms / lowercase normalization / whitespace trim / esc as key / slash key). eventKey (8 — Escape/Enter/Space/Arrow* aliases, Backspace/Delete/Tab passthrough, slash, ASCII letter lowercasing, F1-style multi-char). matchesCombo (9 — no mods / missing mod fails / all mods present / extra mod fails / multi-mod / Escape via alias / Arrow via alias / cross-platform mod resolution / case-insensitive on event key). comboHasEsc (6 — plain / shift+esc / case-insensitive / negatives / "escape" substring guard / whitespace).
33 cases
resolve-merge-tags.test.ts — 17 tests NEW (loop 42) — email-outreach feature
resolveTemplateHtml + resolveTemplateTags from email-outreach. Renders {first_name}/{full_name}/{nickname}/{company_name}/{deck_link} merge tags into per-recipient HTML and TipTap JSON content. resolveTemplateHtml (11 — every known tag, unknown passthrough, null-name + missing-company empty fallbacks, multi-tag composition, non-mergeTag preservation, plain text untouched). resolveTemplateTags (6 — JSONContent walker, attr clearing, empty-resolved tag removal, recursive descent, structuredClone immutability, multi-tag preservation).
17 cases
date.test.ts — 17 tests NEW (loop 43) — master-persons feature
formatDateRange from master-persons/utils. LinkedIn-style work-history span renderer like "Jan 2020 - Sep 2022 · 2 yrs 8 mos". Two input formats (D-M-YYYY full + M-YYYY shorthand with day defaulting to 1). D-M-YYYY (7 — year range, 'yr' singular, year+month combo, 'mo'/'mos' singular vs plural, months-only span, '< 1 mo' for same-month). M-YYYY (2 — day defaults, multi-year). Invalid inputs (6 — empty, 'null' literals, 'null' day defaults, 4-part rejected, single-part rejected, '1-1' parses as M-YYYY year=1 quirk). 'Present' (1 — null endDate).
17 cases
categorize-attachments.test.ts — 22 tests NEW (loop 44) — email-inbox feature
Splits incoming attachment lists into 4 buckets (images / calendars / documents / others) by content_type. Used by inbox composer + reader to render type-specific previews. getAttachmentCategory (18 — 4 explicit image MIMEs + image/* prefix fallback (HEIC/AVIF), 2 calendar types, 6 document types (PDF/Word/Excel/PowerPoint/YAML), 4 fallback others (text/zip/octet-stream/unknown), 3 normalization rules (strip ;params, trim whitespace, lowercase)). categorizeAttachments (4 — empty input, mixed grouping, insertion order preserved, all-other bucket).
22 cases
class-accents.test.ts — 15 tests NEW (loop 45)
Per-class color identity for the 14 active outcome classes (find_investors indigo, find_talent purple, etc.). Used by every cards-rendering surface (canvas, right-rail, ScanningCard) to set primary/secondary/gradient/bg. Table invariants (7 — exactly 14 entries, all 5 fields present, hex format on primary + secondary, gradient = [primary, secondary], bg matches rgba(*,0.12) format, all 14 primaries distinct). getClassAccent (8 — 3 spot-checks + all-14 round-trip + 4 fallback cases for unknown / empty / "ask_back" classifier-only / "purchase_real_estate" classifier-only).
15 cases
starters.test.ts — 10 tests NEW (loop 46)
ANYTHING_ENGINE_STARTERS — the curated 15-starter list shown in the canvas welcome state and hamburger "All outcomes" tray. Robert May 11 spec: hamburger collapses 14 of these by default. Tests: exactly 15 entries; every starter has displayText/prompt/description/Icon/class; non-empty trimmed strings for the 3 text fields; class strings are snake_case; displayText values unique; prompts unique; all 14 canonical Mark classes present (find_investors → get_advice); meta-starter "What can the engine do?" uses get_advice class.
10 cases
relativity/colors.test.ts — 19 tests NEW (loop 47) — relativity feature
Graph-rendering color palette: 20-color hand-picked tables for node labels + edge types + golden-angle (137.508°) HSL fallback for indices beyond 20. Tables (2 — 20 distinct hex strings each). getNodeLabelColor (5 — table lookup, HSL fallback, deterministic, golden-angle hue spread verified at idx 20 → "hsl(230, 70%, 55%)"). getEdgeTypeColor (5 — table lookup, pastel HSL 50%/70%, +100 internal index offset from node generator). pickPrimaryLabel (7 — empty → "Unknown", single label, single "Entity", prefer non-Entity from multi, FIRST non-Entity wins, all-Entity returns first, case-sensitive filter on literal "Entity").
19 cases
activities/date.test.ts — 7 tests NEW (loop 48) — activities feature
activities/utils/date.ts parseDateString — yyyy-MM-dd → TZDate parser used by the activities timeline. Covers UTC parsing, month index 0-based conversion (Jan=0/Dec=11), day preservation across edges (1, 31), year preservation across decades, timezone arg respect (different tz → different .getTime() but same local-day getters), single-digit month/day via Number coercion, TZDate instanceof Date guarantee.
7 cases
SWARM WAVE 1: 4 quality-audit gaps closed NEW (loop 49) — 10 tests
Worker subagent closed 4 gaps surfaced by an independent quality-auditor agent: build-thread-lang malformed lang (defensive), hotkey-parser empty/edge cases (3 tests for parseCombo), sanitize-html server-side fallback + xlink:href + formaction (3 tests, security-critical SVG attack vector), expandMagnitude empty args + sub-thousand currency (3 tests). Commit 1db791a.
10 cases
funding-round-helpers.test.ts — 35 tests NEW (loop 50) — master-companies feature
Worker subagent extracted 3 helpers from master-companies/components/financial-tab/funding-rounds.tsx into utils/funding-round-helpers.ts. formatMoneyRaised (interprets input as MILLIONS, not raw dollars — important behavioral note), isTerminalRoundType (terminal types: acquired/IPO/post-IPO via case-insensitive Set lookup), formatRoundDate (renamed from formatDate to avoid collision with master-persons formatDateRange). Tests are TZ-safe via mid-day mid-month timestamps. Commit dbadc63.
35 cases
attachment-helpers.test.ts — 18 tests NEW (loop 51) — email-composer feature
Worker subagent extracted formatFileSize + getAttachmentIcon from email-composer/components/recipients-sidebar.tsx. NOT deduped to AE format-helpers' formatBytes — the two differ at the GB tier (caps at MB vs adds GB), so behavior preserved. Then 0aa0ea9 deduped 2 inline copies of these in composer-contents/ that the worker flagged (email-chat-view + email-thread-item). Net -16 lines duplicate code repo-wide.
18 cases
note-modal-helpers.test.ts — 23 tests NEW (loop 52) — notes feature
Worker subagent extracted 3 helpers from notes-modal.tsx. formatNoteDate (12-hour clock with am/pm + zero-padding + month name array — 10 tests). parseNoteContent (DOMParser title/content split — note: returns plain textContent, not HTML — 6 tests). extractMentionIds (DOMParser data-id extraction with dedupe — 6 tests). First test coverage for the notes feature. Commit 7d1146b.
23 cases
organisations: member-display + sort-organisations — 21 tests NEW (loop 53) — organisations feature
Worker subagent extracted from 2 organisations component files. displayNameFor (4-level fallback: full_name → email → workos_user_id, NOT "Anonymous" string — corrected from initial spec) + subtitleFor (returns string|undefined, not string|"") into utils/member-display.ts (14 tests). sortOrganisations (is_personal first, then localeCompare alphabetical — verified non-mutating via [...items].sort) into utils/sort-organisations.ts (7 tests). First coverage for organisations feature. Commit b574096.
21 cases
Section 2 — coverage philosophy

Why pure-function + hook-only scope

Vitest tests cover logic that doesn’t need rendering. The canvas dogfood gate covers everything that does.

Component-render tests in this codebase would be expensive to write and maintain: the AE Renderer mounts via OpenUI Lang strings (not direct JSX), MCQ chips trigger DOM events that wire through ref-based handlers, and the dispatch flow is multi-step async. Capturing all of this in vitest with happy-dom is possible but slow + fragile. The faster + more truthful gate is agent-browser screenshot against the live canvas.

The 4 verification gates (recap)

   pnpm exec tsc --noEmit     # typecheck (catches shape mismatches)
   pnpm exec biome lint        # static analysis (catches deps, a11y, complexity)
   pnpm test                    # vitest (catches logic regressions in helpers + hooks)
   agent-browser screenshot     # live canvas dogfood (catches render + interaction regressions)

Each gate catches a different class of bug. Vitest is the cheap one — runs in 1.13 s, no flake risk. Canvas dogfood is the expensive one — needs the dev server + WorkOS auth + real browser, but catches the things vitest can’t see.

Section 3 — what’s covered

Logic surface area under test

Crayon → OpenUI Lang adapter (chat-shell’s feed-through)
9 cases verify the eventsToOpenLang() function correctly translates legacy 8399 dispatch events into OpenUI Lang strings consumed by <Renderer>. Catches regressions in the legacy compatibility layer.
COVERED
Dispatch gate predicate (when can the user fire dispatch)
4 cases cover the truth table: profile state × polling state × narrative count × error → canDispatch + statusLabel. Catches regressions in the gate UI logic.
COVERED
Pitch profile poll (TanStack Query wrapper)
4 cases verify the polling lifecycle: initial isPolling=true, success state shape, missing_fields propagation, error propagation. Catches regressions in the deck-upload → pitch-profile-ready chain.
COVERED
Conversation title coercion (left-rail history)
13 cases cover real-prompt pass-through, MCQ-fragment detection, money/percent/single-clause heuristics, and edge cases (empty, undefined, length clamp). Catches regressions in the helper that prevents "$15M+" titles.
COVERED
Section 4 — what’s NOT covered

Honest gaps in test coverage

The honest list of what a vitest suite would catch but currently doesn’t.

Each of these gets caught by the canvas dogfood gate today, but a vitest case would catch it 1000× faster on every commit.

sanitizeAskBackReasoning() helper CLOSED 98ebb9b
Closed in loop 20. Helper extracted from canvas-openui.tsx into lib/sanitize-ask-back-reasoning.ts + 21 unit tests. See sanitize-ask-back-reasoning.test.ts.
CLOSED
openui05DispatchFn input validator
No tests for the input validator (rejects invalid class_override, accepts known classes, optional fields). Should test: empty query throws, unknown class_override throws, valid payload passes through.
GAP
use-conversation-starters slot composition
487-LOC hook builds 4 dynamic starter slots (calendar / active-work / network-intel / time-of-day). No tests for: slot priority when calendar is empty, starter cap enforcement, time-of-day routing logic, dedup across slots.
GAP
Class-accent color resolution (class-accents.ts)
No tests for the per-class accent color lookup. Should test: known class returns correct color, unknown class returns indigo fallback, color shape (rgba string format).
GAP
use-selected-anything-engine-conversation TanStack quirk CLOSED 444e622
Closed in loop 21. JSON-parse + Number-coerce-NaN logic extracted into lib/parse-conversation-id.ts with 18 unit tests. Hook is now a thin 1-line wrapper.
CLOSED
eventsToSse() in canvas-openui.tsx
Inline adapter that translates DispatchEvent[] (3 different shapes) → Crayon SSE format. Currently lives inline in the canvas component. Should be extracted to a pure function and tested.
GAP
Section 5

Test infrastructure

vitest 28.x + happy-dom
Standard Vite test runner. happy-dom for browser globals. @testing-library/react for hook + component tests when needed (currently only hook tests).
RUNNER
test-utils/query-wrapper
Shared TanStack Query wrapper for testing hooks that depend on Query context. Used by use-dispatch-gate and use-pitch-profile-poll tests.
UTIL
vi.mock() of server functions
Pattern used in both hook tests: vi.mock("../server/pitch-profile-fn", () => ({ pitchProfileFn: (args) => mockFn(args) })). Lets tests run without the TanStack Start server runtime.
PATTERN
Section 6

Recommended next test additions

Ranked by leverage (line-coverage gain × regression risk):

1. sanitizeAskBackReasoning helper — 21 cases DONE
Closed in commit 98ebb9b (loop 20). Helper extracted to lib/. 21 cases — 3.5× the original ~6-case estimate.
DONE
2. openui05DispatchFn input validator — ~4 cases
Medium leverage. The validator rejects unknown class_override + missing query. Catches refactor-time regressions if someone adds a class but forgets to update the KNOWN_CLASSES set.
DO NEXT
3. eventsToSse extraction + tests — ~10 cases
Medium leverage. Currently inline in canvas-openui.tsx. Extracting it makes the canvas easier to read AND testable. 10 cases would cover the 3 event shapes + edge cases.
REFACTOR + TEST
4. use-selected-anything-engine-conversation parsing — 18 cases DONE
Closed in commit 444e622 (loop 21). Logic extracted to lib/parse-conversation-id.ts. 18 cases — 3.6× the original ~5-case estimate. Same self-correcting loop as item 1 (sanitizeAskBackReasoning).
DONE
Cumulative this session: 30 → 163 test cases (+133, +443%).

SIX helper modules extracted + tested within the same session as the gap was documented (or surfaced). Three from the original next-steps queue (sanitizeAskBackReasoning, parseConversationId, networkErrorMessage) plus three bonus extractions during the inline-helper sweep: adaptContactCard suite (event-renderer.tsx), format-helpers x4 (chat-shell.tsx), contact-card-utils x4 (components.tsx). One queue item — openui05DispatchFn validator — is wrapped in createServerFn so harder to extract without a server-runtime mock; deferred. Item 3 (eventsToSse extraction) was moot.