Completing Forms (Procurement) — User Journeys
⚠️ BID-ERA HISTORICAL REFERENCE (bannered S462). Last verified S248 — predates the id-61 renames, id-130 per-form domain model, DR-014 (manual form upload), DR-038 (workspace retirement), and DR-041 (three-zone IA). Valuable for flow/mechanism archaeology only; hook names, table names, workspace-level state framing, and the “100s draft-all timeout” (retired S224 — async 202 queue) are all stale. Current model: ID-145 —
specs/id-145-procurement-form-first/RESEARCH.md.
Last verified: Session 248 (20 May 2026) — refreshed for S248 T4 procurement umbrella rename (lib/bid → lib/procurement, components/bid → components/procurement, /api/bids → /api/procurement, BID_STATES → PROCUREMENT_WORKFLOW_STATES); ID-71 bid→forms rename + outcome-grouped MCP tool surface (form_questions/form_responses tables, find/get/where_are_we_exposed/whats_in_my_queue tools); prior S210 (29 April 2026) for S195-S209 + prod-readiness S10-S13 Pending updates: None
Completing Forms (Procurement) — User Journeys
Section titled “Completing Forms (Procurement) — User Journeys”Overview
Section titled “Overview”Completing Forms provides workflows for responding to UK public-procurement
tenders. Editors and admins extract question structures from uploaded PDF/DOCX
tenders, draft responses via the contextually-aware 3-pass AI pipeline,
curate and review responses inside a dedicated Tiptap session, and export the
final document. The Q&A Library (/library) is the parallel surface for
maintaining canonical Q&A pairs that feed the form drafting pipeline; the same
inline-edit shape (useInlineFieldEdit) is used in both surfaces since the
S198 §1.5 ContentEditor upgrade.
Viewers may inspect forms and export, but cannot mutate state.
Entry Points
Section titled “Entry Points”| Entry Point | Route | Accessible By |
|---|---|---|
| Procurement index | /procurement | All roles |
| Procurement detail | /procurement/[id]?tab={overview|questions|documents}&q=&status=&sort= | All roles |
| Procurement session | /procurement/[id]/session | All roles |
| Procurement templates | /procurement/[id]/templates | All roles |
| Q&A Library | /library | All roles |
| Item detail | /item/[id] (for canonical Q&A pairs) | All roles |
| Reorientation | / (Reorient Me — active-procurement panel) | All roles |
| MCP App | ui://form-dashboard/app.html (via show_procurement_dashboard) | Authenticated MCP clients |
Procurement detail tabs are URL-synced via ?tab=, ?q= (search), ?status=
(filter), and ?sort= (P1-5, S184) — deep-links and browser-refresh persist
state. Managed by useFormActions.
User Journeys
Section titled “User Journeys”Journey 1: Initiating a Form Workflow
Section titled “Journey 1: Initiating a Form Workflow”Actor: Editor or admin Goal: Ingest a tender document and prepare the workspace for answering. Preconditions: Tender file is available in DOCX or PDF format; user has editor or admin role.
-
Create the form workspace
- Route:
/procurement→ “New” button →ProcurementCreationWizard - Component:
ProcurementCreationWizard(equal-weight 2-card layout —grid-cols-1 sm:grid-cols-2— Create Blank vs Create & Upload Tender) - What the user sees: Title + buyer + deadline metadata entry, with the option to upload the tender immediately.
- Route:
-
Upload & extract questions
- Route:
/procurement/[id](Documents tab) - Component:
TenderUpload - Action:
POST /api/procurement/[id]/tenderstores the file; an “Extract Questions” CTA triggersPOST /api/procurement/[id]/questions/extract(Claude PDF/DOCX extraction vialib/ai/extract-questions.ts). - Result: Structured questions appear under the Questions tab with
confidence_posture: null(matching not yet run).
- Route:
-
Review extracted questions (optional)
- Component:
QuestionReview - Action: User confirms / edits / merges extracted questions before they
enter the pipeline. Confirms via
handleQuestionReviewConfirmed.
- Component:
-
Match to KB content
- Action: User clicks “Match to KB” (admin/editor only) →
POST /api/procurement/[id]/questions/matchrunssearch_for_form_responsefor each generated query and writesconfidence_posture+matched_content_idsto eachform_questionsrow. - Result: Each question gains a posture (
strong_match,partial_match,needs_sme, orno_content), driving the priority-ordered draft queue.
- Action: User clicks “Match to KB” (admin/editor only) →
-
Assess readiness
- Component:
ReadinessChecklist(withReadinessBadge) - What the user sees: A 7-criteria checklist surfacing whether
extraction, matching, KB integration review, drafting, citation
coverage, review approval, and export prerequisites are met. The
useBidReadinesshook polls/api/procurement/[id]/readiness.
- Component:
Variations
Section titled “Variations”- Viewer: Cannot upload, extract, match, or transition state. Sees the
same surface in read-only mode (controls hidden via
useUserRole().canEdit). - Mobile: Procurement detail page collapses the side panels; the action menu
consolidates into a
MoreHorizontaldropdown (S153 mobile flake on the “New button visible for editor” assertion is logged in backlog asS153-Mobile-1— pre-existing, not blocking).
Edge Cases
Section titled “Edge Cases”- Empty extraction (corrupt PDF, encrypted DOCX): the route returns a
structured error; the UI surfaces a “no questions found” toast and keeps
the form in
draftstate. - All-low-confidence matches: the readiness checklist flags the form as
not draft-ready; the user is prompted to add KB content via
/item/newbefore drafting.
Journey 2: Drafting Responses
Section titled “Journey 2: Drafting Responses”Actor: Editor or admin Goal: Draft a well-cited, high-quality answer to a specific tender question. Preconditions: Questions extracted; KB matching complete; user has editor or admin role.
-
Enter the working session
- Route:
/procurement/[id]/session - Page: the session page at
app/procurement/[id]/session/page.tsx - What the user sees: The Tiptap
ResponseEditor, aQuestionNavigatorfor cross-question navigation, theCitationPanel(default-expanded for admin role or review-or-later form states), and theContentLibraryDrawerfor inserting KB snippets.
- Route:
-
Trigger AI drafting (3-pass)
- Action: User clicks “Draft” →
useDraftStreamopens an SSE stream against/api/procurement/[id]/responses/draft-stream. - System steps:
- Pass 1:
analyseQuestion(Sonnet) — identifies topic, structure, tone. - Pass 2:
draftResponse(Opus) — drafts with inline citations. - Pass 3:
checkResponseQuality(Haiku) — deterministic + AI quality gate.
- Pass 1:
- Result: Markdown streams into the editor as it’s generated; the
StreamingPhaseIndicatorshows the current phase.
- Action: User clicks “Draft” →
-
Edit the response
- The editor operates in markdown-native mode (S182 P0-BM Phase 2):
@tiptap/markdownextension reads/writes markdown viaeditor.getMarkdown(). Stored inform_responses.response_text.- Table extensions (Table, TableRow, TableHeader, TableCell) loaded so GFM tables survive parse/serialise cycles.
CharacterCountis a soft indicator only — no hard limit; the word count footer warns when over-limit or under-target.
- Auto-save: editor content debounced into
localStorageviauseDraftRecovery(cleared on successful save / accept).
- The editor operates in markdown-native mode (S182 P0-BM Phase 2):
-
Cite + save
- Action: User clicks “Save” →
PATCH /api/procurement/[id]/responses/[rId]persistsresponse_text. Citations link viacite_contentMCP tool or via the Citation Panel UI;citationsrecords the citing_form_response_id ↔ cited_content_item_id link. - Result: A new row lands in
form_response_history(append-only;change_reasonpopulated). Version increments.
- Action: User clicks “Save” →
-
Redraft / regenerate (optional)
- Action: User clicks “Redraft” (UI label for AI regeneration; internal
handler is
handleRegenerate). - Endpoint:
POST /api/procurement/[id]/responses/[rId]/regenerate— wrapsrunDraftingPipelinewith rate-limit (5/min/user) and 120s function timeout. Body validated byResponseRegenerateBodySchema.
- Action: User clicks “Redraft” (UI label for AI regeneration; internal
handler is
Citation Panel Behaviour
Section titled “Citation Panel Behaviour”The CitationPanel below the response editor is default-expanded when:
- The viewer is an admin, or
- The form is in a review-or-later state (
in_review,ready_for_export,submitted,won,lost,withdrawn).
Otherwise default-collapsed. Implemented in
app/procurement/[id]/session/page.tsx:264-266 (P1-4, S184). Per-question remount
via key prop preserves the initial default on question switch.
Batch Drafting
Section titled “Batch Drafting”Action: “Draft All” → POST /api/procurement/[id]/responses/draft-all runs questions
sequentially with a 100s safety timeout. Partial failures surface via
warningsEnvelope() and the WarningsBanner UI; users can retry
individually.
Variations
Section titled “Variations”- Viewer: Cannot draft, edit, redraft, or save. Read-only view of the
session —
ResponseEditormounts inreadOnlymode. - Cmd+S / Ctrl+S keyboard shortcut triggers
handleActionWithRecovery('save')when content is non-empty (page-level handler).
Edge Cases
Section titled “Edge Cases”- Stream interruption (network drop, tab close):
useDraftRecoverypreserves the last partial draft in localStorage; on remount, theDraftRecoveryDialogprompts to restore. - Quality-check fails Pass 3: the response is still saved (the gate
records issues in
form_responses.metadata), and the response surface flags problem areas for manual review.
Journey 3: Maintaining Q&A Library Content (S198 §1.5)
Section titled “Journey 3: Maintaining Q&A Library Content (S198 §1.5)”Actor: Editor or admin
Goal: Update a canonical Q&A pair so future form drafts pull a corrected
or improved answer. Preconditions: Q&A pair exists in content_items
(content_type = 'question_answer').
The Q&A Library is the upstream surface that feeds form drafting via
search_for_form_response. Q&A pair quality is the single most important
input to form response quality.
-
Open the Q&A pair
- Route:
/library(filter, search, list) → click row →/item/[id], or/item/[id]directly. - Component:
QAPairRenderer(read mode) →QAAnswerDisplaypanels.
- Route:
-
Edit Standard answer
- Action: Click “Edit” on the Standard panel.
- Component:
QAAnswerDisplaymounts a single dynamically-imported TiptapContentEditor(components/item-detail/content-editor.tsx), pre-populated fromitem.answer_standard ?? ''. Identified bydata-testid="qa-answer-panel-standard". - Constraints (S198 §1.5):
- Single-field-at-a-time: clicking Edit on the Standard panel mounts ONE editor; Save/Cancel unmounts it. Editing the Advanced panel must follow.
- Save-safety guard: ≥20% shrink relative to the per-field baseline
blocks the save (
SAVE_SAFETY_BLOCK_MESSAGEtoast). Composes inside the active editor’shandleSave. - Per-field regen-embedding checkbox: ticking it threads
regenerate_embedding: trueinto the PATCH body. Resets onstartEdit/cancelEdit/ successful save so the flag never leaks to a sibling field’s save (S198 verifier H1 fix).
-
Save
- Endpoint:
PATCH /api/items/[id]with field-level payload (answer_standardoranswer_advanced). - Side effect: rebuilds
content_items.contentfromQ: {question}\n\n{answer_standard}\n\n{answer_advanced}. Theif (question) parts.push(...)rebuild (S198 Wave 1 §4.1 H2 fix) correctly omits theQ:prefix when the question is empty. - When
regenerate_embedding=true: re-embeds the rebuilt content viatext-embedding-3-largeand refreshes downstream chunks.
- Endpoint:
-
Edit Advanced answer
- Same pattern via
data-testid="qa-answer-panel-advanced". Independent baseline + independent regen-embedding flag.
- Same pattern via
Variations
Section titled “Variations”- Viewer: Read mode only via
QAPairRenderer → ContentRenderer. No edit affordances render. - Empty
question: PATCH omits theQ:prefix, leaving the rebuilt content as{answer_standard}\n\n{answer_advanced}.
Edge Cases
Section titled “Edge Cases”- Save-safety block (≥20% shrink): editor stays mounted; user adjusts content; embedding flag retained across the failed attempt.
- Race condition (admin browses while another edit is mid-save):
optimistic-concurrency does NOT guard
answer_*fields directly — the rebuiltcontentfield is the storage contract; concurrent edits to the same field will overwrite without explicit collision detection.
Journey 4: Reviewing & Approving Responses
Section titled “Journey 4: Reviewing & Approving Responses”Actor: Reviewer (admin) or editor (self-review)
Goal: Move drafted responses through review to ready-for-export.
Preconditions: All form questions have a form_responses row.
-
Transition to
in_review- Action: User clicks “Send to Review” on
/procurement/[id](Overview tab). TriggershandleStatusTransition('in_review')inuseFormActions, which callsPATCH /api/procurement/[id]with{ status: 'in_review' }. - State machine:
drafting → in_review(or back fromready_for_export).
- Action: User clicks “Send to Review” on
-
Review each response
- Route:
/procurement/[id]/session(citation panel auto-expands for review-or-later states). - Reviewer reads each response, verifies citations (
useCitationOrphansbatch-checks for missing source IDs), and either approves (review_status='approved'onform_responses) or sends back for revision.
- Route:
-
Mark ready for export
- Action: All responses approved + readiness checklist green → user
transitions to
ready_for_export. - State machine:
in_review → ready_for_export.
- Action: All responses approved + readiness checklist green → user
transitions to
Edge Cases
Section titled “Edge Cases”- Reviewer requests edits: back-transition
in_review → draftingpreserved per state machine. - Orphan citations:
useCitationOrphansflagsform_response_history.citations.source_idvalues that no longer resolve to acontent_itemsrow (e.g. deleted KB content). Reviewer must re-cite or remove.
Journey 5: Exporting the Form
Section titled “Journey 5: Exporting the Form”Actor: Editor or admin
Goal: Construct the final, deliverable document for the buyer.
Preconditions: Form in ready_for_export; readiness checklist green.
-
Trigger export
- Component:
ProcurementExportMenu - Actions:
- “Export to DOCX” →
POST /api/procurement/[id]/export/docx—markdownToDocxParagraphspreserves headings, lists, bold/italic, links, and GFM tables; plain-text fields usestripMarkdown. - “Export to XLSX” →
POST /api/procurement/[id]/export/xlsx—exceljsoutput with plain-text equivalents.
- “Export to DOCX” →
- Component:
-
Template-based export (optional)
- Route:
/procurement/[id]/templates→ select template → “Fill” →POST /api/procurement/[id]/templates/[templateId]/fill. - Side effect:
template_completionsrow recordsfields_filled+storage_path. - Download:
GET /api/procurement/[id]/templates/[templateId]/completions/[completionId]/download.
- Route:
-
Submit & record outcome
- State transition:
ready_for_export → submitted. - After tender close:
ProcurementOutcomeDialogrecordswon/lost/withdrawnviaPOST /api/procurement/[id]/outcome. OptionallyPOST /api/procurement/[id]/outcome/integrateintegrates outcome learnings into the KB.
- State transition:
Edge Cases
Section titled “Edge Cases”- DOCX rendering edge cases (nested tables, deep list nesting):
lib/procurement/procurement-export-docx.tscovers viamarkdownToDocxParagraphs; tests in__tests__/lib/procurement/procurement-export-docx.test.ts. - Template Python write-back path: template completion currently uses
the Python
python-docxwrite-back (out-of-band relative to pure Vercel deployments).
Role Permissions Summary
Section titled “Role Permissions Summary”| Action | Viewer | Editor | Admin |
|---|---|---|---|
| View forms + responses | Yes | Yes | Yes |
| Export form (DOCX/XLSX) | Yes | Yes | Yes |
| Create / update / delete form | No | Editor only for create/update; Admin for delete | Yes |
| Upload tender + extract questions | No | Yes | Yes |
| KB matching | No | Yes | Yes |
| Draft / redraft / batch-draft | No | Yes | Yes |
| Save / cite / approve responses | No | Yes | Yes |
| Edit Q&A Library content | No | Yes | Yes |
Edit regenerate_embedding | No | Yes | Yes |
| Transition form state | No | Yes (most transitions); Admin only for delete | Yes |
| Record form outcome | No | Yes | Yes |
| Cite content via MCP | No | Yes | Yes |
| Citation panel default-expanded | Per state | Per state | Always (admin) |
Current Limitations
Section titled “Current Limitations”- No real-time collaboration —
Tiptap Collaborationis not wired in. Concurrent editors race via standard REST PATCH saves. - localStorage-bound recovery —
useDraftRecoverycannot recover a draft from a different browser or device. - No isolated draft scope per user — all editors see all drafts; there is no “my drafts” filter.
- Q&A field edit is field-by-field, not multi-field — by design (S198 §1.5). Users cannot mount Standard + Advanced editors simultaneously; they must save one before editing the other.
- Mobile editor flake on the “New button visible for editor”
assertion (
bid-pipeline.spec.ts:467) — pre-existing, logged asS153-Mobile-1in product backlog. - §1.7 admin dedup review + §1.9 near-duplicate merge dashboard ratified S209 WP2 but not yet implemented — duplicate Q&A pairs may surface as form matches until the dedup UI ships.
Related Documentation
Section titled “Related Documentation”- Technical reference:
docs/product-functionality/bid-management/technical.md - Workflows:
docs/product-functionality/bid-management/workflows.md - State of the product (Bid Workflow):
docs/reference/state-of-the-product.md§5 - Schema reference:
docs/reference/SCHEMA-QUICK-REFERENCE.md - Q&A ContentEditor spec (S198 §1.5):
docs/specs/qa-contenteditor-upgrade-spec.md - Publication-lifecycle spec (S205-S208 §5.2):
docs/specs/publication-lifecycle-state-machine-spec.md - §1.7 admin dedup review spec:
docs/specs/§1.7-admin-dedup-review-spec.md - §1.9 near-duplicate merge dashboard spec:
docs/specs/§1.9-near-dup-merge-dashboard-spec.md