Foundry Foundry

E3: UI — Epic Design Doc

Status: 🔄 In Refinement (Step 0) Authors: Dan Hannah & Clay Created: 2026-04-18 Parent: QuoteAI Project Design Doc


Overview

Goals & Non-Goals

Goals:

  • Ship a structured quote-request form (the "wall" per John) with all 15+ required fields, validated
  • Ship a draft-view page that renders the CC-generated quote in 4M-template shape
  • Wire the form → CC → draft-view flow end-to-end
  • Dark-mode-first design (per user preference) — clean, "nice-looking," professional enough to show John
  • Feedback buttons on the draft view (👍/👎 per line item)

Non-Goals:

  • No authentication / login
  • No approval workflow UI (Full MVP)
  • No quote log / dashboard UI (Full MVP)
  • No PDF export (copy-paste from the rendered draft is fine for demo)
  • No responsive mobile polish (desktop demo first)
  • No admin UI for managing ingested data (CLI only, per E1)

Problem Statement

The UI is the face of the demo. Two things have to work: (1) the form must feel like a natural upgrade to the Brehob spreadsheet salespeople fill today, and (2) the output must look like a Brehob quote, not a "generic AI draft." Both are needed for John to react with "this is how every quote should look."

Per John's "wall" constraint, the form is the only way salespeople interact with the system. No chat, no freeform AI prompt. The form is the contract.

What Is This Epic?

A Next.js (App Router) UI with two primary pages:

  1. /quotes/new — the structured quote request form
  2. /quotes/draft/[id] — the CC-assembled draft view in Brehob template format

Plus the glue that connects them: a Server Action or API route that receives the form submission, writes a pending request to a temp table, triggers CC to assemble the draft, and displays the result.


Context

