Purpose: This file orients automated agents (Claude Code, GitHub Copilot, Cursor, etc.) to the repository structure, architecture, testing requirements, and expectations for automated work.
Keep this file minimal, readable, and authoritative. If something here conflicts with other docs, stop and ask a human.
Vibe Scaffold is a Next.js 15.1.0 application that implements a multi-step wizard interface for generating technical specification documents using AI-powered chat conversations. The AI assistant is called "Vibe Scaffold Assistant" in the chat interface.
Users progress through 4 sequential steps, each involving:
- Chat Phase: Interactive conversation with AI to gather requirements
- Generation Phase: AI synthesizes chat history into a structured markdown document
- Approval Phase: User reviews and approves before proceeding to next step
- Step 1 - One Pager: High-level product vision and requirements
- Step 2 - Dev Spec: Technical specification and architecture details
- Step 3 - Prompt Plan: Implementation checklist and staged development plan
- Step 4 - AGENTS.md: Agent guidance and workflow documentation
app/store.ts— Zustand store with localStorage persistence (key:wizard-storage)app/types.ts— TypeScript interfaces for state, steps, and configsapp/wizard/steps/step{1-4}-config.ts— Step configurations (instructions, prompts, document inputs)
app/wizard/page.tsx— Main wizard orchestrator with navigation, state management, and sidebarapp/wizard/components/WizardStep.tsx— Per-step component handling chat/preview/generation with example modalapp/wizard/components/ChatInterface.tsx— Custom streaming chat implementation (not using @ai-sdk/react hooks)app/wizard/components/DocumentPreview.tsx— Markdown renderer with raw/rendered toggleapp/wizard/components/FinalInstructionsModal.tsx— Completion modal with download, copy command, and email subscribe
app/api/chat/route.ts— Streaming chat endpoint using Vercel AI SDKstreamText()app/api/generate-doc/route.ts— Streaming document generation endpointapp/api/subscribe/route.ts— Email subscription endpointapp/api/spikelog/route.ts— Analytics event logging endpointapp/api/log-metadata/route.ts— Spec metadata logging endpoint
app/wizard/utils/sampleDocs.ts— Sample documents for quick testing/developmentapp/wizard/utils/stepAccess.ts— Step access validation (prevents skipping steps)app/utils/analytics.ts— Google Analytics tracking utilitiesapp/utils/spikelog.ts— Custom analytics event logging
tests/— Vitest test suite with 200 tests (seetests/README.md)tests/unit/— Unit tests (store, utilities, components)tests/integration/api/— API integration tests
Zustand with localStorage persistence ensures state survives page refreshes:
- Key:
wizard-storage - Each step stores:
chatHistory,generatedDoc,approvedstatus - Navigation locked until current step approved
- State mapping in
app/wizard/page.tsxline 18:stepKeyMap
Step State Structure (app/types.ts):
StepData {
chatHistory: Message[] // All chat messages for this step
generatedDoc: string | null // Generated markdown document
approved: boolean // Whether step is complete
}Wizard State includes:
currentStep: number— Current step (1-4)isGenerating: boolean— Whether document generation is in progress (transient, not persisted)resetCounter: number— Incremented on reset to force component re-renderssteps: { onePager, devSpec, checklist, agentsMd }— Per-step data
Custom streaming implementation (not using useChat hook due to version compatibility):
- Manual fetch from
/api/chatand reads streamed text chunks - Appends streamed chunks directly to the latest assistant message
- Implementation in
app/wizard/components/ChatInterface.tsx
Stream Handling Logic:
const reader = response.body?.getReader();
const decoder = new TextDecoder();
// Read chunks and append to assistant message
const chunk = decoder.decode(value);
assistantMessage = {
...assistantMessage,
content: assistantMessage.content + chunk,
};
setMessages([...updatedMessages, assistantMessage]);Note: This assumes API returns plain text chunks; if streaming format changes, update this code.
/api/generate-docreceives:chatHistory,stepName,documentInputs(previous docs for context),generationPrompt- Step 2+ can reference earlier documents (e.g., Step 2 receives Step 1's one-pager)
- Streaming generation for progressive document display
- Document context passed via step config's
documentInputsarray - AGENTS.md documents get attribution appended:
<!-- Generated with vibescaffold.dev -->
Document Context Passing (app/wizard/components/WizardStep.tsx:28-35):
const documentInputsForChat: Record<string, string> = {};
if (config.documentInputs.length > 0) {
for (const inputKey of config.documentInputs) {
const key = inputKey as keyof typeof steps;
if (steps[key]?.generatedDoc) {
documentInputsForChat[inputKey] = steps[key].generatedDoc!;
}
}
}Important: Empty string values are NOT passed (only non-null documents).
- Uses OpenAI models via the Vercel AI SDK (
@ai-sdk/openai) - Model configured via
OPENAI_MODELenvironment variable (defaults togpt-4o) - Both API routes use Edge Runtime (not Node.js)
- Requires
OPENAI_API_KEYin.env.local
WizardPage (app/wizard/page.tsx)
├── Header (logo, reset button, dev tools)
├── Main content area
│ └── WizardStep component (per-step orchestrator)
│ ├── Step header with example output link
│ ├── ChatInterface (streaming chat)
│ │ └── Custom streaming implementation
│ ├── Loading indicator (during generation)
│ ├── DocumentPreview (after generation)
│ │ └── ReactMarkdown renderer
│ └── Example output modal
├── Sidebar
│ ├── Example Output panel (sample doc preview)
│ ├── Actions panel (Generate, Approve buttons)
│ └── Sequence panel (step progress with download buttons)
├── Footer
└── FinalInstructionsModal (on wizard completion)
├── Download instructions
├── Agent command to copy
└── Email subscribe / Discord links
-
Understand the Three-Phase Pattern
- Chat → Generation → Approval
- Don't break this flow when adding features
-
Respect State Structure
- Never add fields to
StepDatawithout updatingapp/types.ts - Always consider localStorage persistence implications
- Test state hydration after changes
- Never add fields to
-
Step Configuration Changes
- Update step configs in
app/wizard/steps/stepN-config.ts - Step configs are the source of truth for AI behavior
documentInputsarray controls which previous docs are passed to generation
- Update step configs in
-
API Route Modifications
- Both routes use Edge Runtime (no Node.js APIs)
- Maintain streaming for chat, non-streaming for generation
- Always validate
OPENAI_API_KEYpresence
-
UI Changes
- Maintain responsive grid:
lg:grid-cols-[70%_30%](chat/sidebar layout) - Keep sidebar sticky:
lg:sticky lg:top-20 - Preserve download functionality (individual + ZIP)
- Maintain responsive grid:
Test Framework: Vitest 4.0.10 with @testing-library/react
- ✅ Unit tests: Store, Utilities, Components (ChatInterface, WizardStep, WizardPage)
- ✅ Integration tests: Chat API, Generate Doc API
npm test # Run all tests once
npm run test:watch # Watch mode for development
npm run test:ui # Visual test UI
npm run test:coverage # Generate coverage report-
Always write tests for new features
- Unit tests for pure functions and utilities
- Integration tests for API routes
- Component tests for React components (when applicable)
-
Run full test suite before committing
npm testAll 200 tests must pass.
-
Test Coverage Requirements
- New business logic must have unit tests
- New API routes must have integration tests
- Maintain or improve overall coverage
Before considering work complete, verify:
-
Chat Flow
- Start chat, send messages, verify streaming works
- Test with/without API key (error handling)
-
Generation Flow
- Generate document from chat history
- Verify previous document context is passed (Step 2+)
- Test regeneration functionality
-
State Persistence
- Generate document, refresh page, verify state intact
- Test "Reset Wizard" clears all state
- Test "Load Sample Docs" populates all steps
-
Download Features
- Individual document download (verify ALL_CAPS_UNDERSCORES.md naming)
- Download All as ZIP (verify all docs included)
- Test with partial state (some steps incomplete)
-
Navigation
- Previous/Next buttons enable/disable correctly
- Can't proceed without approval
- Can navigate back to completed steps
- Don't introduce breaking changes to step configs without migration plan
- Don't change localStorage key (
wizard-storage) — will lose all user data - Don't use Node.js APIs in Edge Runtime routes
- Don't remove existing step configs (breaks state mapping)
- Don't change the OpenAI model default without testing all endpoints
- Don't skip writing tests for new features
- Don't commit code with failing tests
- Write tests before or alongside implementation (TDD approach)
- Run
npm testbefore committing - Test full wizard flow after changes (all 4 steps)
- Verify localStorage persistence after state changes
- Check both API routes if changing AI SDK usage
- Update this file (AGENTS.md) if architecture changes significantly
- Test with and without sample docs loaded
- Ask before changing state structure
- Ask before modifying streaming implementation
- Ask before changing step count (currently hardcoded to 4)
- Ask before major UI layout changes
- Ask before adding new dependencies
CRITICAL: All generated document filenames MUST use:
toUpperCase()for casereplace(/\s+/g, '_')for spaces → underscores- Example: "One Pager" →
ONE_PAGER.md
This applies to:
- Individual downloads (
app/wizard/page.tsx:59) - ZIP file contents (
app/wizard/page.tsx:90)
- Update
app/types.ts→ add field toStepDataorStepConfig - Update
app/store.ts→ handle new field in actions - Write tests in
tests/unit/store.test.ts - Update
app/wizard/components/WizardStep.tsx→ use new field - Test localStorage migration if needed
- Run
npm testto verify
- Edit
app/wizard/steps/stepN-config.ts - Modify
systemPromptfor chat behavior - Modify
generationPromptfor document generation (if exists) - Test: chat → generate → verify output quality
- No tests needed for prompt changes (unless adding new fields)
- Edit step config's
documentInputsarray - Add keys of previous steps (e.g.,
["onePager", "devSpec"]) - API route automatically passes these to generation
- Test manually to verify context is used
- Modify the
generationPromptin step config - Or update
/api/generate-doc/route.tsfor global changes - Test regeneration on existing chat history
- Add integration test if changing route behavior
- Create route file in
app/api/ - Use Edge Runtime:
export const runtime = "edge"; - Write integration tests in
tests/integration/api/ - Mock external dependencies (OpenAI, etc.)
- Test error handling
- Run
npm testto verify
# Install dependencies
npm install
# Set up environment
cp .env.example .env.local
# Add your OPENAI_API_KEY (and optionally OPENAI_MODEL)
# Start dev server
npm run dev
# Run tests in watch mode (recommended)
npm run test:watch- Use "Load Sample Docs" button for quick state population
- Test individual step flows
- Test full 4-step progression
- Test download features
- Verify state persistence (refresh browser)
- Run automated tests:
npm test
npm run dev # Development server (http://localhost:3000)
npm run build # Production build (tests TypeScript compilation)
npm run lint # ESLint check
npm start # Production server
npm test # Run all tests
npm run test:watch # Test watch mode- Edge Runtime: Both API routes use Edge Runtime (not Node.js)
- Model: Configured via
OPENAI_MODEL(defaults togpt-4o) in both API routes - Tailwind v3: Using Tailwind CSS v3.4, not v4 (due to PostCSS plugin compatibility)
- Step Count: Adding/removing steps requires updating:
stepKeyMapinapp/wizard/page.tsx(line 18)stepsobject type inapp/types.tsinitialStepDatastructure inapp/store.ts
- No useChat Hook: Custom streaming implementation (version compatibility issue)
// app/wizard/page.tsx:18
const stepKeyMap = ["onePager", "devSpec", "checklist", "agentsMd"] as const;Don't change without updating:
app/types.ts→WizardState["steps"]app/store.ts→initialStepDatastructure
# Required
OPENAI_API_KEY=sk-...
# Optional (defaults shown)
OPENAI_MODEL=gpt-4oTo inspect wizard state:
- Open browser DevTools → Application → Local Storage
- Look for key
wizard-storage - Value is JSON with current wizard state
To reset state:
- Click "Reset Wizard" button in UI
- Or delete
wizard-storagefrom localStorage
Ask the human if:
- Changing the number of steps (currently hardcoded to 4)
- Modifying localStorage persistence structure (data migration needed)
- Changing AI model or API provider
- Major UI/UX changes that affect the three-phase workflow
- Adding new dependencies that increase bundle size significantly
- Changing file naming conventions (affects existing user workflows)
- Modifying Edge Runtime routes in ways that might not be compatible
- Unsure whether to add tests for a particular change
- Test coverage drops below current level (200 tests)
| File | Purpose | Tests? |
|---|---|---|
app/wizard/page.tsx |
Main wizard, navigation, downloads, sidebar, completion modal | ✅ |
app/wizard/components/WizardStep.tsx |
Per-step logic: chat ↔ preview ↔ generation | ✅ |
app/wizard/components/ChatInterface.tsx |
Streaming chat UI and manual stream parsing | ✅ |
app/wizard/components/DocumentPreview.tsx |
Markdown rendering with raw/rendered toggle | Not yet |
app/wizard/components/FinalInstructionsModal.tsx |
Completion modal with instructions | Not yet |
app/api/chat/route.ts |
Streaming chat endpoint (Edge Runtime) | ✅ |
app/api/generate-doc/route.ts |
Document generation endpoint (Edge Runtime) | ✅ |
app/api/subscribe/route.ts |
Email subscription endpoint | Not yet |
app/store.ts |
Zustand + localStorage state management | ✅ |
app/types.ts |
TypeScript interfaces for entire app | — |
app/wizard/steps/stepN-config.ts |
Step-specific configuration and prompts | Not yet |
app/wizard/utils/sampleDocs.ts |
Sample documents for testing | ✅ |
app/wizard/utils/stepAccess.ts |
Step access validation logic | Not yet |
app/utils/analytics.ts |
Google Analytics tracking | Not yet |
app/utils/spikelog.ts |
Custom event logging | Not yet |
To add a new step beyond the current 4:
-
Update step key mapping in
app/wizard/page.tsx:const stepKeyMap = ["onePager", "devSpec", "checklist", "agentsMd", "newStep"] as const;
-
Add to type definitions in
app/types.ts:steps: { onePager: StepData; devSpec: StepData; checklist: StepData; agentsMd: StepData; newStep: StepData; // Add this }
-
Initialize in store in
app/store.ts:steps: { // ... existing steps newStep: { ...initialStepData }, }
-
Create step config
app/wizard/steps/step5-config.ts:export const step5Config: StepConfig = { stepNumber: 5, stepName: "New Step", userInstructions: "...", systemPrompt: "...", generateButtonText: "Generate New Step", approveButtonText: "Approve Draft & Save", documentInputs: ["onePager", "devSpec"], // Previous steps for context };
-
Import in main wizard
app/wizard/page.tsx:import { step5Config } from "./steps/step5-config"; const stepConfigs = [step1Config, step2Config, step3Config, step4Config, step5Config];
-
Write tests for any new logic introduced
No component changes needed - WizardStep component handles all steps generically.
-
Follow the AAA Pattern
it('should do something specific', () => { // Arrange const input = "test"; // Act const result = functionUnderTest(input); // Assert expect(result).toBe("expected"); });
-
Use Descriptive Test Names
- ✅ Good:
it('should return 400 when messages are missing', ...) - ❌ Bad:
it('test validation', ...)
- ✅ Good:
-
Mock External Dependencies
vi.mock("ai", () => ({ streamText: vi.fn(() => ({ ... })), }));
-
Test Error Cases
- Don't just test happy paths
- Verify error handling works correctly
-
Keep Tests Isolated
- Use
beforeEach(() => vi.clearAllMocks()) - Don't rely on test execution order
- Use
See tests/setup.ts for global mocks:
- localStorage is mocked globally
- Console methods are mocked to reduce noise
- Clear all mocks before each test
For API tests, mock the AI SDK:
vi.mock("ai", () => ({ ... }));
vi.mock("@ai-sdk/openai", () => ({ ... }));For more detailed testing documentation, see tests/README.md.
For user-facing documentation, see README.md.