← Market Analyst · c12

problem-pages-plan.md

c12 thread: problem-pages — plan

Written 2026-09-21 from a read of every page, the layout, the composable, and the components in site/. Prompt: problem-pages-prompt.md. This expands plan.md §2J into a placement plan and becomes the spec for thread T2. Status: plan; nothing built.

1. What the site is today

Eight pages, one composable, ten components. Every page reads data/all-calls.json (+ use-cases.json, feedback.json, analysis.json) through useCallData(); a global FilterBar (size, call type, funnel, source type, perspective, theme, interest) and a nav-level TimeframeFilter narrow filteredCalls. All pages share one card idiom: border border-grey-700 rounded-xl p-6 with an uppercase tracking-wider section label.

PageWhat it answers todayLensVerdict for the problem framing
Dashboard /Three rows of StatCard (conversations, avg interest, avg urgency; use cases with evidence, product feedback, market signals; next steps, action items, risks) → Use-Case Evidence rollup (gate status, direction/perspective pills, top 3 items) → Direction (c11 session rollup) → six DistributionBar charts → themes → AI recommendations → recent callsSales review first, thesis secondRe-order, don't rebuild. The Use-Case Evidence and Direction blocks are already problem-adjacent; the KPI rows are the sales-shaped part.
Calls /callsFilterable table of callsNeutralAdd a Problems column (count chips).
Call detail /calls/[id]Summary + Details, attendees, themes, Use-Case Evidence, Product Feedback, market-signal sections, Session Record, urgency, action items, decision making, positioning, competitive, key quotesPer-call, type-slicedAdd one section near the top: problems tagged here + candidate problems from extraction. This is where promotion happens.
Companies /companies, /companies/[name]Per-company cards; detail rolls up the company's callsSegmentAdd problems-per-company chips. Small.
Insights /insightsInsightCategoryCard per market-signal type grouped by category, with mention/company counts; competitive landscapeThesis categoriesStays as the market lens. Gains a surface facet in the filter, nothing more.
Feedback /feedbackProduct-feedback observations grouped by product area, status pill from feedback.json (always new)TriageRetire the lifecycle (decision 4): drop the status pill and the rollup read; banner "not a tracker"; bugs and blockers stay listed. Keep the page, demote it in the nav.
Segments /segmentsSegmentCard by company size / persona / funnel; AI segment analysisMarketUntouched this cycle; persona segments later read personas.json.
AboutProse mirror of README "How to Interpret"—Add the micro-problem section.

What already exists that the problem pages compose from (assemblage, not invention): StatCard, Insight + InsightQuote (text + verbatim + speaker + role + company links), InsightCategoryCard (collapsible grouped list with mention and company counts — nearly a problem list already), CountDots, DistributionBar, the pill helpers in site/utils/format.ts (perspectivePill, sourceTypePill, directionPill, validationPill), the Use-Case Evidence card markup on the dashboard (gate pill with reasons tooltip, direction pills, perspective pills, top-N items with call links), and the Direction block (three-column rollup of session items). The problem detail page is the use-case card at full size; the problem list is InsightCategoryCard with standing instead of category.

2. Placement — where the problem-centric framing lives

2.1 Navigation

Dashboard · Problems · Calls · Companies · Insights · Feedback · Segments · About → Problems moves to second position, immediately after Dashboard. Feedback drops to after Insights (it is observation-level now). The nav is the first thing that says what the tool is for.

