Skip to content

feat(spec): generate a Markdown technical spec from the canvas - #28

Merged
Suharshit merged 1 commit into
feat/ai-sessions-docsfrom
feat/generate-spec
Sep 15, 2026
Merged

Suharshit merged 1 commit into
feat/ai-sessions-docsfrom
feat/generate-spec

Conversation

@Suharshit

Copy link
Copy Markdown
Owner

Stacked on #27. The base is feat/ai-sessions-docs, so this diff shows the spec feature only.

⚠️ Merge order note: GitHub main has #21–#23 only. #25 and #26 were merged into their stacked base branches after those bases had already been merged, so steps 4 and 5 never reached main. They're carried by #27 and this PR, so bring main up to date (via #27, then this PR) before relying on main.

Why

The Specs tab in the AI sidebar was a placeholder. This PR turns the canvas into a Markdown technical spec that downloads automatically. It highlights the major decisions, and draws every diagram and workflow on the canvas as a Mermaid diagram.

What

How it works

  1. Start: Generate spec calls POST /api/projects/[projectId]/spec, which returns 202 with the run id (409 with the existing run id if one is already running). It triggers the new spec-agent Trigger task and records a TaskRun with kind: SPEC.

  2. The task (src/trigger/spec-agent.ts):

    • reads the room read-only
    • builds the spec graph in code: groups of connected components, short refs, notes
    • makes one structured model call for the prose
    • renders the Markdown in code

    The model never adds components or connections, and anything in its output that refers to an unknown group or component is dropped. It stops with a user-facing message on an empty canvas or more than 200 components.

  3. Storage when the status is read, like AI turns: GET /api/projects/[projectId]/spec retrieves the latest spec run. When it has finished, the route:

    • uploads the Markdown to private Vercel Blob
    • records specMdPath, specRunId, specGeneratedAt and specStats on Project, in an update guarded on the run id
    • deletes the duplicate or previous blob

    So a spec is saved even if the tab closes, and the task never touches the database or Blob storage.

  4. Download: GET /api/projects/[projectId]/spec/markdown serves {project-slug}-spec.md as an attachment. The hook downloads it automatically once the run you started is stored.

The document

  • A title and generation line, then a table of contents.
  • 1. Overview: prose, goals, and notes that aren't near any diagram.
  • 2. Key decisions: > [!IMPORTANT] callouts with the decision, why, alternatives, trade-offs, the components involved, and the source (recorded during AI design, stated on the canvas, or inferred). Decisions from the requesting user's AI design sessions are included.
  • 3. System diagrams: one section per group of connected components, with a Mermaid diagram (shapes and arrow directions match the canvas; async links are dashed), a component table, flow steps and nearby notes.
  • 4. Component reference: kind, diagram, responsibility, and receives from / sends to.
  • 5. Connections: a table of from, to, label, sync or async, and direction.
  • 6. Risks and open questions.

Code

  • lib/spec/:
    • spec-graph.ts
    • mermaid.ts
    • spec-schema.ts: the model output schema, sanitizeSpecContent, and the run and status types
    • spec-prompt.ts
    • spec-engine.ts
    • render-markdown.ts
    • spec-store.ts: storing, starting runs and recorded decisions, with Trigger and Blob swappable for tests
    • spec-file-name.ts
  • UI: hooks/use-spec-generator.ts, mounted in AiSidebar so a run keeps being followed while another tab is open, and components/editor/spec-panel.tsx, which replaces the placeholder tab.
  • Shared with the design agent: lib/ai/model.ts (model id, thinking settings, schema retry), lib/ai/run-failure.ts (friendly failure messages) and lib/canvas-room.ts (reading the room). Design-turn behaviour is unchanged.
  • Migration 20260915195650_add_project_spec only adds things: a TaskRunKind enum, TaskRun.kind defaulting to DESIGN with an index, and four nullable spec fields on Project. It's already applied to the shared database.
  • Context files: the Spec Generation section in project-overview.md, a Spec Agent section in architecture-context.md, the Specs tab in ui-context.md, and the tracker entry. (docs.md is gitignored and was updated locally.)

