Skip to content

ID-147 {147.4} PLAN — Procurement UI foundation: decomposition of the remaining Lane B work into id-147 foundation + {145.19}-wave implementation subtasks

ID-147 {147.4} PLAN — Procurement UI foundation decomposition

Section titled “ID-147 {147.4} PLAN — Procurement UI foundation decomposition”

Type: PLAN ({N.4}). Decomposition of the ratified {147.2} PRODUCT (53 invariants §A1–§K3) + {147.3} TECH (§1–§7 proposed changes; DR-075 route ruling) into implementable subtasks. Fresh Planner context. Authored 16/07/2026. UK English throughout. Track: /procurement (ca-s435-procurement-baseline); ledger writes on MAIN only, nothing committed here.

Substrate (read before this plan): ratified PRODUCT.md, TECH.md, id-147-empirical-grounding.md; DR-063…068 + DR-075; the {145.19} record + journal.

What this plan owns. The decomposition of the REMAINING Lane B work only — the id-147 foundation mechanics ({147.5+}) plus the {145.19} UI half. It does not re-propose the route re-key API halves (groups A–C) that ALREADY LANDED in this branch (see §Current-state reconciliation). The parent applies FINAL Subtask numbering on MAIN; this plan RECOMMENDS the per-subtask landing (id-147 vs {145.19}-wave) and the wave structure.

ID-147 establishes the Procurement UI foundation on Extend copy-in components + Streamdown, and gates ID-145.19 (item-page UI). The API/route-re-key half (DR-075 groups A–C) is done; what remains is (A) the id-147 foundation mechanics — Extend registry install, reference/evidence attachment store, fill-slot geometry plumbing, bounding-box citation derivation, DOCX/XLSX editor + forked e-signature, custom stepper + custom editors — and (B) the {145.19} UI half that consumes that foundation — item-page hybrid frame, state-stepper wiring, question/coverage surfaces, grouping lineage, drafting re-point, the content-renderer.tsx Streamdown migration, and the dead workspace-era UI removal.

The decomposition is 21 subtasks across two landing groups (14 id-147 foundation + 7 {145.19}-wave), sequenced in 4 waves with a HARD STOP install gate at Wave 0.

Context — current-state reconciliation (LOAD-BEARING: do NOT re-propose landed work)

Section titled “Context — current-state reconciliation (LOAD-BEARING: do NOT re-propose landed work)”

Verified live (16/07/2026): id-147 in_progress, id-145 in_progress, id-145.19 in_progress, id-145.18 done, id-145.17 done (DR-002 — statuses read from the ledger, not from done-task journals).

