Spec Canvas Docs

Patterns & Practices

Advanced techniques for working with specs — progressive precision, behavioral specification, integrated specs, and more.

This page covers practical techniques for writing better specs and using them effectively. For format reference, see UI Spec and Data Spec. For methodology overview, see How It Works.

Progressive Precision

You do not need to specify everything at once. Start rough, add detail where it matters. Different blocks — and even different screens — can be at different precision levels within the same spec.

Level 0: Exploration

Screens with titles and purposes only. No blocks, no theme. Use this when you are still figuring out what the app needs.

screens:
  user_profile:
    title: User Profile
    purpose: "User profile page"

Level 1: Structure

Add blocks with purposes and basic columns. Optionally add theme. The structure is visible, but visual details are left to AI.

screens:
  user_profile:
    title: User Profile
    purpose: "User profile page"
    blocks:
      - id: avatar_section
        purpose: "User avatar, name, and bio"
      - id: edit_button
        purpose: "Edit profile button"

Level 2: Detailed

Add palette, templates, navigation. Write detailed purposes with behavior. Use nested blocks where structure matters.

metadata:
  theme: "modern minimal interface"
  palette:
    primary: "soft blue gradient"
    accent: "warm orange"

screens:
  user_profile:
    title: User Profile
    purpose: "User profile page with editable fields"
    blocks:
      - id: avatar_section
        purpose: "User profile card with hover effect"
        blocks:
          - id: avatar
            purpose: "Circular user photo, 120px, clickable for upload"
          - id: info
            purpose: "Name (bold, large text) and bio (multiline, editable)"
      - id: edit_button
        purpose: "Edit button, top-right, opens modal_edit_profile"

Level 3: Full Control

Add exact sizes, spacing, colors, animations, transitions. The purpose becomes an implementation-ready specification.

- id: avatar
  purpose: "120px circle, border 2px white, shadow-md,
    hover scale 1.05 transition 200ms, click opens file upload dialog"
- id: name
  purpose: "h2, text-primary, 24px bold, line-height 1.2"
- id: bio
  purpose: "p, text-dimmed, 16px, max 3 lines ellipsis, margin-top 12px"
- id: edit_button
  purpose: "absolute top-right, padding 10px 20px, border-radius 12px,
    glass-secondary background, hover brightness 110%, transition 150ms,
    onClick opens modal_edit_profile"

Mixing Levels

You do not need to apply the same level everywhere. A common approach: keep most screens at L1–L2, and only go to L3 for the screens or blocks that need pixel-level consistency.

# L1 — still exploring this screen
settings:
  title: Settings
  purpose: "Application settings and preferences"
  blocks:
    - id: general
      purpose: "General settings section"
    - id: notifications
      purpose: "Notification preferences"

# L3 — this screen needs exact control
dashboard:
  title: Dashboard
  purpose: "Main dashboard with KPI cards"
  blocks:
    - id: stats
      purpose: "4 cards in responsive grid, each: width 25%, min-width 200px,
        padding 20px, glass-primary background, rounded-xl, shadow-lg.
        Large number (32px bold), label (14px text-dimmed), flat white icon (24px).
        Hover: scale 1.02, transition 200ms ease-out."

Purpose Text vs Nested Blocks

There are two ways to add precision to a block, and they complement each other.

Everything in Purpose

Put all detail in the purpose text. Works well for simple components and quick iteration.

- id: user_card
  purpose: "Card with circular avatar (80px), name (bold), role (dimmed), and contact button (primary)"

Nested Structure

Break the block into child blocks. Works well for complex components, when individual parts need separate control, or when you want AI to treat each part as a distinct element.

- id: user_card
  purpose: "User card with hover effect, rounded corners"
  blocks:
    - id: avatar
      purpose: "Circular photo, 80px"
    - id: name
      purpose: "User name, bold"
    - id: role
      purpose: "User role, dimmed text"
    - id: contact_button
      purpose: "Contact button, primary style"

When to Use Which

Use purpose text when the block is simple, self-contained, or you are iterating quickly. Use nested blocks when the block has multiple distinct parts that need separate control, when you want to reference individual parts in behavior descriptions, or when the block is complex enough that a single purpose becomes hard to read.

You can combine both: put container-level styles in the parent purpose, and content details in child blocks.

- id: user_card
  purpose: "Glass card, padding 20px, rounded-xl, hover shadow transition 200ms"
  blocks:
    - id: avatar
      purpose: "Circular photo, 80px, border 2px white"
    - id: info
      purpose: "Name (bold) and role (dimmed), stacked vertically"

