ID-147 {147.2} PRODUCT — Procurement UI foundation: Extend document surfaces + Streamdown (form-first item UI behaviour)
ID-147 {147.2} PRODUCT — Procurement UI foundation (form-first item UI behaviour)
Section titled “ID-147 {147.2} PRODUCT — Procurement UI foundation (form-first item UI behaviour)”Type: PRODUCT ({N.2}). Behavioural spec for the Procurement-domain UI foundation — the user-visible behaviour of every UI surface ID-145’s form-first model needs, built on the Extend UI document components (copy-in shadcn source) and Streamdown (markdown). Authored 15/07/2026, fresh Planner context. UK English throughout (DD/MM/YYYY, “colour”, “organisation”). Track:
/procurement(ca-s435-procurement-baseline); ledger writes on MAIN only, not committed here.Design against settled ground. OQ-1…OQ-7 are RESOLVED (owner round 2, 13/07/2026) and the load-bearing rulings are in the Decision Register — DR-063 (adoption posture), DR-064 (bounding-box geometry), DR-065 (Schema Builder → custom), DR-066 (Editor/E-Signature v1), DR-067 (custom stepper), DR-068 (multi-doc hybrid), plus DR-040 (Streamdown). This spec cites those rulings; it does not re-open them.
Substrate (read for full grounding):
id-147-empirical-grounding.md(round-2, all OQs resolved, empirical §12) andRESEARCH.md({147.1}). This spec is the behavioural layer over that substrate;{147.3}TECH.md carries the install slice, geometry plumbing, and vendor-in mechanics.
Summary
Section titled “Summary”ID-147 establishes the Procurement-domain UI foundation by adopting Extend UI’s document components (as copy-in shadcn source, no npm runtime dependency on Extend itself) and Streamdown for markdown, mapped onto the ID-145 form-first surfaces. This spec defines the user-visible behaviour of each adopted surface — the multi-document item page, document viewers, spatial fill-slot review, bounding-box citations, the upload affordance, the DOCX/Excel editor + e-signature, the workflow stepper, the requirement/question editors, and the Streamdown rendering surfaces — so an implementer can build them without re-deriving product intent. The chain gates ID-145.19 (item-page UI surfaces).
Problem
Section titled “Problem”ID-145 ratified a form-first model (the item is the form) but its item-page, document, citation, fill, and dashboard surfaces have no consistent UI foundation. The domain heart of ID-145 is not a document-processing problem, so most high-value surfaces (question list, match surfaces, workflow stepper) are custom builds; a bounded set of genuine document-processing surfaces (viewers, spatial citations, upload, editors) map onto Extend’s client-only document components. This spec fixes the behaviour of that bounded set plus the Streamdown markdown surfaces, so ID-145.19 builds on settled behaviour rather than improvising per surface.
gitnexus / in-tree orientation (verbatim, 15/07/2026):
- Incumbent markdown site (migrate target).
components/item-detail/content-renderer.tsx:1-8importsMarkdown, { type Components } from 'react-markdown'+remarkGfm from 'remark-gfm', with ahasMarkdown()gate andslugify()heading-id helper. It is the only react-markdown render site in the ID-145 scope; consumed transitively bycomponents/qa/qa-pair-renderer.tsx. - Adopted Streamdown pattern (reuse target).
components/okf/file-render-pane.tsx:37imports{ Streamdown, type Components } from 'streamdown'and (:41-43,:101) usesnormaliseInternalMdLinksForStreamdownfrom@/lib/okf/prepare-streamdown-contentso internal directory-relative links survive Streamdown’s bundledrehype-hardenlink-safety pass;:169-171renders<Streamdown components={markdownComponents}>. - Existing upload dropzone.
gitnexus_context({name:'FileDropzone'})→Function components/shared/file-dropzone.tsx:FileDropzone(52-192), withvalidateFile(69-86) +FileDropzoneProps(10-33); incoming/outgoing/processes all empty — a leaf composition surface, not an indexed execution flow. - Item document listing.
app/api/procurement/[id]/route.ts:GET(43-272) lists Supabase Storagetender-documents/<form_id>/— anListObjectsV2-shaped listing already. - Spatial citation/fill-slot rendering.
gitnexus_queryfor “markdown rendering / citation / bounding box / document viewer” returned no UI execution flows (processes: []; hits were pipeline-side Python tests + definitions only). Corroborates the grounding: spatial citation/fill-slot overlay is greenfield in-tree — no existing spatial-overlay flow to extend. The only citation UI today,components/source-document-detail/document-citations-panel.tsx, is a text-only list grouped by target kind (0 rows), not a spatial overlay.
Confirmed in-tree surfaces this spec builds on (grouped): item-page components/procurement/*
(procurement-forms-card.tsx, procurement-workflow-indicator.tsx, readiness-checklist.tsx,
question-navigator.tsx, procurement-context-provider.tsx, …), components/source-document-detail/*
(document-citations-panel.tsx, source-document-detail-client.tsx), components/qa/*
(qa-pair-renderer.tsx, qa-answer-display.tsx, …), and the existing components/reader/pdf-document.tsx
react-pdf viewer.
Figma: none provided. Design reference is Warm Meridian
(design/warm-meridian-implementation-spec.md) — semantic tokens only, never raw Tailwind
colours (components/CLAUDE.md F3), WCAG 2.1 AA (never colour alone for meaning).
Goals / Non-goals
Section titled “Goals / Non-goals”Goals. Define the user-visible behaviour of the Procurement document + markdown UI foundation so ID-145.19 can be built against settled behaviour.
Non-goals (explicitly out of scope, do not design here):
- Wider-platform rollout — Intelligence, Product Guides, Sales Proposals, and the shared document / Q&A / citation surfaces those application types will consume. A deliberate follow-on Task (RESEARCH §0). This spec is Procurement-domain baseline only.
- Anthropic backend answer-quality items (prompt-caching fix, structured-output consistency,
Files API) — carved to backlog
bl-460, not this UI chain (grounding §7). - DOCX/XLSX spatial fill-slot / citation overlay — out of v1; backlog
bl-459(PDF-convert-then-revert) (DR-064, grounding §4c). - AI-SDK Elements
workflowgraph canvas — rejected for the stepper (DR-067); reserved for a future branching-graph need. - Extend Schema Builder for the requirement catalogue / question slots — rejected (DR-065); reserved for a future JSON-extraction feature.
Behavior
Section titled “Behavior”Invariants are grouped by surface and labelled §<group><n> (e.g. §B2) for stable citation from
{147.3} TECH.md’s per-invariant proposed-changes mapping. References to ID-145 behaviour
invariants are always qualified as “ID-145 BI-n” to avoid collision with this document’s labels.
A. Multi-document item page (hybrid frame — DR-068)
Section titled “A. Multi-document item page (hybrid frame — DR-068)”-
§A1 — The item is the form. The item page presents a single
form_instancesrow as a form-as-page: a header carrying the form’s identity —reference_number,issuing_organisation,deadline, currentworkflow_state,estimated_value— followed by the form’s questions, documents, and workflow surfaces. There is no separate “bid” container around the form (ID-145 BI-1/BI-4). -
§A2 — Custom domain frame, not a Finder. The page uses a custom domain-shaped frame (header
- optional engagement rail + Documents tab), not Extend’s File System / Finder as the top-level surface (DR-068). No folder/containment metaphor is presented: the user never sees the form’s documents arranged as a filesystem tree.
-
§A3 — Engagement sibling rail shown only when grouped. When the form belongs to an engagement group (
engagement_group_idset), the page shows a read-only sibling-forms link rail presenting the lineage (e.g. PSQ → ITT → tender). When the form is ungrouped (engagement_group_idnull), no rail is shown. -
§A4 — Grouping is a link, not a container (ID-145 BI-28/29). The rail is read-only lineage: a grouped form’s URL, workflow state, questions, and outcome are identical whether viewed grouped or not. Grouping never rolls up or aggregates state across siblings, and never presents an engagement-level outcome or win-rate (ID-145 BI-28/29; owner ratification S470 — no stored roll-up).
-
§A5 — Documents tab, split by role. The Documents tab lists the item’s documents in two labelled groups: FORM SOURCE (the form’s own uploaded document(s)) and REFERENCE / EVIDENCE (attached supporting material, e.g. a CV). The split is by text label, never colour alone. The form-owns-its-documents half lists the existing
tender-documents/<form_id>/storage contents (see Problem,[id]/route.ts:GET) with no new store. -
§A6 — Labelled reference/evidence attachment (v1, DR-068). A user can attach a labelled reference/evidence document at form level or engagement level, choosing its role (form-source vs reference/evidence). The attachment then appears in the correct Documents-tab group. The exact persistence shape (a new store keyed by nullable
form_instance_id+ nullableengagement_group_id+ aroleenum +storage_path) is a{147.3}TECH choice; the behaviour is: attach → label → see it grouped correctly. -
§A7 — Deleting an engagement does not delete a form’s documents. Because an engagement is a link and
form_instances.engagement_group_idis nullable (ON DELETE SET NULL), removing the engagement grouping leaves each form and its own documents intact; only the engagement-level link (and any engagement-level attachment) is affected. -
§A8 — Progressive disclosure. A single ungrouped form with no reference/evidence attachments collapses to today’s simple document list (no rail, no empty second group heading). The engagement rail and the two-group split appear only when there is something to show.
-
§A9 — Selecting a document previews it. Selecting a document in the Documents tab opens it in the viewer pane (§B), navigable via an Extend Document-Viewer-Sidebar of thumbnails; the grid / sidebar thumbnails are supplied by us (Extend File-Thumbnail renders only the shell / loading / error state, never generating the preview image itself).
B. Document viewers (PDF / DOCX / Excel / CSV — DR-063 broad adoption)
Section titled “B. Document viewers (PDF / DOCX / Excel / CSV — DR-063 broad adoption)”-
§B1 — Viewer matches document type. PDF, DOCX, XLSX, and CSV documents each render in the viewer for that type (Extend PDF / DOCX / XLSX / CSV Viewer). The correct viewer is selected from the document’s type; the same document type renders in the same viewer wherever it appears on the item page.
-
§B2 — PDF is the standard rendered viewer. PDF form and tender documents render inline with zoom and toolbar controls. Adopting the Extend PDF Viewer as the standard is the broad-adoption posture (DR-063); the user sees a consistent PDF experience across the item page.
-
§B3 — DOCX and XLSX gain real client viewers.
.docxforms (SQ/ITT) and.xlsxprice schedules render as formatted documents / grids (not raw text and not a download-only link). This fills a genuine in-tree gap (the existingdocx/mammoth/xlsxpackages are parsers/converters, not viewers). -
§B4 — Viewers respect the app light/dark theme. The DOCX and XLSX viewers own explicit dark-mode state (Extend requires
isDark+onIsDarkChange); the viewer’s light/dark appearance follows the app theme rather than diverging from it. -
§B5 — Document backgrounds render as-is first (DR-063). Document viewers install as-is with their default (white) document backgrounds — preferred for reading documents — and are migrated to Warm Meridian semantic tokens incrementally, not re-themed on day one (DR-063, grounding §10). White document backgrounds are an accepted v1 state, not a defect.
-
§B6 — Loading and error states are explicit. Each viewer shows a loading state while the document fetches, and a soft error state with a retry affordance if the document fails to load — never a blank pane. UK English error copy; non-colour-only signalling.
-
§B7 — Unsupported type falls back to download, not a broken render. A document whose type has no viewer (or a corrupt file no viewer can render) shows an explicit “cannot preview” message with a download fallback affordance, never a broken or empty render.
C. Spatial fill-slot review (DR-064 — PDF-only v1)
Section titled “C. Spatial fill-slot review (DR-064 — PDF-only v1)”-
§C1 — Detected fill-slots overlay the PDF page. For a PDF form with detected mechanical fill-slots (
form_instance_fields), each slot’s page location is shown as a bounding-box highlight overlaid on the rendered PDF page, positioned over the actual field region (page-accurate). -
§C2 — Slot list ↔ overlay are linked. Selecting a slot in the slot list scrolls to and highlights its box on the page; selecting a highlighted box on the page selects the corresponding slot in the list. The correspondence is never colour-only — each slot/box carries a text label or icon.
-
§C3 — Fill status is visible per slot. Each slot shows its status (e.g. unfilled / mapped / filled) as a text label or icon, not colour alone (WCAG 2.1 AA).
-
§C4 — PDF-only; DOCX/XLSX degrade honestly. Spatial fill-slot overlay is PDF-only in v1 (DR-064). For a DOCX/XLSX form, the slots are presented as a list without a spatial overlay, with a note that spatial review is available for PDFs only (backlog
bl-459). The UI never draws a box that does not correspond to a real field region — a missing or unresolvable geometry degrades to the list, never a misaligned box.
D. Bounding-box citations (Option B — DR-064)
Section titled “D. Bounding-box citations (Option B — DR-064)”-
§D1 — PDF citations render as overlays. A
q_a_paircitation (ID-145 BI-37) that anchors to a span in a PDF document renders as a highlighted region overlaid on the rendered PDF at the cited location. Selecting the citation in the citations panel scrolls the PDF to and highlights the cited region; selecting a highlighted region on the page surfaces its citation. Correspondence is never colour-only. -
§D2 — Exact primary path, approximate fallback. For text-based PDFs (most UK procurement forms) the highlight sits exactly over the cited text span. For scanned / flat / no-text-layer PDFs, an approximate highlight region is shown. The distinction is not surfaced as an error; the fallback is a best-effort region the user can visually confirm.
-
§D3 — Unresolvable coordinates degrade to the text entry, never a misplaced box. A citation whose on-page coordinates cannot be resolved (neither primary nor fallback yields a mappable region) is shown as a text-anchored entry in the citations panel, never a box drawn at a wrong location.
-
§D4 — DOCX/XLSX citations stay text-anchored. Citations against DOCX/XLSX documents are out of the spatial-overlay v1 scope; they render in the existing citations panel (
document-citations-panel.tsx) as text-anchored list entries grouped by target kind (Answers / References / Source documents / Concepts), text+icon labelled, never colour-only. -
§D5 — Honest empty state. When a document has no citations, the citations panel shows an honest empty state (today’s 0-row behaviour), not a mislabelled or spurious entry.
E. Upload surface (bind to the hardened backend — ID-145 BI-9 / DR-014 / DR-063)
Section titled “E. Upload surface (bind to the hardened backend — ID-145 BI-9 / DR-014 / DR-063)”-
§E1 — Upload affordance with client validation. The upload surface (Extend File Upload / PDF Dropzone shell) accepts files by drag-drop and click-to-select, surfacing client-side type / size / count / duplicate validation inline before submission.
-
§E2 — Upload goes to the existing hardened backend, unchanged. The actual upload is handled by the existing hardened backend (ID-145 BI-9 / DR-014): magic-byte content sniffing, 50 MB cap, rate-limiting — all unchanged. The Extend shell contributes only the client affordance; its
onFilesAcceptedcallback binds to that backend. No new upload backend is introduced (Extend ships zero backend — DR-063). -
§E3 — Uploading a form creates the form-first item (ID-145 BI-9). Uploading a form document creates the form-first item (the uploaded form is the item), consistent with ID-145 BI-9; this behaviour must not regress.
-
§E4 — Progress, success, and honest rejection. The surface shows upload progress and a success state; a rejected file (wrong type, oversize, rate-limited) shows the backend’s actual reason, not a generic failure. Error copy is UK English and non-colour-only.
F. DOCX/Excel editor + forked e-signature (DR-066)
Section titled “F. DOCX/Excel editor + forked e-signature (DR-066)”-
§F1 — In-place edit / fill of missing answers (v1). For a DOCX/XLSX document, an admin/editor user has an affordance to manually edit the document and to fill a missing answer in place (Extend DOCX/Excel Editor, experimental). Edits are persisted.
-
§F2 — Fill mechanism is deferred to TECH. How an answer is written (Claude-side draft vs in-editor manual entry — the R5 decision) is a
{147.3}TECH choice, not fixed here. The behaviour fixed here: the user has an edit affordance and a fill-a-missing-answer affordance, and the result persists. -
§F3 — E-signature drives our fields and persists a signed document. For a form that requires a signature, an admin/editor user can place and complete signature fields (driven from our data) and the completed result is persisted as a signed PDF. (Extend’s E-Signature block exposes only
file?:stringwith no callbacks/persistence, so the surface is a fork that adds field-driving + persistence — DR-066; the fork mechanics are TECH’s, the behaviour is: signature fields appear, can be signed, and the signed document is saved.) -
§F4 — Editing and signing are admin/editor-gated. Edit and signature affordances are available only to admin/editor roles; reviewer/viewer roles see the document read-only (ID-145 BI-47 — mutations admin/editor-gated).
-
§F5 — Experimental editor degrades to read-only, never blank. Because the editors are experimental maturity, an editor that fails to initialise shows a soft error and falls back to the read-only viewer for that document, never a blank pane.
G. Workflow-state stepper (custom Warm Meridian — DR-067)
Section titled “G. Workflow-state stepper (custom Warm Meridian — DR-067)”-
§G1 — Linear 10-state stepper, custom component. The workflow surface presents the 10-state
workflow_statemachine (ID-145 BI-13) as a linear ordered stepper with a current-state badge. It is a custom Warm Meridian component (lucide icons + semantic tokens), not the AI-SDK Elementsworkflowgraph canvas (DR-067). -
§G2 — Never colour-only. Each state — completed, current, upcoming — carries a text label or icon meaning; state is never signalled by colour alone (WCAG 2.1 AA). Current state is distinctly marked (badge + label), not by colour alone.
-
§G3 — Only valid transitions offered (ID-145 BI-18). The transition control offers only valid next states for the current state; an attempted invalid jump is refused with a surfaced, readable reason (not a silent no-op).
-
§G4 — Deadline / outcome shown non-colour-only. Deadline, submission date, issuing organisation, and outcome shown alongside the stepper each carry a text label or icon; an overdue deadline or a terminal outcome is never signalled by colour alone.
H. Requirement / question editors (custom — DR-065)
Section titled “H. Requirement / question editors (custom — DR-065)”-
§H1 — Custom requirement-catalogue editor. The reusable requirement catalogue (
form_requirement_templates, ID-145 BI-24) is edited through a custom editor exposing its domain fields:requirement_type,requirement_text,matching_keywords,matching_guidance,is_mandatory,word_limit_guidance,section_ref,sector_applicability, and domain/subtopic classification. Extend Schema Builder is not used for this (it carries no domain metadata — DR-065). -
§H2 — Custom question/answer-slot editor. The answerable question/answer slots (
form_questions/form_responses, ID-145 BI-20) are edited through a custom editor exposingquestion_text,word_limit,evaluation_weight, assignee,review_status, and version. Schema Builder is not used here either. -
§H3 — Catalogue writes are admin/editor-gated. Creating or editing catalogue rows is restricted to admin/editor roles (ID-145 BI-47); reviewer/viewer roles see them read-only.
-
§H4 — Schema Builder is reserved, not offered. Schema Builder is not presented anywhere in the requirement/question surfaces; it is reserved for a future “extract typed fields from an uploaded RFP” JSON-extraction feature only (DR-065).
I. Streamdown markdown surfaces (DR-040)
Section titled “I. Streamdown markdown surfaces (DR-040)”-
§I1 — All Procurement markdown renders via Streamdown. Every markdown-rendering surface in the Procurement scope uses Streamdown (DR-040). New surfaces use it natively; the same markdown input renders consistently across surfaces.
-
§I2 — Incumbent react-markdown migrates (folded into ID-145.19). The single incumbent react-markdown site —
content-renderer.tsx, consumed byqa-pair-renderer.tsx— is migrated to Streamdown (DR-040 “new surfaces first”; owner: fold the migration into ID-145.19). Rendered output for existing content (headings, links, lists, bold, GFM tables, blockquotes, code) must not visibly regress versus the react-markdown output it replaces. -
§I3 — Internal relative links survive hardening. The migration reuses the existing
normaliseInternalMdLinksForStreamdownshim (lib/okf/prepare-streamdown-content.ts) so internal directory-relative links survive Streamdown’s bundledrehype-hardenlink-safety pass; links from unexpected external origins remain hardened (Streamdown default), and the user sees the full URL before navigating an external link. -
§I4 — Streamed answers render natively. Streamed answer surfaces (ID-145 BI-40/41 SSE draft-stream) render with Streamdown natively: a streaming caret indicator while streaming, and clean rendering of unterminated/partial markdown (an unclosed code block or bold span renders without breaking mid-stream). This is the DR-040 “new surface” case and needs no migration.
J. Theming, icons, accessibility (cross-cutting — DR-063)
Section titled “J. Theming, icons, accessibility (cross-cutting — DR-063)”-
§J1 — One icon family (lucide). All adopted surfaces present a single, consistent icon family (lucide); vendored Extend components are re-pointed from Hugeicons to lucide at vendor-in (DR-063). The user never sees a visual mix of two icon families in shared chrome.
-
§J2 — Shared chrome matches the app; viewers may keep internal primitives. Shared chrome the user sees app-wide (buttons, menus) uses the app’s existing shadcn primitives, so adopted surfaces are visually consistent with the rest of the app. Self-contained document viewers may retain their internal (Coss UI) primitives, which are rarely themed and seen only inside the viewer (DR-063).
-
§J3 — Semantic tokens only. All Procurement UI code uses Warm Meridian semantic tokens, never raw Tailwind colours (
components/CLAUDE.mdF3). Adopted Extend source is re-pointed to bare semantic tokens as the incremental adaptation (§B5), not a day-one gate. -
§J4 — WCAG 2.1 AA across every surface. No surface signals meaning by colour alone — states, citations, fill-slots, workflow, validation, and outcomes each carry a text label or icon. UK English throughout (DD/MM/YYYY, “colour”, “organisation”).
K. Scope boundary and gating relationship
Section titled “K. Scope boundary and gating relationship”-
§K1 — Procurement-domain baseline only. This foundation is Procurement-domain baseline only. It must not design the wider-platform rollout (Intelligence, Product Guides, Sales Proposals, or the shared document / Q&A / citation surfaces) — that is a deliberate follow-on Task (RESEARCH §0; Non-goals).
-
§K2 — This chain gates ID-145.19. The
{147.2}/{147.3}chain gates ID-145.19 (item-page UI surfaces). Concretely, the following land in ID-145.19 built on this foundation: the item-page hybrid frame (§A) replacing the composition shells (source-document-detail-client.tsx,procurement-forms-card.tsxremoval per ID-145 BI-4), the document/citation/fill surfaces (§B/§C/§D), the workflow stepper (§G), and thecontent-renderer.tsx→ Streamdown migration (§I2). ID-147 does not itself ship the ID-145.19 wiring; it defines the behaviour ID-145.19 consumes. -
§K3 — ID-147 owns the first registry install. Adopted Extend components arrive in-tree as copy-in shadcn source — there is no npm runtime dependency on Extend itself; ID-147 owns the first
extend-hq/uiregistry install (DR-063). (The DOCX/XLSX render engines@extend-ai/react-docx/@extend-ai/react-xlsxare the exception — genuine runtime deps pulled by those viewers.) The install-slice mechanics are{147.3}TECH’s; the behavioural consequence is that adopted surfaces are our own adaptable source, re-themable to Warm Meridian.
Empirical grounding note
Section titled “Empirical grounding note”The external-library behavioural facts this spec relies on (Extend components are client-only
copy-in shells with the prop shapes cited; HighlightArea is %-0–100 top-left; commonforms
computes then discards normalised-top-left geometry; Streamdown streams partial markdown) were
empirically verified in the substrate’s verification block (id-147-empirical-grounding.md §12,
13/07/2026 — all CONFIRMED) and RESEARCH §8. Streamdown is installed and in live use
(package.json streamdown ^2.5.0; file-render-pane.tsx:37) — PRESENT. Extend is
ABSENT by design (unshipped registry) — its import-and-call verification belongs to the
{147.3} install slice, not this PRODUCT spec (RESEARCH §8); this is not an ABSENT verification
blocker. No SIGNATURE_DRIFT / BEHAVIOUR_DRIFT surfaced against the pinned Streamdown version.
End of PRODUCT — ID-147 {147.2}. Output:
specs/id-147-extend-streamdown-ui-foundation/PRODUCT.md. Not committed here. Feeds {147.3}
TECH.md (per-invariant proposed changes against §A1–§K3); gates ID-145.19.