2.2 Dashboard / — re-ordered, first row becomes the problem space

  1. Row 1 (new): Problem KPIs. StatCard × 3: Problems recurring (value N, subtitle "first-hand from 2+ orgs", → /problems?standing=recurring), Problems observed (subtitle "first-hand from one org"), Candidates awaiting review (subtitle "proposed by extraction, not promoted", → /problems#candidates). A fourth optional: Briefed / in design once consumers.json exists.
  2. Row 2 (new): Problem space — a compact card: top 6 problems by standing then first-hand count, each row = statement (bold) · question (muted, one line) · standing pill · company chips · persona chips · status pill · "→". Link "All problems →". This replaces nothing; it sits above Use-Case Evidence.
  3. Use-Case Evidence stays, and each use-case card gains a line of child problem chips (the decomposition made visible) — problems whose useCaseIds include it. A use case with no children reads "no problem under this hypothesis" in the same muted yellow used for "Not validated".
  4. Direction stays; each decision/idea that carries a problemId shows a small problem chip.
  5. Rows 3–4 (existing sales KPIs) move below Direction: conversations / avg interest / avg urgency; next steps / action items / risks. Nothing removed.
  6. Charts, themes, recommendations, recent calls unchanged. One new chart in the charts grid: Problems by surface (DistributionBar over surfaces[] of promoted problems) beside Observation Mix; build it with the /dataviz rules when the time comes.

2.3 /problems — the registry, the new home

Header: "Problems" · "The problem space, formed from N conversations · standing is computed from first-hand, non-internal evidence; advisor and team input is labelled".

  • Controls row: the global FilterBar (so timeframe/source/perspective still narrow evidence counts) plus three problem-specific selects: standing (candidate / observed / recurring), persona, surface, use case, status. A "sort by" toggle: standing · first-hand count · recency of last evidence · status.
  • List: one card per promoted problem, in the InsightCategoryCard collapsible idiom:
    • collapsed row: statement · standing pill (recurring green / observed sky / candidate grey, tooltip = reasons like the validation pill) · CountDots of first-hand count · company chips (with source-type colour, so "Tiled · Moto‑advisory" reads honestly) · status pill (in-design, partial, …) · persona chips · surface chips · use-case chips
    • expanded: the question in the quote style · situation · evidence grouped by perspective (first-hand first, then relayed, then internal) as Insight cards with verbatim, speaker, role, company link · challenge evidence in its own sub-row (red-tinted pill) · session items tagged to it (decision/idea with status) · consumers (briefs, design/bob-wire cycles) · "what would make this recurring" (the computed reasons)
  • Candidates section (anchor #candidates, below the list): every candidateProblems[] item across filteredCalls not yet promoted, grouped by nearestProblemId ("near: personal-vs-enterprise-accounts") or "new", each with statement, question (if proposed), persona hint, speaker + perspective pill, verbatim, call link, and a copy-JSON action that puts a ready-to-paste registry entry on the clipboard. This is the promotion workflow: you curate data/problems.json; the site never writes.
  • Missing-hypothesis callout at the top of the list when any promoted problem has empty useCaseIds: "N problems have no use case — a hypothesis may be missing" with chips.

2.4 /problems/[id] — one problem, full size

The use-case card at full page size, same order as the expanded row above, plus: a status timeline (dated transitions from the registry: candidate → observed → … with the consumer that moved it), a by-company table (company · source type · first-hand / relayed / internal counts · last heard), and related problems (share a use case or a surface). Deep-linkable so a brief's Source block can cite /problems/<id>.

2.5 /calls/[id] — where promotion starts

New section Problems directly after Summary + Details (before Attendees):

  • left: tagged problems — chips for every problemId on this call's observations and session items, each with the count of items and a link to /problems/<id>; hover shows the items.
  • right: candidate problems (from candidateProblems[]): statement, question, persona hint, speaker + perspective pill, verbatim, near: <id> chip, copy-JSON action. Empty state: "extraction proposed no new problems for this call".
  • Details grid gains Shown (artifact chips from the triage shown[] declaration) once Q10 lands; observations with artifactRef show a small "re: " chip.

Existing sections unchanged. Observation cards (evidence, feedback, market signals) gain a problem chip where problemId is set.

2.6 Smaller placements

  • /calls table: a Problems column with chips (count-capped, "+N").
  • /companies/[name]: a Problems card: which promoted problems this company evidences, with perspective counts. /companies cards: a problem count.
  • /insights: FilterBar gains surface and persona selects (shared, so they also apply on /problems); Insight cards show a problem chip when tagged. No structural change — this stays the market lens.
  • /feedback: remove the feedback.json read and status pill; banner "Product feedback is listed here, not tracked here — defects go to a tracker when one exists (c12 decision 4)"; items gain a surface chip and problem chip. Nav label stays "Feedback".
  • /about: a "Micro problems" subsection under How to Interpret (statement vs question, standing rule, candidates vs promoted), mirroring the README change in T4.

3. Data contract the site needs (what T1/T2 must produce)

The site reads JSON at runtime from /data/. New or extended files:

FileProducerShape (summary)
data/problems.jsonMark (curated){ problems: [{ id, statement, question, personaIds[], situation, useCaseIds[], surfaces[], status, statusHistory[{status,date,by?}], origin, notes, evidence?: [{ callId, observations?: number[], session?: { decisions?: number[], ideas?: number[] } }] }] }
data/problems-index.jsonbuild-index.ts (T1)per problem: standing, counts { companies, firstHand, relayed, internal, challenges }, reasons[], companies[{ name, sourceType, counts }], lastHeard, resolved evidence items (callId, index, kind, perspective)
data/all-calls.json / data/calls/*.jsonextraction (T1)observations[].problemId?, .problemDirection?, .surface?, .artifactRef?; session.decisions[].problemId?, session.ideas[].problemId?; candidateProblems[]
data/personas.jsonMark, seeded from c55 (T1){ personas: [{ id, name, orgSize, role, jobs[] }] }
data/consumers.jsonnpm run consumers (T4){ briefs: { [file]: cycles[] }, problems: { [id]: [{ repo, cycle, artifact }] } }

Bridge so the site can ship before extraction tags flow: problems.json carries hand-curated evidence[] refs (call id + observation indices), transcribed from problems-draft.md. build-index.ts merges curated refs with extraction's problemId tags into one evidence list per problem (deduplicated by call + index). Curation stays legitimate after T1 — Mark can always pin an observation to a problem the extractor missed.

useCallData.ts keeps its own interface copies (known gotcha): add problemId, problemDirection, surface, artifactRef on Observation; problemId on session items; candidateProblems on CallData; Problem, ProblemStanding, Persona interfaces. Load problems.json, problems-index.json, personas.json, consumers.json in loadData() (each .catch to empty, as today). Drop the feedback.json load.

4. Composable and components

  • useCallData() gains: problems, personas, problemRollup (per problem: registry record + index standing + evidence items resolved against filteredCalls, so the global filters and timeframe narrow the counts; recomputed client-side from the same rule as problem-standing.ts so filtered views stay honest), candidateProblems (flattened across filteredCalls, with promoted: boolean), problemsBySurface, problemsByPersona. Filters gains problemId, surface, persona; filterOptions gains the three lists.
  • New components: ProblemRow.vue (collapsed/expanded card), StandingPill.vue (with reasons tooltip; mirrors validationPill), ProblemChip.vue (id → statement tooltip, link), SurfaceChip.vue, PersonaChip.vue, CandidateProblem.vue (with copy-JSON). Reused as-is: StatCard, Insight, InsightQuote, CountDots, DistributionBar, FilterBar (extended).
  • Colours stay site-side in site/utils/format.ts (standingPill, problemStatusPill, surfaceLabel, personaLabel); labels for surfaces and personas come from schemas/vocabularies.ts / personas.json through the existing cross-project import bridge.

5. Build order (this is T2, sequenced)

  1. Registry bridge (no extraction change): transcribe problems-draft.md into data/problems.json with curated evidence[] refs; personas.json from c55 + P12; problem-standing.ts + build-index.ts writing problems-index.json. npm run index. Gate: the 15 problems and their standing appear in the index exactly as the draft computed by hand.
  2. /problems list + /problems/[id] against the bridge data; nav reordered.
  3. Dashboard re-order (rows 1–2, child chips on use cases, sales KPIs demoted).
  4. Call page Problems section (tagged + candidates; candidates will be empty until T1 lands, the section says so).
  5. Smaller placements (calls table column, companies, insights filter facets, feedback retire, about).
  6. After T1: candidates populate, problemId tags merge with curated refs, artifact chips appear. No page changes needed for that — only data.

Each step: npx tsc --noEmit clean in site/; dev server check; commit.

6. Decisions — confirmed by Mark 2026-09-21

  1. Nav: "Problems", second position. ✅
  2. Dashboard: re-order as §2.2 — chosen because it keeps the most gateways to other places. Rule for the build: every new element is a link (KPI cards → /problems filtered; problem rows → /problems/<id>; child chips → problem; company chips → company; nothing on the existing dashboard loses its link). ✅
  3. Feedback page: remove it and fold in. Product-feedback observations surface on /problems (tagged ones under their problem; untagged bugs/blockers in a "Feedback not tied to a problem" section at the bottom, with the "not a tracker" note), on the call page (already), and via a type facet on Insights. Nav loses "Feedback"; feedback.json is no longer read. Redirect /feedback → /problems#feedback. ✅ (supersedes §2.6 "feedback" bullet)
  4. Build against the bridge now. ✅
  5. Source type beside each company chip in the standing label. ✅

7. Verification

  • /problems lists 15 promoted problems; the four recurring ones (unknown-agent-fleet, personal-vs-enterprise-accounts, unexplained-ai-spend, prove-posture-over-time) carry recurring with two company chips each; counts equal problems-index.json.
  • Narrowing the FilterBar to sourceType = prospect drops Moto evidence and re-computes standing live (personal-vs-enterprise-accounts falls to observed).
  • /problems/personal-vs-enterprise-accounts shows the question as the quote, evidence grouped first-hand → relayed → internal, and the c21 brief under consumers once consumers.json exists.
  • Dashboard row 1 reads recurring 4 / observed 11 / candidates 0 (before T1); use-case cards show child chips; gateway-bypass-detection reads "no problem under this hypothesis".
  • /calls/09ecfede… Problems section shows chips for sensor-coverage-gap, personal-vs-enterprise-accounts, unexplained-ai-spend, web-ai-usage-blindspot, session-transcript-governance from curated refs.
  • /feedback renders with no status pills and the not-a-tracker banner; feedback.json is no longer fetched.
  • npx tsc --noEmit clean at root and in site/; npm run generate succeeds (with the dev server stopped).

Qpoint Brand Style Guide