Dependents

  • Demo experience — E3 is what John actually sees
  • Future Full MVP E4 (Approval Workflow) — adds a review queue on top of /quotes/draft/*

Dependencies

  • E0 (Foundation) — Next.js shell, Tailwind, shadcn/ui primitives exist
  • E2 (MCP Servers) — CC needs them running to assemble drafts
  • E1 (Data) — via E2
  • Claude Code — the demo's generator; triggered from the app

Current State

Next.js shell exists from E0 with a /health page. No forms, no draft view, no generate flow.

Affected Systems

System / LayerHow It's Affected
app/ (Next.js)New routes: /quotes/new, /quotes/draft/[id]; shared form components
PostgresNew temp table quote_drafts (demo-only) to persist form submissions + generated drafts
Claude CodeTriggered from a server-side flow with the form data + template reference

Design

Page: /quotes/new

Form built with React Hook Form + Zod validation. Sections:

  1. Customer Info — Company, contact, address, ship-to, email, phone
  2. Quote Metadata — Salesperson name, TM number, date required, (auto-filled) quote number preview
  3. Line Items (repeatable, 1-N) — Each with: description hint, qty, CFM, PSI, HP, voltage, cooling (air/water), unit price (entered by salesperson)
  4. Context — Free-text field for "special requirements" (e.g., "food-grade environment, oilless required")

Submit button: "Generate Draft"

Validation (per design doc "The Wall"):

  • CFM: 1–5,000
  • PSI: 10–500
  • HP: 1–500
  • Voltage: must match patterns like 230/1/60, 460/3/60
  • TM Number: required (salesperson's territory number)
  • All customer fields required
  • Date required: must be a future date
  • At least one line item with a description

Page: /quotes/draft/[id]

Renders the assembled draft in Brehob template shape. Layout:

  1. Header bar — Quote #, status badge, copy-to-clipboard button
  2. Document preview — Rendered markdown (from CC's output) styled to mimic the 4M letterhead/template
  3. Per-line-item feedback — Each line item has 👍/👎 with optional comment
  4. Action bar — "Regenerate" button (useful during iteration), "Copy Quote Text" (for paste-into-email-to-customer)

Important: This is a display layer for CC's output, not a WYSIWYG editor. For demo, salesperson edits happen by re-submitting the form. Full MVP can add inline editing.

Generate Flow (form → CC → draft)

Demo architecture uses Claude Code as the generation engine. This is the only real "magic" in the demo — the flow needs to be explicit about it.

1. Form submits to POST /api/quotes/generate (Next.js Route Handler)
2. Handler validates form data against Zod schema
3. Handler inserts a row into `quote_drafts` with status='pending', form_json
4. Handler invokes Claude Code via a local subprocess (or a "/quoteai-generate" skill)
   passing: form_json + path to templates/brehob-quote.md + MCP server config
5. CC calls search_line_items, search_equipment, search_past_quotes as needed
6. CC returns assembled draft text
7. Handler updates quote_drafts row: status='ready', generated_text
8. Handler redirects to /quotes/draft/[id]
9. Page renders generated_text in Brehob template style

CC invocation detail: Easiest path is a local CLI spawn: claude -p "$PROMPT" --mcp-config ./cc-mcp-config.json. The prompt assembles form data + reference to the template file. CC handles MCP tool calls internally.

If the subprocess path is awkward, alternative: CC runs as a persistent background agent and the app talks to it via a local HTTP endpoint. TBD in S0 spike.

Styling / Design System

  • shadcn/ui components (Form, Input, Card, Button, Badge, Tabs)
  • Tailwind with dark-mode class on <html> by default
  • Typography — Inter or similar for UI; serif for the document preview (mimics Brehob's Word-doc feel)
  • Color palette — neutral dark with a single accent (maybe Brehob blue?) — keep it calm, professional
  • Print styles — document preview should print/PDF cleanly even though explicit PDF export is post-MVP

Data Model Changes

One new table for demo flow persistence:

CREATE TABLE quote_drafts (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    form_json JSONB NOT NULL,
    generated_text TEXT,
    status TEXT DEFAULT 'pending',  -- pending, ready, failed
    generated_at TIMESTAMPTZ,
    error TEXT,
    created_at TIMESTAMPTZ DEFAULT now()
);

This is demo-scoped. When Full MVP E5 lands, quote_drafts rows get promoted into quote_log + quote_log_items. Or the table gets renamed. Decide during Full MVP scoping.


API / Interface Changes

Route Handlers (Next.js)

RouteMethodPurpose
/api/quotes/generatePOSTReceive form, trigger CC, return draft ID
/api/quotes/[id]GETFetch draft by ID (used by the draft page)
/api/quotes/[id]/feedbackPOSTLog 👍/👎 on a line item

Client Components

  • <QuoteRequestForm /> — the form
  • <LineItemEditor /> — repeatable line-item sub-form
  • <DraftView /> — renders generated_text
  • <FeedbackButton /> — per-line-item rating

Edge Cases & Gotchas

ScenarioExpected BehaviorWhy It's Tricky
CC takes >30s to generateShow loading state with progress messageGeneration isn't instant; UX needs to feel "working" not "broken"
CC fails / errorsUpdate quote_drafts.status='failed', show retry button on draft pageDon't silently lose the form data
User submits same form twice (double-click)Idempotency key or disable button on submitClassic form gotcha
Multi-option quote (~50% of real quotes)Support adding an "Option 2" tab with its own line itemsDesign doc calls this out as open; E3 must answer it
Very long descriptions in draftAuto-wrap, don't overflow; print-safeSome installation blocks are 500+ words
Dark mode clashes with document preview (which mimics a white-paper feel)Preview area uses a light-on-dark-background surface even in dark modeDocument-preview-in-dark-mode is a UX tension
Voltage regex too strictOffer suggestions, not just rejectReal data shows variations John might enter differently

Testing Strategy

Test Layers

LayerApplies?Notes
Unit testsYesZod schemas; form validation; voltage pattern matcher
Integration testsYesForm submit → DB row created; draft page fetches by ID
Visual regression (Crucible)YesForm page + draft page have baselines. Dark mode is the canonical baseline per user preference.
E2E (Playwright or manual)YesFull flow: open /quotes/new, fill valid form, submit, wait, see draft
AccessibilityYesBasic a11y check: form fields have labels, errors are announced

Required Fixtures

Fixture NameWhat It TestsPriority
fixtures/e3-form-submission.jsonFull valid form payload for CC to assemble against🔴 High
fixtures/e3-golden-draft.mdExpected Brehob-format output for the golden scenario🔴 High
fixtures/benchmark-quotes/4m-industries.mdEnd-to-end benchmark: given 4M's original form inputs, the generated draft should match this finished Brehob quote for description fidelity, formatting, and completeness🔴 High
fixtures/benchmark-quotes/bowen-engineering.mdEnd-to-end benchmark: standard-complexity multi-line system quote🟡 Medium
fixtures/benchmark-quotes/munson-hospital.mdEnd-to-end benchmark: complex quote with installation / custom scope🟡 Medium
baselines/form-dark.pngForm page dark-mode baseline🔴 High
baselines/draft-dark.pngDraft page dark-mode baseline (with seeded content)🔴 High

Note on benchmark quotes (new per 2026-04-18 decision): Distinct from the golden retrieval test (E1). The golden retrieval test asks "did we find the right line items?" The benchmark quotes ask "does the assembled draft match a known-good Brehob quote?" Both run during E3's E2E verification.

Verification Rules

  1. Golden scenario passes end-to-end before demo. Open form → fill it in → draft appears matching golden-draft.md within acceptable drift.
  2. Benchmark quotes pass end-to-end. Given the original form inputs for 4M / Bowen / Munson, the generated drafts match the benchmark fixtures for description fidelity, formatting, and completeness. This is the assembled-output quality gate (complementary to the golden retrieval test in E1).
  3. Dark mode is the primary baseline. Per user preference.
  4. Form validation errors are clear and in-place (not a toast at the top).
  5. All user preferences from memory files respected (dark mode especially).

Stories

StorySummaryStatusCommit
S0Agent SDK round-trip spike + reconciled handoff from Claude Design✅ Shipped72595c7
S1quote_drafts staging table + atomic quote_no allocation✅ Shippedf233420
S2/quotes/new — Form A with React Hook Form + Zod validation✅ Shipped7deda9d
S3Brehob Letterhead component + verified data const✅ Shippedde4f27f
S4Claude Agent SDK wiring + SSE route handler✅ Shipped4625f6a
S5markdown → RenderMeta adapter + SSE wire-up✅ Shipped30defbd
S6Price injection — {{LINE_N_TOTAL}} / {{TOTAL}} substitution on render✅ Shipped7a4bc5e
S7/quotes/[id] — streaming "Generating" screen with SSE consumer✅ Shipped2212101
S8/quotes/draft/[id] — Draft F three-rail view✅ Shipped17d8370
S9Regenerate-with-guidance — dialog + action + chip composer✅ Shipped9d7c1c1
S10Content-failure banner for Draft F✅ Shipped58e291f
S11Backend smoke script + demo SMOKE.md checklist✅ Shippedc97d787, f114b72

Post-S11 demo polish (2026-04-21 → 2026-04-22)

Not numbered stories — small scoped changes landing ahead of John's review.

WorkSummaryCommit
App shellHeader + landing + recent drafts + demo scenarios7231547
Thinking fixDisable Sonnet 4.6 extended thinking (437s → 110s) — see E3-D4ecb250d
Router fixRelocate quote pages under app/app/ so Next.js picks them up0aefb1e
Hi-fi design portWarm-greyscale palette + Source Serif 4 / Inter / JetBrains Mono ported to form + landing + header — see E3-D711eac9d
Streaming UXFour-stage display-driven checklist + client-side pacer + window-scroll auto-follow — see E3-D5, E3-D62d75fa8
Preamble trimServer-side md_delta buffer until first fence — see E3-D84975380
Replay + sticky/quotes/[id]/replay route + draft-rail top-offset fix6f9db34

Note on story drift. The pre-implementation Stories list (2026-04-18) had different summaries for S3-S10 — field validation, line-item repeater, multi-option tabs, feedback buttons, loading states, E2E walkthrough, dark-mode polish. Implementation diverged as shape emerged. Most notably S5 (multi-option quotes) was dropped per E3-D1, and post-generation UX (streaming, three-rail draft, regenerate flow) became the bulk of the work. The table above reflects what shipped, not the original aspiration.


Known Issues / Tech Debt

IssueSeverityNotes
quote_drafts overlaps with future quote_log🟡 MediumIntentional demo-scoped; merge in Full MVP
No inline editing of the draft🟡 MediumDemo accepts "regenerate with different form input"; Full MVP adds edit
No print-optimized CSS🟢 LowGood enough; PDF export is post-MVP anyway
CC subprocess has no timeout / cancellation🟡 MediumAdd a 60s timeout; failed generation shows retry
No user identity in quote_drafts🟢 LowDemo is single-tenant Brehob; Full MVP adds salesperson identity

Risks

RiskLikelihoodImpactMitigation
CC subprocess path is fragileMedium🔴 HighS0 spike explicitly derisks this; fall back to persistent-agent + local HTTP if needed
Draft rendering doesn't look "Brehob enough"Medium🔴 HighBuild golden-draft.md as a ground truth; iterate styling until rendered output matches visually
Multi-option quotes are messy in the UIMedium🟡 MediumScope S5 explicitly; prototype before committing to tabs vs stacked sections
Form feels clunky or slowLowMediumReact Hook Form is fast; use optimistic UI where possible
Dark mode + Brehob-letterhead-feel clashesMedium🟢 LowPreview area can be a light surface within the dark chrome — common pattern in admin tools

Decisions Log

DateDecisionRationaleAlternatives Considered
2026-04-18Next.js App RouterE0 already chose thisPages Router (rejected: legacy)
2026-04-18React Hook Form + ZodMatches shadcn/ui's patterns; type-safe validationFormik (rejected: older), raw state (rejected: tedious for 15+ fields)
2026-04-18Dark mode as default (not opt-in)Per user memory — Dan uses Foundry exclusively in dark modeLight default with toggle (rejected: contradicts preference)
2026-04-18CC as generator for demoPer project design doc Rev 7Anthropic API in-app (rejected: Full MVP)
2026-04-18Display-only draft view (no inline edit)Simpler; "regenerate" loop is fine for demo iterationWYSIWYG editor (rejected: scope creep)
2026-04-18quote_drafts is a demo-scoped staging tableAvoids mixing with quote_log which has approval workflow semanticsPromote directly to quote_log (rejected: quote_log schema assumes approval state)
2026-04-18Feedback is per-line-item, not per-quoteMatches retrieval granularity; better signalPer-quote (rejected: too coarse)
2026-04-18Visual baselines mandatory for form + draft pagesPer user preference and QA methodologySkip for demo (rejected: risks visual regression during iteration)
2026-04-18End-to-end benchmark quotes for assembled-output validationPick 2-3 finished Brehob quotes (4M Industries, Bowen Engineering, Munson Hospital) as system-output ground truth. Given the same form inputs, generated drafts are compared for description fidelity, formatting, and completeness. Complementary to E1's golden retrieval test.Golden retrieval test only (rejected: doesn't validate assembled output quality)
2026-04-21Markdown output from model, not structured JSON (E3-D2)Agent SDK emits tokens incrementally; composing a valid JSON object piece-by-piece during a 60-90s stream is fragile (one unescaped quote = parse failure). Model drafts a markdown doc in natural shape; server-side adapter (app/lib/cc/adapter.ts) parses markdown to typed RenderMeta for Draft F. Streams cleanly, survives malformed output, and lets the draft be human-readable even mid-generation.Structured JSON output (rejected: fragile streaming, model needs to escape every quote, ~1/3 slower in spike); freeform prose with post-hoc parsing (rejected: unbounded format drift)
2026-04-22Port hi-fi design tokens + rename --accent to --brand (E3-D7)design/e4-hifi.html defined the warm-greyscale palette + Source Serif 4 + Inter + JetBrains Mono direction. Ported to app/app/globals.css wholesale. Hi-fi's --accent is warm orange (brand color); shadcn's --accent is a subtle hover/focus surface token that Tailwind maps to bg-accent. Using the same name would have made every shadcn bg-accent render warm orange. Renamed hi-fi → --brand; shadcn's --accent now aliases --surface-3. If you ever port more hi-fi CSS snippets, mentally substitute --accent--brand.Keep shadcn cold-grey defaults (rejected: visual identity matters for John's review); override shadcn's --accent to the warm orange (rejected: would break every shadcn hover/focus state)

Active decisions being validated during John's demo review live in quoteai/decisions.md — E3-D1 (no multi-option), E3-D4 (thinking disabled), E3-D5 (four-stage checklist), E3-D6 (display pacer), E3-D8 (preamble trim). They graduate here once the demo validates them.


E3 is the demo. Everything previous exists to make this moment — John opens the page, fills the form, and sees the draft — land right.

Review

🔒

Enter your access token to view annotations