Already landed in this branch (via the #119 merge) — OUT of this plan’s scope:

  1. Route groups A–C API halves (checker-gated PASS_WITH_NOTES):
    • c43031e8 (group A+C): [id]/route.ts re-keyed to form_instances flat reads; get_procurement_rollup RPC + child-forms list DROPPED (S470 no-stored-roll-up); PATCH/DELETE write the PK row directly; computeWorkflowTransition extracted+consolidated (outcome delegates); outcome/integrate collapsed; forms/route.ts RETIRED 410.
    • ec04e2db (group B): templates/route.ts RETIRED; [templateId] detail + field CRUD re-pathed to [id]/fields/*; completions download re-keyed + tender-documents/templates bucket fallback; get_template_summary dropped.
    • f141b907 (gate-groups-ac testfix): combined status+outcome PATCH fail-fast.
    • ⇒ The route/API re-key is DONE. This plan folds in only the UI-half collateral those retirements created (see §Collateral, below).
  2. W1 batch PUSHED to staging + database.types.ts REGENERATED (be8cf6c6, 1ef9757d). ⇒ TECH §7’s “sequence geometry + attachment migrations AFTER the W1 push + type-regen” precondition is MET. BUT (Lane B constraint): the geometry + attachment migrations are AUTHORED IN-BRANCH ONLY, NEVER pushed from this lane (another lane owns DB pushes). Every authored migration is flagged migration_authored = a parent-sequenced push. database.types.ts is Read-DENIED by the harness — subtasks cite migrations for shape, never the types file.
  3. {145.18} (item-page data/identity re-anchor) DONE; {145.17} (matches) DONE ⇒ {145.19} data deps satisfied.

UI-half collateral (from the group-A/B retirements) folded into this plan (subtask W1): components/procurement/procurement-forms-card.tsx (now fetches a 410 — gitnexus_context confirms incoming:{}, no live callers, dead since {145.18}); components/coverage/template-upload.tsx (POSTs the retired list route → 404; workspace-era “add another template” step with NO form-first successor per BI-1/DR-068); page.tsx handleDownloadOriginal dead code + initial list-fetch degradation.

Code-intelligence orientation (verbatim, 16/07/2026)

Section titled “Code-intelligence orientation (verbatim, 16/07/2026)”

Outputs quoted (not paraphrased) so the Checker can verify the orientation step. Repo disambiguated to /Users/liamj/Documents/development/procurement (two canonical repos are registered).

  • gitnexus_context({name:'PdfDocument', file_path:'components/reader/pdf-document.tsx'})Function:components/reader/pdf-document.tsx:PdfDocument (53-358); incoming.calls:[BinarySide (components/diff/binary-diff-pane.tsx)]; outgoing.calls:[goToPage, Button, Input, Separator, Skeleton, createClient(lib/supabase/client.ts)]; processes:[]. The §C/§D overlay host already owns goToPage (the “scroll-to-and-highlight” hook) and uses our components/ui primitives.
  • gitnexus_context({name:'computeWorkflowTransition'})Function:app/api/procurement/[id]/route.ts:computeWorkflowTransition (77-134); incoming.calls:[outcome/route.ts:POST, [id]/route.ts:PATCH]; outgoing.calls:[canTransition(lib/domains/procurement/procurement-workflow.ts), validateFormOutcome(lib/validation/schemas.ts)]; processes:[]. Confirms the group-A/C consolidation LANDED — the §G3 stepper transition backend exists; the custom stepper (147-L) reads through it, it is NOT rebuilt.
  • gitnexus_context({name:'normaliseInternalMdLinksForStreamdown'})Function:lib/okf/prepare-streamdown-content.ts:normaliseInternalMdLinksForStreamdown (70-94); incoming.calls:[preparedContent(components/okf/file-render-pane.tsx)]; outgoing.calls:[stripMdSuffix, resolveInternalMdLink(lib/okf/resolve-internal-link.ts)]. The §I3 hardening shim reuse target — confirmed present and used by the Streamdown pattern.
  • gitnexus_query('content-renderer markdown streamdown migration heading id injection')processes:[]; definitions: Function:components/item-detail/content-renderer.tsx:ContentRenderer (88-136), isMarkdown (90), Function:components/okf/file-render-pane.tsx:FileRenderPane (57-174). content-renderer.tsx is the only react-markdown site in scope (§I2 migrate target); FileRenderPane is the Streamdown reuse pattern.
  • gitnexus_query('analyse_form worker lane pdf orchestrator ExtractedField geometry PdfDetectedField') → definitions: scripts/bid_worker.py:analyse_form_job (517-663) + _write_form_instance_fields (487-514); scripts/cocoindex_pipeline/form_extractors/orchestrator.py:_pdf_result_to_extracted_form (110-161) + extract_form_structure (164-239); scripts/cocoindex_pipeline/form_extractors/pdf.py:detect_pdf_fields (265-378) + PdfDetectedField (87-130). The geometry writer lane: pdf.py detects, orchestrator.py adapts, bid_worker.py:_write_form_instance_fields persists to form_instance_fields.
  • gitnexus_context({name:'ProcurementFormsCard'})Function:components/procurement/procurement-forms-card.tsx:ProcurementFormsCard (68-237); incoming:{} (no live callers); outgoing.calls:[useProcurementFormTypes, FormTypePicker, outcomeLabel, ProcurementWorkflowBadge, Button, Dialog*, formatDateUK, cn]. Confirms the dead workspace-era card is safe to remove (empty upstream blast radius).

Per §K2 (this chain gates ID-145.19) and DR-075, the split follows one rule: id-147 ships foundation mechanics that ship whole; {145.19} ships the item-page wiring/consumption of them.

LandingSubtasksNature
id-147 ({147.5+})A install gate · B vendor-in · C attachment migration · D attachment API+cleanup · E geometry pipeline · F geometry migration+writer · H overlay primitive+zod · I citation derivation · J editor wiring+fill · K e-sig fork · L custom stepper · M requirement editor · N question editor · O viewer/upload state contractsSelf-contained foundation mechanics (backend plumbing + reusable components) the item page consumes.
{145.19}-wave (ID-145 siblings)W1 dead-code removal · W2 hybrid frame + Documents tab + attachments read-fold · W3 stepper wiring + inline states · W4 questions/coverage + zero-candidate manual-answer · W5 grouping lineage + drafting re-point · W6 §I2 content-renderer→Streamdown + snapshot · W7 fill-slot + citation overlay UIItem-page surfaces that wire the foundation onto the {145.18} shape + DR-075 group-A detail.

Why the group-A read edits (attachments read-fold, group-A DELETE storage remove()) land in {145.19}, not id-147: they edit [id]/route.ts, a {145.19}-owned file (DR-075 group-A owner = {145.19}); the id-147 attachment subtask (D) owns only the NEW [id]/attachments route + its DELETE cleanup + the orphan-sweep backstop. This keeps file-ownership disjoint across the task boundary.

The sibling-only dependency decision (structural, parent must apply)

Section titled “The sibling-only dependency decision (structural, parent must apply)”

The id-147 foundation → {145.19} consumption relationship is a task-level chain-gate (ID-145.19 depends on ID-147), already ratified (§K2, DR-075). It is NOT encoded as a subtask dependency edge — that would be a cross-task sibling violation. Instead:

  • Every id-147 subtask’s dependencies reference only other id-147 local indices.
  • Every {145.19}-wave subtask’s dependencies reference only other {145.19}-wave local indices.
  • The cross-task gate (a {145.19}-wave UI subtask consuming an id-147 foundation subtask) is expressed at the wave level (Wave 3 begins after the id-147 foundation waves complete), and each consuming subtask’s details notes “task-gated on ID-147 foundation (not a subtask edge)”.

This is NOT a boundary escalation. The task boundary is correct and deliberate: ID-147 is the foundation, ID-145.19 the consumer. No proposed subtask needs a genuine cross-task subtask edge, so no split/merge is forced. (Considered and rejected the escalation per the sibling-only forcing function — the ratified chain-gate is the intended shape.)

Wave 0 — STOP GATE + parallel non-Extend foundation.

  • 147-A install gate (HARD STOP). Register the @extend/* registry in components.json; shadcn add ONE viewer; confirm @extend-ai/react-docx + @extend-ai/react-xlsx resolve at the pinned versions shadcn add pulls; import-and-render smoke; record PRESENT/ABSENT/SIGNATURE_DRIFT with date + version. On ABSENT/DRIFT → STOP and escalate (do not proceed to 147-B). This is the deferred §Empirical check and the distinct early checkpoint everything Extend-dependent gates on.
  • Parallel non-Extend track (no Extend dependency — may begin immediately): 147-C (attachment migration), 147-E (geometry pipeline — Python), 147-L (custom stepper), 147-M/147-N (custom editors); {145.19}-wave 145W-1 (dead-code removal), 145W-6 (Streamdown content-renderer migration — Streamdown is already in-tree). These consume NO Extend source and are not gated on 147-A.

Wave 1 — Extend vendor-in + backend continue. 147-B (vendor-in the full v1 set + provenance manifest + vendor policy; deps A). Parallel: 147-D (attachment API + cleanup; deps C), 147-F (geometry migration + writer; deps E).

Wave 2 — Extend-consuming foundation. 147-H (overlay primitive + geometrySchema; deps B), 147-I (citation derivation B1+B2; deps B), 147-J (DOCX/XLSX editor wiring + fill; deps B), 147-K (E-Signature fork; deps B+C), 147-O (viewer/upload state contracts §B6/§B7/§E1/§E3/§E4; deps B). All parallel (disjoint files).

Wave 3 — {145.19}-wave item-page consumption (task-gated on id-147 foundation Waves 0–2). 145W-2 (hybrid frame; deps 145W-1) establishes the child-component structure; then 145W-3, 145W-4, 145W-5, 145W-7 run in parallel (deps 145W-2; each owns distinct child-component files). 145W-6 already ran in Wave 0.

Checkpoint after each wave: bun run test green on touched files; python3 -m pytest scripts/tests/ for the geometry lane; lint clean; no raw Tailwind colours; UK English; WCAG 2.1 AA non-colour-only signalling on every state.

  • app/procurement/[id]/page.tsx is a shared-file serialisation point across the item-page subtasks. 145W-1 (remove dead code) precedes 145W-2 (frame); 145W-2 establishes distinct child-component files so 145W-3/4/5/7 are parallel-safe thereafter.
  • components.json + the provenance manifest are owned solely by 147-A (registry + one viewer) then 147-B (the rest). 147-J/K touch only their own wiring/fork files, never components.json.
  • Python geometry lane (pdf.py/orchestrator.py then bid_worker.py + migration) is a self-contained parallel track disjoint from all TS work.
  • Two authored-not-pushed migrations: 147-C (<ts>_id147_form_attachments.sql) and 147-F (<ts>_id147_form_instance_fields_geometry.sql). Authored in-branch, flagged for a parent-sequenced push; NEVER pushed from Lane B.

Each subtask’s full dispatch brief (file-ownership, TECH/invariant refs, code-intel discipline block, three-part grounding block, testStrategy) lives in the proposed-subtasks YAML (lane-b-proposed-subtasks.yaml). Summary:

  • 147-A — Extend registry install + HARD STOP import-and-render gate. components.json registries block; shadcn add one viewer; verify the two render engines resolve; record PRESENT/ABSENT/DRIFT; STOP on ABSENT/DRIFT. TECH §1(1-2); §K3.
  • 147-B — Vendor-in the v1 component set + provenance manifest + vendor policy. shadcn add the PDF/DOCX/XLSX/CSV viewers, Bounding Box Citations/HumanReviewPanel, File Upload + PDF Dropzone, Document Viewer Sidebar + File Thumbnail, DOCX/Excel Editor + E-Signature shells under a domain subdir (never components/ root); re-point Hugeicons→lucide; re-wire shared chrome to components/ui (viewers may keep Coss UI internals); install-as-is theming (white backgrounds accepted); write a checked-in provenance manifest (component name + registry version + add date). Finder NOT installed. TECH §1(3-4); §B1–B5/§A9/§E1/§J1–J3/§K3.
  • 147-C — form_attachments migration (authored-not-pushed). Table + scope-XOR CHECK + form_source_scoped CHECK + indexes + RLS mirroring engagement_groups (SELECT USING(true); write get_user_role() IN admin,editor; no anon grant). TECH §2; §A5/A6/A7.
  • 147-D — Attachment write API + storage cleanup. NEW POST /api/procurement/[id]/attachments (form-level; role in body; engagement-level via engagement_group_id body) binding onFilesAccepted to the hardened BI-9 backend; storage under tender-documents/...; DELETE with best-effort storage remove(); periodic orphan-sweep backstop. NOT in proxy.ts publicRoutes; authFailureResponse(auth); sb()/tryQuery(); TanStack keys. TECH §2; §A6/A7/§E2.
  • 147-E — Fill-slot geometry pipeline (Python). pdf.py: capture page_width + MediaBox origin
    • page.rotation; normalise bbox into displayed post-rotation top-left page-fraction space; add a geometry dict to PdfDetectedField. orchestrator.py: carry geometry through into ExtractedField (new nullable field); keep the DOCX/XLSX table_index/row_index reading-order path. TECH §3; §C1/C4.
  • 147-F — Geometry migration + writer (authored-not-pushed migration). <ts>_id147_form_instance_fields_geometry.sql adds geometry jsonb nullable; bid_worker.py:_write_form_instance_fields persists geometry. TECH §3; §C1/C3/C4.
  • 147-H — Spatial overlay primitive + geometrySchema zod. Reusable component rendering HighlightArea (%-0–100 top-left, left/top/width/height × 100, no flip) boxes over PdfDocument using the vendored Bounding Box Citations + PdfDocument.goToPage + a shared selection id + a text/icon label per box (never colour-only); geometrySchema zod (left/top/width/height ∈ [0,1], page int ≥ 1, rotation ∈ {0,90,180,270}) — parse-fail degrades to §C4, never a box. TECH §3/§4; §C1–C4/§D1.
  • 147-I — Bounding-box citation coordinate derivation (B1+B2). B1 text-layer getClientRects primary (resolve cited_text + cited_start/cited_end → on-page rects against react-pdf TextLayer → HighlightArea, exact, deterministic); B2 Claude-vision fallback — MUST self-rasterise pages worker/client-side + pre-resize (absolute pixel coords ÷ resized dims → HighlightArea); §D3/§D5 degrade to the text-anchored document-citations-panel.tsx, never a misplaced box. TECH §4; §D1–D5.
  • 147-J — DOCX/XLSX editor wiring + fill mechanism. Wire the vendored (experimental) DOCX/Excel Editor as the edit affordance; §F2 “fill a missing answer” dispatches the EXISTING Claude-side draft lane (SSE draft-stream, form_responses write) — not a bespoke engine; manual edits persist via the editor save bound to storage_path; §F4 getAuthorisedClient(['admin','editor']); §F5 soft-error → read-only viewer fallback, never blank. TECH §5; §F1/F2/F4/F5.
  • 147-K — E-Signature fork. Fork Extend’s E-Signature block (only file?:string): (a) drive signature-field placement from our data; (b) onSigned persistence writing the signed PDF → a form_attachments row role='form_source' (needs 147-C); §F4 gating; §F5 fallback. TECH §5; §F3/F4/F5.
  • 147-L — Custom Warm Meridian workflow stepper. Custom component (lucide + semantic tokens) over the 10-state workflow_state; transition control offers only canTransition-valid next states (reads through the landed computeWorkflowTransition backend); invalid jump refused with a surfaced reason; state/deadline/outcome each carry a text label/icon (never colour-only). TECH §7; §G1–G4.
  • 147-M — Custom requirement-catalogue editor. Custom editor over form_requirement_templates domain fields (requirement_type, requirement_text, matching_keywords, matching_guidance, is_mandatory, word_limit_guidance, section_ref, sector_applicability, domain/subtopic); Schema Builder NOT used; writes admin/editor-gated. TECH §7; §H1/H3/H4.
  • 147-N — Custom question/answer-slot editor. Custom editor over form_questions/form_responses (question_text, word_limit, evaluation_weight, assignee, review_status, version); Schema Builder not used. TECH §7; §H2/H3/H4.
  • 147-O — Viewer + upload state contracts. Behaviour-first state layer over the vendored viewer/upload shells (split out of 147-B to keep vendor-in pure + within budget): §B6 per-viewer loading + soft-error-with-retry (never blank — DISTINCT from the item-page BI-19 states 145W-3 owns); §B7 unsupported/corrupt → “cannot preview” + download fallback; §E1 client type/size/count/duplicate validation inline; §E3 uploading a form still creates the form-first item (BI-9 regression guard); §E4 upload progress/success + the backend’s actual rejection reason. Owns components/procurement/extend/viewer-states.tsx + upload-shell.tsx (the shells’ STATE layer, distinct from 147-B’s vendored source and 147-D’s route). TECH §1 (§B/§E→change map); §B6/B7/E1/E3/E4.

{145.19}-wave (ID-145 siblings; task-gated on id-147 foundation)

Section titled “{145.19}-wave (ID-145 siblings; task-gated on id-147 foundation)”
  • W1 — Dead workspace-era UI removal (collateral). Remove procurement-forms-card.tsx (dead, incoming:{}); remove/replace template-upload.tsx (POSTs the retired list route; no form-first successor per BI-1/DR-068); remove page.tsx handleDownloadOriginal dead code + tidy the initial list-fetch degradation. NOT id-147-gated. Collateral item 4; BI-4.
  • W2 — Item-page hybrid frame + Documents tab + attachments read-fold. §A frame (form-as-page header; engagement sibling rail read-only when grouped; Documents tab split FORM SOURCE / REFERENCE-EVIDENCE; progressive disclosure; select-document-previews via vendored Sidebar + Thumbnail). Fold the form_attachments READ into group-A GET ([id]/route.ts) split by role + group-A DELETE storage remove(). Establishes child-component structure. TECH §6 group-A; §A1–A9.
  • W3 — State-stepper wiring + inline empty/loading/error. Wire 147-L over the {145.18} shape via the context provider (BI-13/18/§G); inline empty/loading/error — the item-page BI-19 states, distinct from the per-viewer §B6 states owned by 147-O. {145.19}; §G1–G4 wired.
  • W4 — Questions/coverage + mixed per-question honest states + zero-candidate manual-answer. Render questions/coverage/documents; mixed per-question states rendered HONESTLY (no all-or-nothing); zero-candidate question offers a manual-answer affordance → optional corpus promotion (BI-40, closes the BI-22 gap loop + BI-24 catalogue). {145.19}; §B6/BI-40.
  • W5 — Grouping lineage + drafting re-point. Read-only sibling lineage where grouped (BI-28/29, no roll-up, no engagement win-rate — S470); re-point the drafting stack (SSE draft-stream, response editor, question navigator, citation panel) to the form id (BI-41, keys on route [id]), stays secondary; §I4 streamed answers via Streamdown natively. {145.19}; §A3/A4/§I4.
  • W6 — content-renderer.tsx react-markdown → Streamdown migration + named snapshot. Preserve heading-id injection (Streamdown components prop as file-render-pane.tsx), GFM parity, prose styling, plain-text fallback; reuse normaliseInternalMdLinksForStreamdown (§I3); a NAMED stored rendered-output snapshot fixture over the enumerated corpus (headings/links/lists/bold/GFM tables/blockquote/code) — fixture name fixed here: content-renderer-streamdown-parity.snapshot. NOT id-147-gated. TECH §7 §I2; §I1/I2/I3.
  • W7 — Fill-slot review UI + citation overlay UI. Wire 147-H + 147-I into the item-page fill-slot review (§C: detected slots box-overlaid page-accurately; slot-list ↔ overlay linkage; fill_status per-slot label; PDF-only, DOCX/XLSX degrade to a list + note) and the citation panel (§D: bidirectional select; unresolvable → text-anchored entry; honest empty state). TECH §3/§4 UI; §C1–C4/§D1–D5.

All 53 PRODUCT §-invariants are accounted for. Behaviour-specific invariants map to a single owning subtask with concrete acceptance criteria. The four viewer/upload behaviours TECH’s §B/§E→change map assigns to the install slice — §B6 (per-viewer loading + soft-error-with-retry), §B7 (unsupported → download fallback), §E3 (form-first-item BI-9 guard), §E4 (upload progress/success/honest reject) — plus §E1 (client validation) are owned by the new 147-O, split out of 147-B so vendor-in stays pure and within budget. §B6 is per-viewer (147-O) and is deliberately NOT the item-page BI-19 empty/loading/error (145W-3) — the two are distinct and separately owned.

Three invariants are cross-cutting / scope, deliberately not single-subtask-owned (matching the checker-accepted baseline): §J4 (WCAG 2.1 AA + UK English on every surface) is enforced by the WCAG 2.1 AA non-colour-only + UK English quality bar in every UI subtask’s testStrategy; §K1 (Procurement-baseline-only scope) is a negative scope boundary honoured across the set (no wider-platform surface designed); §K2 (this chain gates ID-145.19) is the task-level chain-gate encoded at the wave level (the task_gated_on_id_147 fields), not a code change. Force-tagging these onto one subtask would repeat the tag-not-own anti-pattern — they are enforced, not owned.

RiskMitigation
Extend registry ABSENT / SIGNATURE_DRIFT at install147-A is a HARD STOP gate at Wave 0 — no Extend wiring proceeds until PRESENT is recorded; ABSENT/DRIFT escalates.
Experimental DOCX/Excel Editor + E-Signature fail to initialise§F5 degrades to the read-only viewer; the fork boundary (147-K) is budgeted explicitly, not a drop-in.
B2 vision coords unmappable (server-side raster)147-I MUST self-rasterise + pre-resize; B2 is fallback-only, spot-checkable; §D3 degrades to text, never a misplaced box.
/Rotate 90 / non-zero MediaBox → misplaced box147-E normalises into displayed post-rotation top-left space + carries rotation; geometrySchema zod degrades malformed geometry to §C4 (147-H); rotated-page pytest fixture in 147-E/F.
Authored migrations pushed from Lane B by mistakeBoth flagged migration_authored; authored in-branch only; parent sequences the push (another lane owns DB pushes).
page.tsx edit conflicts across item-page subtasks145W-2 establishes distinct child-component files; 145W-1 precedes; 145W-3/4/5/7 own disjoint components.
New [id]/attachments route silently public147-D asserts it is NOT in proxy.ts publicRoutes and routes failures via authFailureResponse(auth).

No new binding ruling surfaced during decomposition — DR-063…068 + DR-075 already settle the adoption posture, geometry, editors, stepper, attachment store, and route re-key. The §I2 snapshot fixture name (content-renderer-streamdown-parity.snapshot) is fixed in this plan per the S474 staff-review amendment; it is an implementation detail, not a DR. No DR-intent returned.


End of PLAN — ID-147 {147.4}. Output: specs/id-147-extend-streamdown-ui-foundation/PLAN.md. Not committed here. Proposed subtask records (TM-shape) for the parent to bulk-insert via bun scripts/ledger-cli.ts add-subtasks live in lane-b-proposed-subtasks.yaml.