Excerpt from a live run

Generated through the local Trigger worker (gemini-3.5-flash-lite) from the Microservices starter template, with one recorded decision:

Important

API Gateway Entry Point: Route all external client traffic through a single API Gateway.

Why: Centralizes entry point concerns such as routing and token verification.

Alternatives considered: Direct client-to-service communication

Trade-offs: Introduces a potential single point of failure and bottleneck at the gateway.

Components: Web Client, API Gateway · Source: Inferred from the diagram

flowchart TD
  c1(["Web Client"])
  c2(["API Gateway"])
  c3["Auth Service"]
  c4["User Service"]
  c5["Order Service"]
  c6[("Session Cache")]
  c7[("User DB")]
  c8[("Order DB")]
  c3 -->|"sessions"| c6
  c1 -->|"HTTPS"| c2
  c2 -->|"verify token"| c3
  c2 --> c5
  c2 --> c4
  c5 -->|"read / write"| c8
  c5 -->|"lookup"| c4
  c4 -->|"read / write"| c7
Loading

Verification

  • pnpm typecheck and pnpm lint pass, after next typegen for the new routes.
  • 61 offline checks (no model or database):
    • grouping, refs, note placement and the 200-component cap
    • Mermaid shapes, arrows and escaping
    • sanitising and list limits
    • every Markdown section, callouts, tables, directions and fallbacks
    • the prompt and file names
    • all 13 starter templates rendered with a stub spec
  • 10 ref-stripping checks: a live run showed flow steps like "Web Client (c1) sends…", so internal refs are now removed from all prose.
  • 22 storage checks against the database, with Trigger and Blob faked:
    • every run state
    • invalid output rejected before upload
    • a finished run stored once, and not retrieved again
    • a newer failed run keeps the old spec
    • concurrent reads store once and delete the duplicate and previous blobs
    • recorded decisions scoped to the user's unexpired completed results
  • Two live runs through the Trigger worker, on a throwaway project, room, session and blob that were all removed afterwards:
    • start → 202, then a second start → 409 with the same run
    • stored in about 11s with stats 8 / 8 / 1
    • the Markdown read back from Blob with every section, one diagram and every component
    • the recorded decision tagged as recorded
    • after a prompt change, the second run also inferred a decision from the diagram (it had listed only the recorded one before)
  • Access: signed-out requests to the three spec routes are stopped by Clerk (307).
  • Not yet verified: the Specs tab in a signed-in browser (stages, auto-download, the Download link) and the ref-stripping fix in a live run. The Mermaid excerpt above should render here on GitHub.

🤖 Generated with Claude Code

The Specs tab now turns the current canvas into a Markdown technical
spec that downloads automatically: an overview, key decisions
highlighted as callouts, one Mermaid diagram section per group of
connected components, a component reference, connections, and risks.

- spec-agent Trigger task: reads the room, builds the spec graph in
  code, makes one structured model call for the prose (sanitised
  against the graph, internal refs stripped), renders the Markdown in
  code; aborts on an empty or oversized canvas
- Stored on read like AI turns: GET /api/projects/[id]/spec uploads a
  finished run to private Vercel Blob and records it on Project
  (guarded on the run id); POST starts a run (409 while one is
  pending); GET .../spec/markdown downloads the file
- Decisions recorded in the user's AI design sessions are included and
  tagged as recorded
- Migration: TaskRun.kind (DESIGN or SPEC) and Project spec fields
- useSpecGenerator and SpecPanel replace the placeholder Specs tab
- Shared lib/ai/model.ts, lib/ai/run-failure.ts and lib/canvas-room.ts
  used by both agents

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
draftly Ready Ready Preview Sep 15, 2026 8:09pm UTC

@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 8acf16e3-1971-4db4-a687-7b9aa5e833b2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Suharshit
Suharshit merged commit b549790 into feat/ai-sessions-docs Sep 15, 2026
5 checks passed

This branch was successfully deployed

1 active deployment
Preview — 0131e65e Deployed Sep 15, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant