Skip to content

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) and RESEARCH.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.

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).

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-8 imports Markdown, { type Components } from 'react-markdown' + remarkGfm from 'remark-gfm', with a hasMarkdown() gate and slugify() heading-id helper. It is the only react-markdown render site in the ID-145 scope; consumed transitively by components/qa/qa-pair-renderer.tsx.
  • Adopted Streamdown pattern (reuse target). components/okf/file-render-pane.tsx:37 imports { Streamdown, type Components } from 'streamdown' and (:41-43, :101) uses normaliseInternalMdLinksForStreamdown from @/lib/okf/prepare-streamdown-content so internal directory-relative links survive Streamdown’s bundled rehype-harden link-safety pass; :169-171 renders <Streamdown components={markdownComponents}>.
  • Existing upload dropzone. gitnexus_context({name:'FileDropzone'})Function components/shared/file-dropzone.tsx:FileDropzone (52-192), with validateFile (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 Storage tender-documents/<form_id>/ — an ListObjectsV2-shaped listing already.
  • Spatial citation/fill-slot rendering. gitnexus_query for “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. 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 workflow graph 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.

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)”
  1. §A1 — The item is the form. The item page presents a single form_instances row as a form-as-page: a header carrying the form’s identity — reference_number, issuing_organisation, deadline, current workflow_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).

  2. §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.
  3. §A3 — Engagement sibling rail shown only when grouped. When the form belongs to an engagement group (engagement_group_id set), 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_id null), no rail is shown.

  4. §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).

  5. §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.

  6. §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 + nullable engagement_group_id + a role enum + storage_path) is a {147.3} TECH choice; the behaviour is: attach → label → see it grouped correctly.

  7. §A7 — Deleting an engagement does not delete a form’s documents. Because an engagement is a link and form_instances.engagement_group_id is 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.

  8. §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.

  9. §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)”
  1. §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.

  2. §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.

  3. §B3 — DOCX and XLSX gain real client viewers. .docx forms (SQ/ITT) and .xlsx price schedules render as formatted documents / grids (not raw text and not a download-only link). This fills a genuine in-tree gap (the existing docx/mammoth/xlsx packages are parsers/converters, not viewers).

  4. §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.

  5. §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.

  6. §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.

  7. §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)”
  1. §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).

  2. §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.

  3. §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).

  4. §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)”
  1. §D1 — PDF citations render as overlays. A q_a_pair citation (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.

  2. §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.

  3. §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.

  4. §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.

  5. §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)”
  1. §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.

  2. §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 onFilesAccepted callback binds to that backend. No new upload backend is introduced (Extend ships zero backend — DR-063).

  3. §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.

  4. §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)”
  1. §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.

  2. §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.

  3. §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?:string with 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.)

  4. §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).

  5. §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)”
  1. §G1 — Linear 10-state stepper, custom component. The workflow surface presents the 10-state workflow_state machine (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 Elements workflow graph canvas (DR-067).

  2. §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.

  3. §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).

  4. §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)”
  1. §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).

  2. §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 exposing question_text, word_limit, evaluation_weight, assignee, review_status, and version. Schema Builder is not used here either.

  3. §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.

  4. §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).

  1. §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.

  2. §I2 — Incumbent react-markdown migrates (folded into ID-145.19). The single incumbent react-markdown site — content-renderer.tsx, consumed by qa-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.

  3. §I3 — Internal relative links survive hardening. The migration reuses the existing normaliseInternalMdLinksForStreamdown shim (lib/okf/prepare-streamdown-content.ts) so internal directory-relative links survive Streamdown’s bundled rehype-harden link-safety pass; links from unexpected external origins remain hardened (Streamdown default), and the user sees the full URL before navigating an external link.

  4. §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)”
  1. §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.

  2. §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).

  3. §J3 — Semantic tokens only. All Procurement UI code uses Warm Meridian semantic tokens, never raw Tailwind colours (components/CLAUDE.md F3). Adopted Extend source is re-pointed to bare semantic tokens as the incremental adaptation (§B5), not a day-one gate.

  4. §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”).

  1. §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).

  2. §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.tsx removal per ID-145 BI-4), the document/citation/fill surfaces (§B/§C/§D), the workflow stepper (§G), and the content-renderer.tsx → Streamdown migration (§I2). ID-147 does not itself ship the ID-145.19 wiring; it defines the behaviour ID-145.19 consumes.

  3. §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/ui registry install (DR-063). (The DOCX/XLSX render engines @extend-ai/react-docx / @extend-ai/react-xlsx are 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.

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.