Behavioral Specification

Purpose describes not just appearance, but behavior — interactions, state changes, animations, and relationships between blocks.

Interactions

purpose: "Button 'Create Task', onClick opens modal_create_task"
purpose: "Task row, click navigates to task_detail screen"
purpose: "Dropdown menu, appears on hover, closes on blur"

State Variations

purpose: "Task list with filters. Empty state: illustration + 'No tasks yet' + create button"
purpose: "Save button, disabled until form is valid, shows spinner during save"
purpose: "Notification badge, hidden when count is 0, red pulse animation when new"

Animations and Transitions

purpose: "Logo text where last 3 characters change font style every 3 seconds"
purpose: "Card grid, items enter with staggered fade-in 100ms delay each"
purpose: "Sidebar, slides from left 300ms ease-out, overlay dims background"

Cross-Block Relationships

# Block A
- id: emotion_picker
  purpose: "User selects emotion: happy, sad, angry, surprised"

# Block B
- id: character_avatar
  purpose: "Character appearance changes based on selected emotion in emotion_picker"

AI agents understand these descriptions and generate appropriate event handling, state management, and animation code.

Integrated Specs: Data + UI

When a project has both a Data Spec and a UI Spec, AI agents understand the connection between them semantically. You do not need to use formal references like Entity.field in your purposes — just describe what the screen should do in natural language, and the agent figures out which entities and fields are involved.

How It Works in Practice

You write the UI Spec the same way you normally would — in natural language, describing intent and behavior.

# Data Spec defines:
entities:
  task:
    fields:
      title:
        type: string
        required: true
      status:
        type: task_status
      due_date:
        type: datetime
      assignee_id:
        type: uuid
        fk: user.id

# UI Spec — just describe what the screen does:
- id: task_form
  purpose: "Form for creating a new task. Fields: title, priority, due date, assignee selector with search"

When you give both specs to an AI agent, the agent reads the Data Spec and understands that title is a required string, status is an enum with specific values, due_date is a datetime, and assignee_id is a reference to the user entity. It generates appropriate controls, validation, and data binding — without you having to spell out each reference explicitly.

Why Two Specs

The Data Spec gives the agent knowledge about the domain: what entities exist, what types and constraints their fields have, how entities relate to each other. The UI Spec gives the agent knowledge about the interface: what screens exist, what each screen shows, how it behaves.

Together they form a complete, compact description of the application. The agent connects the two layers on its own.

Spec as Verification Layer

A spec sits between your idea and the code. When the implementation drifts from the intent, the spec is the anchor.

Debug by Reference

When an AI agent produces something wrong, do not explain the fix in natural language. Point back to the spec.

# Instead of this:
"No, the header should have the logo on the left and navigation on the right,
 and the background should be the glass style we discussed"

# Do this:
"Check the spec for header_bar and fix the implementation to match"

The spec is unambiguous. Natural language is not.

When you use HTML implementations generated by Spec Canvas as references, this mapping is even more precise. Every block in the HTML is marked with a ui-spec-block-id attribute that matches the block ID in the spec. The agent can look at the HTML, find the exact block, check the spec for its intended purpose, and fix the implementation — without you having to explain which part of the page you are talking about.

Spec Reset

When an agent drifts too far after many iterations, reset to the spec instead of adding more corrections.

  1. Stop iterating.
  2. Update the spec to reflect what you actually want.
  3. Generate a fresh implementation from the updated spec.
  4. Continue from a clean baseline.

This is cheaper than layering fix on top of fix.

Spec Diff

When working with an AI agent that already knows the codebase, you do not need to send the full spec every time. Send only the change.

Full Spec vs Diff

Full spec — give this to a new agent that has no context. The agent reads the entire spec and builds from scratch.

Spec diff — give this to an agent that already has the codebase. The diff is a ready-made task.

# Spec diff — tells the agent exactly what changed:
blocks:
  - id: problem_statement
-   purpose: "Bullet points: 'Design upfront', 'Hope it works', 'Discover it's wrong'"
+   purpose: "Flow diagram: Describe → See → Implement"

When to Use Which

SituationWhat to send
New agent, no contextFull spec
Same session, iteratingSpec diff
Agent already knows the codebaseSpec diff
Handing off to a colleagueSpec diff + comment

A spec diff turns "check the whole spec and figure out what changed" into "here is the task, apply it." One step instead of five.

On this page