Spec 0012: HQ File Download
Agent Roles
Section titled “Agent Roles”This specification is the single source of truth for what to build, how to verify it, and who does what. Each agent reads its role below and follows the instructions exactly. Agents do not communicate directly — they communicate through the provenance document.
Builder Agent
Section titled “Builder Agent”Purpose: Read this specification and produce working software with full provenance.
Reads:
- This specification
sites/hq-kevinryan-io/app/api/chat/route.ts— system prompt to updatesites/hq-kevinryan-io/app/components/ChatInterface.tsx— message rendering to updatesites/hq-kevinryan-io/app/components/MessageBubble.tsx— bubble component to update.sdd/provenance/spec-0011-hq-github-mcp.provenance.md— context from prior spec
Produces:
- Download capability for HQ-generated documents
- A provenance record at
.sdd/provenance/spec-0012-hq-file-download.provenance.md
Instructions:
- Save this spec to
.sdd/specification/spec-0012-hq-file-download.mdin the repo. - Read the full specification and all files listed under “Current state” before writing any code.
- Build the software as specified. Where the specification is silent, make a reasonable decision and record it in provenance.
- Write provenance as you build, not after.
- Do not write tests. Testing is not your role.
- When the build is complete, add a “Build Status” entry to the provenance.
- Commit the spec, implementation, and provenance together.
Testing Agent
Section titled “Testing Agent”Purpose: Read this specification and the builder’s provenance, then generate prose scenarios and executable tests.
Reads:
- This specification
- The provenance document at
.sdd/provenance/spec-0012-hq-file-download.provenance.md
Produces:
- Prose scenarios at
.sdd/scenarios/spec-0012-hq-file-download.scenarios.md - Executable test code in the
tests/directory - Updates to the provenance document
- Save this spec to
.sdd/specification/spec-0012-hq-file-download.mdin the repo. - Implement all changes described below.
- After completing all work, create a provenance record at
.sdd/provenance/spec-0012-hq-file-download.provenance.md.
Prerequisites
Section titled “Prerequisites”- Spec-0011 deployed: HQ chat interface with GitHub tools is live.
- Read the spec-0011 provenance before starting.
Context
Section titled “Context”HQ can generate high-quality documents — specs, proposals, reports, plans. Currently those documents only exist in the chat window. This spec adds the ability to download them as markdown files directly from the chat interface.
The pattern uses an explicit document marker. When the user asks HQ to produce something “for download” or to “generate a file”, HQ wraps the document content in markers. The UI detects the markers, renders the content normally, and shows a download button with the correct filename. Clicking the button triggers a client-side browser download — no storage, no backend, no new API routes.
This is entirely a frontend change plus a system prompt update. No infrastructure changes, no Terraform, no K8s changes.
Current state (read these files before making changes)
Section titled “Current state (read these files before making changes)”| File / Directory | What it does |
|---|---|
sites/hq-kevinryan-io/app/api/chat/route.ts | System prompt — add document marker instructions |
sites/hq-kevinryan-io/app/components/MessageBubble.tsx | Message rendering — detect markers, show download button |
sites/hq-kevinryan-io/app/components/ChatInterface.tsx | Chat state — ensure document content renders correctly |
Key facts
Section titled “Key facts”- Document start marker:
---DOCUMENT:filename.md--- - Document end marker:
---END DOCUMENT--- - Download format: Markdown (
.md) only for this iteration - Trigger phrase: User says “for download”, “as a file”, “generate a file”, or similar
- Client-side only:
Blob+URL.createObjectURL+ temporary<a>tag — no backend - No new dependencies required
1. System prompt update
Section titled “1. System prompt update”Update BASE_SYSTEM_PROMPT in sites/hq-kevinryan-io/app/api/chat/route.ts to add document generation instructions. Append the following section to the existing prompt:
DOCUMENT GENERATIONWhen the user asks you to produce a document, spec, proposal, report, or any structured content "for download" or "as a file":1. Write any brief intro text BEFORE the markers2. Wrap the document content in these exact markers: ---DOCUMENT:filename.md--- [document content here] ---END DOCUMENT---3. Choose a descriptive kebab-case filename based on the content e.g. emergn-proposal.md, spec-0014-hq-database.md, platform-review-report.md4. The content inside the markers should be complete and well-structured markdown5. Any closing remarks go AFTER the end marker
Example response structure:"Here's the proposal for Emergn:
---DOCUMENT:emergn-proposal.md---# Emergn Proposal...---END DOCUMENT---
Let me know if you'd like any changes."The DEMO_SYSTEM_PROMPT extends BASE_SYSTEM_PROMPT — no separate change needed there.
2. Message bubble — document detection and download button
Section titled “2. Message bubble — document detection and download button”Update sites/hq-kevinryan-io/app/components/MessageBubble.tsx to detect document markers in assistant messages and render a download button.
Document parsing logic
Section titled “Document parsing logic”Parse the message content to extract document blocks:
interface DocumentBlock { filename: string content: string}
function parseDocumentBlocks(text: string): { cleanText: string documents: DocumentBlock[]} { const documents: DocumentBlock[] = [] const markerRegex = /---DOCUMENT:([^\n-]+)---\n([\s\S]*?)---END DOCUMENT---/g
let match while ((match = markerRegex.exec(text)) !== null) { documents.push({ filename: match[1].trim(), content: match[2].trim(), }) }
const cleanText = text .replace(/---DOCUMENT:[^\n-]+---\n[\s\S]*?---END DOCUMENT---/g, '') .trim()
return { cleanText, documents }}Download handler
Section titled “Download handler”function downloadDocument(filename: string, content: string) { const blob = new Blob([content], { type: 'text/markdown; charset=utf-8' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = filename document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url)}Rendering
Section titled “Rendering”In the MessageBubble component, for assistant messages:
- Run
parseDocumentBlockson the content - Render
cleanTextas the message body (markers stripped) - For each document in
documents, render a download button below the message text
Download button style:
<button onClick={() => downloadDocument(doc.filename, doc.content)} style={{ display: 'inline-flex', alignItems: 'center', gap: '6px', marginTop: '12px', padding: '6px 12px', background: 'transparent', border: '1px solid #A8E10C', color: '#A8E10C', fontFamily: "'JetBrains Mono', monospace", fontSize: '0.75rem', letterSpacing: '0.05em', cursor: 'pointer', borderRadius: '4px', }}> ↓ {doc.filename}</button>Design notes:
- The download button sits inside the assistant message bubble, below the text
- If a response contains multiple documents, each gets its own download button
- The clean text (with markers stripped) renders normally — the user sees the document content in the chat AND can download it
- The button uses the lime accent colour consistent with the HQ brand
3. Streaming consideration
Section titled “3. Streaming consideration”The document markers appear in the streamed response. The parseDocumentBlocks function runs on the final message content once streaming is complete, not on each chunk. This means:
- During streaming, the raw markers are visible briefly as text arrives
- Once streaming completes, the component re-renders with markers stripped and the download button visible
This is acceptable for this iteration. A future improvement could hide markers during streaming, but that adds complexity not warranted here. Record this as a known limitation in provenance.
Constraints and Assumptions
Section titled “Constraints and Assumptions”- Constraint: Client-side download only. No file storage, no new API routes, no backend changes.
- Constraint: Markdown only. No
.docx,.pdf, or other formats in this iteration. - Constraint: The document marker format must be exact —
---DOCUMENT:filename.md---with no spaces around the colon. The system prompt must specify this exactly. - Assumption: The
MessageBubblecomponent receives the full message content as a string prop. If it receives it differently, adapt the parsing accordingly and record in provenance. - Assumption: No new npm dependencies are needed.
BlobandURL.createObjectURLare native browser APIs.
Out of Scope
Section titled “Out of Scope”.docxor.pdfoutput — future spec- Saving documents to GitHub — future spec
- Document history or storage — future spec
- Hiding markers during streaming — future improvement
- Multiple file formats — future spec
Manual steps (not performed by the agent)
Section titled “Manual steps (not performed by the agent)”None. This is a pure frontend + system prompt change. Merge the PR, the deploy workflow triggers automatically, Flux reconciles, done.
Provenance Record
Section titled “Provenance Record”After completing the work, create .sdd/provenance/spec-0012-hq-file-download.provenance.md using the provenance template at .sdd/provenance/template.md.
Validation steps
Section titled “Validation steps”- This spec has been saved to
.sdd/specification/spec-0012-hq-file-download.md BASE_SYSTEM_PROMPTinroute.tscontains theDOCUMENT GENERATIONsection with the marker formatMessageBubble.tsxcontains theparseDocumentBlocksfunctionMessageBubble.tsxcontains thedownloadDocumentfunctionMessageBubble.tsxrenders a download button for each detected document block- The download button uses lime accent colour
#A8E10Cand JetBrains Mono font - The marker text is stripped from the displayed message content
- No new npm dependencies have been added
- No infrastructure files have been modified (no Terraform, K8s, or GitHub Actions changes)
pnpm buildpasses insidesites/hq-kevinryan-io/pnpm lintpasses- The provenance record exists at
.sdd/provenance/spec-0012-hq-file-download.provenance.md - All files are committed together in a single commit