# Orchard Studio: Implementation Roadmap

This roadmap converts the vision into executable phases with clear deliverables.

---

## Phase 0: Engine Validation (Current)

**Goal:** Prove the SDLC engine can autonomously build complex systems.

**Status:** In Progress (2 of 4 trees passing)

### Slackpath Verification Results

| Tree | Infrastructure | Build | Status | Notes |
|------|----------------|-------|--------|-------|
| `slackpath-1` | ✅ Pass | ✅ Pass | ✅ Complete | 5 polls (~30s) |
| `slackpath-2` | ✅ Pass | ✅ Pass | ✅ Complete | 111 polls (~9 min) |
| `slackpath-3` | ✅ Pass | ⚠️ Timeout | ❌ Incomplete | Build stayed "pending" - worker capacity |
| `slackpath-4` | ✅ Pass | ⚠️ Timeout | ❌ Incomplete | Build stayed "running" - long task |

### Key Findings

1. **Infrastructure provisioning works reliably** - all 4 trees pass `wait-infra`
2. **SDLC execution works** - slackpath-1 and slackpath-2 complete successfully
3. **Worker capacity issue** - slackpath-3 never started (stayed "pending")
4. **Long-running task issue** - slackpath-4 timed out while still "running"

### Remaining Work

| Task | Status | Notes |
|------|--------|-------|
| Worker pool scaling | 🔄 In Progress | Need capacity for parallel builds |
| Long-running task handling | 🔄 In Progress | Extend timeouts or checkpoint |
| Component templates complete | 🔄 In Progress | service ✅, worker 🔄, app-react 🔄 |

### Success Criteria

```bash
# All 4 slackpath trees complete successfully
./cookbooks/scripts/tree-runner.sh cookbooks/trees/slackpath-1-authenticated-service.yaml  # ✅
./cookbooks/scripts/tree-runner.sh cookbooks/trees/slackpath-2-*.yaml                      # ✅
./cookbooks/scripts/tree-runner.sh cookbooks/trees/slackpath-3-*.yaml                      # 🔄
./cookbooks/scripts/tree-runner.sh cookbooks/trees/slackpath-4-*.yaml                      # 🔄
```

### Exit Condition

All 4 slackpath trees complete autonomously without manual intervention.

---

## Phase 1: Blueprint API (Week 1-2)

**Goal:** Add Blueprint storage and chat endpoint for Architect conversations.

### Week 1: Database & CRUD

#### Day 1-2: Schema & Migrations

```sql
-- migrations/000X_blueprints.up.sql
CREATE TABLE blueprints (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    project_id UUID NOT NULL REFERENCES projects(id),
    feature_name TEXT NOT NULL,
    summary TEXT,
    sections JSONB NOT NULL DEFAULT '{}',
    open_questions JSONB NOT NULL DEFAULT '[]',
    assumptions JSONB NOT NULL DEFAULT '[]',
    ready_to_build BOOLEAN NOT NULL DEFAULT FALSE,
    blockers JSONB NOT NULL DEFAULT '[]',
    status TEXT NOT NULL DEFAULT 'draft',
    built_feature_slug TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE blueprint_messages (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    blueprint_id UUID NOT NULL REFERENCES blueprints(id) ON DELETE CASCADE,
    role TEXT NOT NULL,
    content TEXT NOT NULL,
    blueprint_snapshot JSONB,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_blueprints_project ON blueprints(project_id);
CREATE INDEX idx_blueprint_messages_blueprint ON blueprint_messages(blueprint_id);
```

#### Day 3-4: Domain & Repository

```go
// internal/domain/blueprint.go
type Blueprint struct {
    ID            uuid.UUID
    ProjectID     uuid.UUID
    FeatureName   string
    Summary       string
    Sections      BlueprintSections
    OpenQuestions []OpenQuestion
    Assumptions   []Assumption
    ReadyToBuild  bool
    Blockers      []string
    Status        string // draft, building, built, archived
    CreatedAt     time.Time
    UpdatedAt     time.Time
}

type BlueprintSections struct {
    DataModel    DataModelSection
    APIEndpoints APIEndpointsSection
    UIComponents UIComponentsSection
    Dependencies DependenciesSection
}

// internal/port/blueprint.go
type BlueprintRepository interface {
    Create(ctx context.Context, bp *domain.Blueprint) error
    Get(ctx context.Context, id uuid.UUID) (*domain.Blueprint, error)
    Update(ctx context.Context, bp *domain.Blueprint) error
    Delete(ctx context.Context, id uuid.UUID) error
    ListByProject(ctx context.Context, projectID uuid.UUID) ([]domain.Blueprint, error)
    AddMessage(ctx context.Context, msg *domain.BlueprintMessage) error
    GetMessages(ctx context.Context, blueprintID uuid.UUID) ([]domain.BlueprintMessage, error)
}
```

#### Day 5: Handler & Routes

```go
// internal/handlers/blueprints.go
func (h *BlueprintHandler) Mount(r chi.Router) {
    r.Route("/projects/{projectId}/blueprints", func(r chi.Router) {
        r.With(auth.RequireScope(auth.ScopeProjectsRead)).Get("/", h.List)
        r.With(auth.RequireScope(auth.ScopeProjectsRead)).Get("/{blueprintId}", h.Get)
        r.With(auth.RequireScope(auth.ScopeProjectsExecute)).Post("/", h.Create)
        r.With(auth.RequireScope(auth.ScopeProjectsExecute)).Delete("/{blueprintId}", h.Delete)
        r.With(auth.RequireScope(auth.ScopeProjectsExecute)).Post("/{blueprintId}/chat", h.Chat)
        r.With(auth.RequireScope(auth.ScopeProjectsExecute)).Post("/{blueprintId}/build", h.Build)
    })
}
```

### Week 2: Architect Integration + Design References

#### Day 6-7: Architect Service + Reference Handling

The Architect service handles both text conversations and design reference processing.

**Reference Capture (parallel track):**

```go
// internal/service/reference_service.go
type ReferenceService struct {
    verifyExecutor port.VerifyExecutor  // Reuse Playwright infrastructure
    storage        port.FileStorage
    logger         *slog.Logger
}

func (s *ReferenceService) CaptureURL(ctx context.Context, blueprintID uuid.UUID, url string) (*domain.Reference, error) {
    // 1. Screenshot the URL using Playwright pod
    result, err := s.verifyExecutor.Capture(ctx, domain.VerifySpec{
        URL:       url,
        Viewports: []string{"1920x1080"},  // Desktop only for references
        FullPage:  true,
    })

    // 2. Store screenshot
    path := fmt.Sprintf("/references/%s/%s.png", blueprintID, uuid.New())
    s.storage.Save(ctx, path, result.Screenshots["1920x1080"])

    // 3. Return reference metadata
    return &domain.Reference{
        ID:        uuid.New(),
        Type:      "url",
        Source:    url,
        Thumbnail: path,
    }, nil
}

func (s *ReferenceService) ProcessUpload(ctx context.Context, blueprintID uuid.UUID, data []byte) (*domain.Reference, error) {
    // Handle user-uploaded screenshots
    path := fmt.Sprintf("/references/%s/%s.png", blueprintID, uuid.New())
    s.storage.Save(ctx, path, data)

    return &domain.Reference{
        ID:        uuid.New(),
        Type:      "screenshot",
        Source:    path,
        Thumbnail: path,
    }, nil
}
```

#### Day 6-7: Architect Service

```go
// internal/service/architect_service.go
type ArchitectService struct {
    blueprintRepo port.BlueprintRepository
    llmClient     port.LLMClient
    logger        *slog.Logger
}

func (s *ArchitectService) Chat(ctx context.Context, blueprintID uuid.UUID, message string) (*ChatResponse, error) {
    // 1. Load blueprint and conversation history
    bp, _ := s.blueprintRepo.Get(ctx, blueprintID)
    messages, _ := s.blueprintRepo.GetMessages(ctx, blueprintID)

    // 2. Build prompt with Architect persona
    prompt := s.buildArchitectPrompt(bp, messages, message)

    // 3. Call LLM
    response, _ := s.llmClient.Complete(ctx, prompt)

    // 4. Parse structured response
    reply, blueprintUpdate := s.parseResponse(response)

    // 5. Apply blueprint update
    s.applyBlueprintUpdate(bp, blueprintUpdate)

    // 6. Save message and updated blueprint
    s.blueprintRepo.AddMessage(ctx, &domain.BlueprintMessage{...})
    s.blueprintRepo.Update(ctx, bp)

    return &ChatResponse{Reply: reply, Blueprint: bp}, nil
}
```

#### Day 8-9: Architect Prompt Engineering

````markdown
<!-- .claude/agents/architect.md -->

# Architect Agent

You are the Architect for Orchard Studio. Your job is requirements engineering.

## Response Format

ALWAYS respond with valid JSON in this exact format:

```json
{
  "reply": "Your conversational response to the user",
  "blueprint": {
    "feature": "Feature name",
    "summary": "One-line summary",
    "sections": {
      "dataModel": {
        "status": "empty|partial|complete",
        "entities": [...]
      },
      "designSystem": {
        "status": "empty|partial|complete",
        "colors": [...],
        "typography": [...],
        "spacing": [...],
        "inspirationNotes": "..."
      },
      ...
    },
    "references": {
      "items": [...]
    },
    "openQuestions": [...],
    "assumptions": [...],
    "readyToBuild": false,
    "blockers": [...]
  }
}
```
````

## Behavior Rules

1. Start with the current blueprint (provided below)
2. Update it based on the user's message
3. Ask 1-2 clarifying questions if gaps exist
4. Suggest building when the plan is complete
5. NEVER write implementation code

## Handling Design References

When the user provides a URL or screenshot:

1. **Describe what you observe** (conversational, natural language):
    - Layout structure, visual hierarchy
    - Color palette, theme (light/dark)
    - Component patterns you recognize
    - Typography and spacing feel

2. **Ask clarifying questions about intent**:
    - "Match exactly or use as inspiration?"
    - "Keep these colors or use your brand?"
    - "Include all elements or simplify for v1?"

3. **Extract structured tokens into designSystem**:
    - Colors: Primary, secondary, accent, background, text
    - Typography: Font families, sizes, weights
    - Spacing: Observed rhythm (4px, 8px, 16px, etc.)
    - Border radius, shadows, other patterns

4. **Document in inspirationNotes**:
    - Which elements came from which reference
    - What was adapted vs. copied
    - User's stated preferences

The conversation stays loose. The Blueprint stays precise.

## Building the Plan Agentively

The transition from loose conversation to structured Blueprint is the core challenge.
This is NOT a simple extraction—it requires judgment, interpretation, and iteration.

Key principles:

1. **Don't rush to structure.** Let the conversation develop naturally.
2. **Show your work.** "I'm inferring spacing of 16px from the card padding I see."
3. **Invite correction.** "Does this match what you had in mind?"
4. **Iterate incrementally.** Each turn refines the Blueprint slightly.
5. **Distinguish confidence levels.** "I'm confident about the 3-tier layout, less sure about the accent color."

The Architect is not a form-filler. It's a collaborator that builds shared understanding.

````

#### Day 10: Testing & Verification

```bash
# Test conversation flow
curl -X POST $RDEV_API_URL/projects/$PROJECT_ID/blueprints \
  -H "X-API-Key: $RDEV_API_KEY" \
  -d '{"featureName": "Cat Photos"}'

# Returns blueprint_id

curl -X POST $RDEV_API_URL/projects/$PROJECT_ID/blueprints/$BLUEPRINT_ID/chat \
  -H "X-API-Key: $RDEV_API_KEY" \
  -d '{"message": "I want users to post cat photos"}'

# Returns {reply: "Should photos be public or...", blueprint: {...}}

# Test with design reference
curl -X POST $RDEV_API_URL/projects/$PROJECT_ID/blueprints/$BLUEPRINT_ID/chat \
  -H "X-API-Key: $RDEV_API_KEY" \
  -d '{
    "message": "Build a photo grid like this",
    "references": [{"type": "url", "source": "https://unsplash.com/"}]
  }'

# Returns {reply: "I see a masonry grid with rounded corners...", blueprint: {...}}
# Blueprint now includes references.items[] and sections.designSystem
````

#### Cookbook Tree: design-from-reference

```yaml
# cookbooks/trees/design-from-reference.yaml
name: design-from-reference
description: "E2E test: Build feature from visual inspiration"

vars:
    project_name: ""
    reference_url: "https://stripe.com/pricing"

steps:
    create-project:
        action: api
        method: POST
        endpoint: /project
        body:
            name: "{{ .vars.project_name }}"
        outputs:
            - project_id: .data.name

    start-blueprint:
        depends_on: [create-project]
        action: api
        method: POST
        endpoint: "/projects/{{ .outputs.create-project.project_id }}/blueprints"
        body:
            featureName: "Pricing Page"
        outputs:
            - blueprint_id: .data.id

    provide-reference:
        depends_on: [start-blueprint]
        action: api
        method: POST
        endpoint: "/projects/{{ .outputs.create-project.project_id }}/blueprints/{{ .outputs.start-blueprint.blueprint_id }}/chat"
        body:
            message: "Build a pricing page inspired by this"
            references:
                - type: url
                  source: "{{ .vars.reference_url }}"
        outputs:
            - has_design_system: .blueprint.sections.designSystem.status != "empty"

    clarify-intent:
        depends_on: [provide-reference]
        action: api
        method: POST
        endpoint: "/projects/{{ .outputs.create-project.project_id }}/blueprints/{{ .outputs.start-blueprint.blueprint_id }}/chat"
        body:
            message: "Use as inspiration. Match the 3-tier layout but use a light theme with blue accents."

    confirm-plan:
        depends_on: [clarify-intent]
        action: api
        method: POST
        endpoint: "/projects/{{ .outputs.create-project.project_id }}/blueprints/{{ .outputs.start-blueprint.blueprint_id }}/chat"
        body:
            message: "Looks good, build it"

    trigger-build:
        depends_on: [confirm-plan]
        action: api
        method: POST
        endpoint: "/projects/{{ .outputs.create-project.project_id }}/blueprints/{{ .outputs.start-blueprint.blueprint_id }}/build"
        outputs:
            - operation_id: .data.operationId

    wait-build:
        depends_on: [trigger-build]
        action: wait_build
        build_id: "{{ .outputs.trigger-build.operation_id }}"
        max_attempts: 120

teardown:
    - action: api
      method: DELETE
      endpoint: "/project/{{ .outputs.create-project.project_id }}"
```

### Phase 1 Exit Criteria

- [ ] Blueprints table created and migrated
- [ ] CRUD endpoints working
- [ ] Chat endpoint returns structured responses
- [ ] Chat endpoint accepts `references[]` array (URLs and uploads)
- [ ] URL references auto-screenshot via Playwright
- [ ] Architect describes visual references in conversation
- [ ] Design tokens extracted into `sections.designSystem`
- [ ] Conversation history persisted
- [ ] `readyToBuild` computed correctly

---

## Phase 2: Operation Tracking (Week 2-3)

**Goal:** Move tree/build orchestration from shell scripts to database-tracked operations.

### Week 2 (overlap with Phase 1): Schema & Core

#### Operations Schema

```sql
-- migrations/000Y_operations.up.sql
CREATE TABLE operations (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    project_id UUID NOT NULL REFERENCES projects(id),
    blueprint_id UUID REFERENCES blueprints(id),
    operation_type TEXT NOT NULL,
    tree_name TEXT,
    status TEXT NOT NULL DEFAULT 'pending',
    current_phase TEXT,
    progress JSONB NOT NULL DEFAULT '{}',
    result JSONB,
    error TEXT,
    started_at TIMESTAMPTZ,
    completed_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE operation_events (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    operation_id UUID NOT NULL REFERENCES operations(id) ON DELETE CASCADE,
    event_type TEXT NOT NULL,
    payload JSONB NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_operations_project ON operations(project_id);
CREATE INDEX idx_operations_status ON operations(status) WHERE status IN ('pending', 'running');
CREATE INDEX idx_operation_events_operation ON operation_events(operation_id);
```

#### Orchestrator Service

```go
// internal/service/orchestrator_service.go
type OrchestratorService struct {
    operationRepo port.OperationRepository
    eventRepo     port.OperationEventRepository
    sdlcService   *SDLCService
    workQueue     port.WorkQueue
    logger        *slog.Logger
}

func (s *OrchestratorService) StartBuild(ctx context.Context, blueprintID uuid.UUID) (*domain.Operation, error) {
    // 1. Create operation record
    op := &domain.Operation{
        ProjectID:     projectID,
        BlueprintID:   &blueprintID,
        OperationType: "build",
        Status:        "pending",
    }
    s.operationRepo.Create(ctx, op)

    // 2. Convert blueprint to SDLC feature spec
    spec := s.blueprintToSpec(blueprint)

    // 3. Enqueue work item with operation ID
    s.workQueue.Enqueue(ctx, &domain.WorkItem{
        Type:        "sdlc",
        OperationID: op.ID,
        Spec:        spec,
    })

    return op, nil
}

func (s *OrchestratorService) EmitEvent(ctx context.Context, opID uuid.UUID, event OperationEvent) error {
    // 1. Persist event
    s.eventRepo.Create(ctx, opID, event)

    // 2. Update operation status
    if event.Type == "phase" {
        s.operationRepo.UpdatePhase(ctx, opID, event.Phase, event.Status)
    }

    return nil
}
```

### Week 3: SSE Streaming

#### SSE Handler

```go
// internal/handlers/operations_stream.go
func (h *OperationsHandler) Stream(w http.ResponseWriter, r *http.Request) {
    opID := chi.URLParam(r, "operationId")

    // Set SSE headers
    w.Header().Set("Content-Type", "text/event-stream")
    w.Header().Set("Cache-Control", "no-cache")
    w.Header().Set("Connection", "keep-alive")

    flusher, ok := w.(http.Flusher)
    if !ok {
        api.WriteInternalError(w, "streaming not supported")
        return
    }

    // Get existing events (for reconnection)
    events, _ := h.eventRepo.GetByOperation(r.Context(), opID)
    for _, e := range events {
        fmt.Fprintf(w, "event: %s\ndata: %s\n\n", e.Type, e.Payload)
        flusher.Flush()
    }

    // Subscribe to new events
    ch := h.eventBus.Subscribe(opID)
    defer h.eventBus.Unsubscribe(opID, ch)

    for {
        select {
        case event := <-ch:
            fmt.Fprintf(w, "event: %s\ndata: %s\n\n", event.Type, event.Payload)
            flusher.Flush()
            if event.Type == "complete" || event.Type == "error" {
                return
            }
        case <-r.Context().Done():
            return
        }
    }
}
```

#### SDLC Executor Instrumentation

```go
// internal/worker/sdlc_executor.go
func (e *SDLCExecutor) Execute(ctx context.Context, task *domain.WorkItem) error {
    // Emit start event
    e.emitEvent(ctx, task.OperationID, OperationEvent{
        Type:    "phase",
        Phase:   "started",
        Status:  "in_progress",
        Message: "Starting build",
    })

    // ... existing execution logic ...

    // Emit phase transitions
    for _, phase := range []string{"spec", "design", "implement", "test", "deploy"} {
        e.emitEvent(ctx, task.OperationID, OperationEvent{
            Type:    "phase",
            Phase:   phase,
            Status:  "in_progress",
            Message: fmt.Sprintf("Starting %s phase", phase),
        })

        // Execute phase...

        e.emitEvent(ctx, task.OperationID, OperationEvent{
            Type:    "phase",
            Phase:   phase,
            Status:  "complete",
            Message: fmt.Sprintf("Completed %s phase", phase),
        })
    }

    // Emit completion
    e.emitEvent(ctx, task.OperationID, OperationEvent{
        Type:    "complete",
        Status:  "success",
        Message: "Build complete",
        URL:     deployedURL,
    })

    return nil
}
```

### Phase 2 Exit Criteria

- [ ] Operations table created and migrated
- [ ] `POST /operations` starts tracked operation
- [ ] `GET /operations/{id}` returns current status
- [ ] `GET /operations/{id}/stream` returns SSE events
- [ ] SDLC executor emits phase events
- [ ] Events persist for replay on reconnect

---

## Phase 3: Blueprint → Build Integration (Week 3)

**Goal:** Connect "Build It" button to full SDLC execution.

### Blueprint to Spec Conversion

```go
// internal/service/architect_service.go
func (s *ArchitectService) Build(ctx context.Context, blueprintID uuid.UUID) (*domain.Operation, error) {
    bp, _ := s.blueprintRepo.Get(ctx, blueprintID)

    // Validate readiness
    if !bp.ReadyToBuild {
        return nil, errors.New("blueprint not ready: " + strings.Join(bp.Blockers, ", "))
    }

    // Convert to spec markdown
    spec := s.renderSpec(bp)

    // Create SDLC feature
    feature, _ := s.sdlcService.CreateFeature(ctx, bp.ProjectID, domain.FeatureRequest{
        Name:    bp.FeatureName,
        Spec:    spec,
    })

    // Link blueprint to feature
    bp.Status = "building"
    bp.BuiltFeatureSlug = feature.Slug
    s.blueprintRepo.Update(ctx, bp)

    // Start operation
    op, _ := s.orchestratorService.StartBuild(ctx, bp.ID, feature.Slug)

    return op, nil
}

func (s *ArchitectService) renderSpec(bp *domain.Blueprint) string {
    tmpl := `# Feature: {{.FeatureName}}

## Summary
{{.Summary}}

## Data Model
{{range .Sections.DataModel.Entities}}
### {{.Name}}
| Field | Type |
|-------|------|
{{range .Fields}}| {{.Name}} | {{.Type}} |
{{end}}
{{end}}

## API Endpoints
{{range .Sections.APIEndpoints.Endpoints}}
- ` + "`{{.Method}} {{.Path}}`" + ` - {{.Description}}
{{end}}

## UI Components
{{range .Sections.UIComponents.Components}}
- **{{.Name}}**: {{.Purpose}}
{{end}}
`
    // ... render template ...
}
```

### Phase 3 Exit Criteria

- [ ] `POST /blueprints/{id}/build` triggers SDLC feature creation
- [ ] Spec markdown generated from blueprint sections
- [ ] Operation created and trackable
- [ ] Blueprint status updated to "building" → "built"

---

## Phase 4: Frontend (Week 4-5)

**Goal:** Build the three-pane Studio interface.

### Week 4: Project Setup & Core Components

#### Project Structure

```
apps/studio/
├── app/
│   ├── layout.tsx
│   ├── page.tsx                    # Template selection
│   ├── auth/
│   │   └── callback/page.tsx       # OAuth callback
│   └── projects/
│       └── [id]/
│           └── page.tsx            # Workspace
├── components/
│   ├── templates/
│   │   └── TemplateCard.tsx
│   ├── workspace/
│   │   ├── ChatPane.tsx
│   │   ├── PlanPane.tsx
│   │   ├── PreviewPane.tsx
│   │   └── BuildProgress.tsx
│   └── ui/                         # shadcn components
├── lib/
│   ├── api.ts                      # rdev-api client
│   ├── sse.ts                      # SSE connection
│   └── store.ts                    # Zustand state
├── tailwind.config.ts
└── package.json
```

#### Core State Management

```typescript
// lib/store.ts
import { create } from "zustand";

interface StudioState {
	// Blueprint state
	blueprint: Blueprint | null;
	messages: Message[];

	// Operation state
	operation: Operation | null;
	events: OperationEvent[];

	// Actions
	sendMessage: (message: string) => Promise<void>;
	startBuild: () => Promise<void>;
	subscribeToOperation: (operationId: string) => void;
}

export const useStudio = create<StudioState>((set, get) => ({
	blueprint: null,
	messages: [],
	operation: null,
	events: [],

	sendMessage: async (message) => {
		const { blueprint } = get();
		const response = await api.chat(blueprint.id, message);
		set({
			blueprint: response.blueprint,
			messages: [
				...get().messages,
				{ role: "user", content: message },
				{ role: "architect", content: response.reply },
			],
		});
	},

	startBuild: async () => {
		const { blueprint } = get();
		const operation = await api.build(blueprint.id);
		set({ operation });
		get().subscribeToOperation(operation.id);
	},

	subscribeToOperation: (operationId) => {
		const eventSource = new EventSource(
			`${API_URL}/operations/${operationId}/stream`,
		);
		eventSource.onmessage = (e) => {
			const event = JSON.parse(e.data);
			set({ events: [...get().events, event] });
			if (event.type === "complete") {
				eventSource.close();
			}
		};
	},
}));
```

#### Chat Pane Component

```typescript
// components/workspace/ChatPane.tsx
export function ChatPane() {
  const { messages, sendMessage, blueprint } = useStudio();
  const [input, setInput] = useState('');

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    if (!input.trim()) return;
    await sendMessage(input);
    setInput('');
  };

  return (
    <div className="flex flex-col h-full">
      <div className="flex-1 overflow-y-auto p-4 space-y-4">
        {messages.map((msg, i) => (
          <div key={i} className={cn(
            "p-3 rounded-lg",
            msg.role === 'user' ? "bg-blue-100 ml-8" : "bg-gray-100 mr-8"
          )}>
            {msg.content}
          </div>
        ))}
      </div>

      <form onSubmit={handleSubmit} className="p-4 border-t">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Describe what you want to build..."
          className="w-full p-2 border rounded"
        />
      </form>
    </div>
  );
}
```

#### Plan Pane Component

```typescript
// components/workspace/PlanPane.tsx
export function PlanPane() {
  const { blueprint } = useStudio();

  if (!blueprint) return <EmptyState />;

  return (
    <div className="p-4 space-y-6">
      <h2 className="text-xl font-bold">{blueprint.feature}</h2>

      {/* Design References */}
      {blueprint.references?.items?.length > 0 && (
        <Section title="Design References" status="complete">
          <div className="flex gap-2 overflow-x-auto">
            {blueprint.references.items.map(ref => (
              <ReferenceCard key={ref.id} reference={ref} />
            ))}
          </div>
        </Section>
      )}

      {/* Design System (extracted from references) */}
      {blueprint.sections.designSystem?.status !== 'empty' && (
        <Section
          title="Design System"
          status={blueprint.sections.designSystem.status}
        >
          <DesignTokens tokens={blueprint.sections.designSystem} />
          {blueprint.sections.designSystem.inspirationNotes && (
            <p className="text-sm text-gray-600 mt-2 italic">
              {blueprint.sections.designSystem.inspirationNotes}
            </p>
          )}
        </Section>
      )}

      <Section
        title="Data Model"
        status={blueprint.sections.dataModel.status}
      >
        {blueprint.sections.dataModel.entities.map(entity => (
          <EntityCard key={entity.name} entity={entity} />
        ))}
      </Section>

      <Section
        title="API Endpoints"
        status={blueprint.sections.apiEndpoints.status}
      >
        {blueprint.sections.apiEndpoints.endpoints.map(ep => (
          <EndpointRow key={ep.path} endpoint={ep} />
        ))}
      </Section>

      <Section
        title="UI Components"
        status={blueprint.sections.uiComponents.status}
      >
        {blueprint.sections.uiComponents.components.map(comp => (
          <ComponentCard key={comp.name} component={comp} />
        ))}
      </Section>

      {blueprint.openQuestions.length > 0 && (
        <div className="bg-yellow-50 p-4 rounded">
          <h3 className="font-semibold text-yellow-800">Open Questions</h3>
          <ul className="list-disc list-inside">
            {blueprint.openQuestions.map(q => (
              <li key={q.id}>{q.question}</li>
            ))}
          </ul>
        </div>
      )}

      <BuildButton
        ready={blueprint.readyToBuild}
        blockers={blueprint.blockers}
      />
    </div>
  );
}

function ReferenceCard({ reference }: { reference: Reference }) {
  return (
    <div className="flex-shrink-0 w-32">
      <img
        src={reference.thumbnail}
        alt={reference.source}
        className="w-full h-20 object-cover rounded border"
      />
      <p className="text-xs text-gray-500 truncate mt-1">
        {reference.type === 'url' ? new URL(reference.source).hostname : 'Uploaded'}
      </p>
    </div>
  );
}

function DesignTokens({ tokens }: { tokens: DesignSystem }) {
  return (
    <div className="space-y-3">
      {tokens.colors?.length > 0 && (
        <div>
          <p className="text-xs font-medium text-gray-500 mb-1">Colors</p>
          <div className="flex gap-1">
            {tokens.colors.map(c => (
              <div
                key={c.name}
                className="w-6 h-6 rounded border"
                style={{ backgroundColor: c.value }}
                title={`${c.name}: ${c.value}`}
              />
            ))}
          </div>
        </div>
      )}
      {tokens.spacing?.length > 0 && (
        <div>
          <p className="text-xs font-medium text-gray-500 mb-1">Spacing</p>
          <p className="text-sm">{tokens.spacing.join('px, ')}px</p>
        </div>
      )}
      {tokens.borderRadius && (
        <div>
          <p className="text-xs font-medium text-gray-500 mb-1">Border Radius</p>
          <p className="text-sm">{tokens.borderRadius}</p>
        </div>
      )}
    </div>
  );
}
```

### Week 5: Polish & Integration

#### Preview Pane with Build Progress

```typescript
// components/workspace/PreviewPane.tsx
export function PreviewPane() {
  const { operation, events } = useStudio();
  const [previewUrl, setPreviewUrl] = useState<string>();

  // Update preview URL when build completes
  useEffect(() => {
    const completeEvent = events.find(e => e.type === 'complete');
    if (completeEvent?.url) {
      setPreviewUrl(completeEvent.url);
    }
  }, [events]);

  return (
    <div className="flex flex-col h-full">
      {/* Preview iframe */}
      <div className="flex-1 relative">
        {previewUrl ? (
          <iframe
            src={previewUrl}
            className="w-full h-full border-0"
            sandbox="allow-scripts allow-same-origin allow-forms"
          />
        ) : (
          <div className="flex items-center justify-center h-full bg-gray-50">
            <p className="text-gray-500">Preview will appear after first build</p>
          </div>
        )}
      </div>

      {/* Build progress overlay */}
      {operation && operation.status === 'running' && (
        <BuildProgress events={events} />
      )}
    </div>
  );
}

function BuildProgress({ events }: { events: OperationEvent[] }) {
  const phases = ['spec', 'design', 'implement', 'test', 'deploy'];

  return (
    <div className="absolute inset-0 bg-black/50 flex items-center justify-center">
      <div className="bg-white p-6 rounded-lg shadow-xl w-80">
        <h3 className="font-bold mb-4">Building...</h3>
        <ul className="space-y-2">
          {phases.map(phase => {
            const event = events.find(e => e.phase === phase);
            const status = event?.status || 'pending';

            return (
              <li key={phase} className="flex items-center gap-2">
                {status === 'complete' && <CheckIcon className="text-green-500" />}
                {status === 'in_progress' && <Spinner />}
                {status === 'pending' && <Circle className="text-gray-300" />}
                <span className={cn(
                  status === 'in_progress' && 'font-semibold'
                )}>
                  {phase.charAt(0).toUpperCase() + phase.slice(1)}
                </span>
              </li>
            );
          })}
        </ul>
      </div>
    </div>
  );
}
```

### Phase 4 Exit Criteria

- [ ] Template selection page renders available seeds
- [ ] Three-pane workspace renders correctly
- [ ] Chat sends messages and displays responses
- [ ] Chat supports URL/image reference attachments (drag/drop, paste)
- [ ] Plan pane updates on each chat turn
- [ ] Plan pane displays reference thumbnails
- [ ] Plan pane renders extracted design tokens
- [ ] Build progress shows SSE events
- [ ] Preview iframe loads deployed app
- [ ] Preview refreshes on build complete

---

## Phase 5: Aeries Demo (Week 6)

**Goal:** Build Aeries (social simulation world) entirely through Studio.

### Demo Script

```
1. Open Orchard Studio
2. Click "Social World" template
3. Wait for project to spawn (shows "Live" badge)

4. Chat with Architect:
   "I want agents that walk around a 2D world and have conversations"

5. Architect asks:
   "How should agents decide who to talk to? Proximity-based, or interest matching?"

6. User: "Proximity - they talk to whoever is nearby"

7. Architect asks:
   "Should conversations be visible to all users, or only when you click on an agent?"

8. User: "Visible as speech bubbles above agents"

9. Architect: "Plan looks complete. Ready to build?"

10. User: "Yes, build it"

11. Watch build progress:
    ✓ Creating spec
    ✓ Designing schema (agents, conversations, positions)
    ✓ Writing handlers (agent CRUD, movement, chat)
    → Running tests
    ○ Deploying

12. Build completes. Preview refreshes.

13. See agents walking and chatting in the preview.
```

### Success Criteria

- [ ] Social World template exists and provisions correctly
- [ ] Architect conversation produces coherent simulation spec
- [ ] Build executes without manual intervention
- [ ] Deployed app shows agents with movement and chat
- [ ] Total time from spawn to working demo < 15 minutes

---

## Phase 6: Platform Services (Parallel Track)

**Goal:** Add shared platform services that projects can opt into.

This work runs **parallel to Phases 1-5**. It focuses on the "upgrade existing projects" use case first.

### Service Rollout Order

Build infrastructure with the simplest service, then add complexity:

```
Logging ──► Email ──► Stats ──► Auth
   │          │         │        │
   │          │         │        └── Complex (middleware, user flows)
   │          │         └── Frontend SDK + backend events
   │          └── Simple API, clear success/failure
   └── Pure infrastructure, no user code changes
```

### Phase 6a: Service Infrastructure (Week 3-4)

**Goal:** Build the provisioning and injection infrastructure.

```go
// internal/port/platform_provisioner.go
type PlatformProvisioner interface {
    Provision(ctx context.Context, req ProvisionRequest) (*ProvisionResult, error)
    Verify(ctx context.Context, projectID string, creds map[string]string) error
    Deprovision(ctx context.Context, projectID string) error
}
```

**Deliverables:**

1. `project_services` table and domain model
2. Service catalog (YAML definition of available services)
3. Provisioner interface and base implementation
4. Credential encryption/storage
5. K8s secret injection
6. Integration PR creation flow
7. `POST /projects/{id}/services` endpoint

### Phase 6b: Logging Service (Week 4)

**Goal:** First concrete service - ship logs to centralized Loki.

```go
// internal/adapter/loki/provisioner.go
type LokiProvisioner struct {
    lokiURL string
}

func (p *LokiProvisioner) Provision(ctx context.Context, req ProvisionRequest) (*ProvisionResult, error) {
    tenantID := fmt.Sprintf("project-%s", req.ProjectID)
    return &ProvisionResult{
        Credentials: map[string]string{
            "LOKI_URL":       p.lokiURL,
            "LOKI_TENANT_ID": tenantID,
        },
    }, nil
}
```

**Integration templates:**

- Go: Update slog config to ship to Loki
- Node: Add pino-loki transport

**Exit Criteria:**

- [ ] `POST /projects/{id}/services` with `type: logging` works
- [ ] Credentials injected into K8s secrets
- [ ] Integration PR created with logger config
- [ ] After merge, logs appear in Loki

### Phase 6c: Email Service (Week 5)

**Goal:** Transactional email via Resend.

```go
// internal/adapter/resend/provisioner.go
type ResendProvisioner struct {
    masterKey string
}

func (p *ResendProvisioner) Provision(ctx context.Context, req ProvisionRequest) (*ProvisionResult, error) {
    // Create scoped API key (or use master with project tracking)
    apiKey := p.createAPIKey(req.ProjectName)
    return &ProvisionResult{
        Credentials: map[string]string{
            "RESEND_API_KEY": apiKey,
        },
        Config: map[string]string{
            "RESEND_FROM_DOMAIN": fmt.Sprintf("%s.threesix.ai", req.ProjectName),
        },
    }, nil
}
```

**Integration templates:**

- Go: `internal/email/client.go` with Resend SDK
- Node: `lib/email.ts` with Resend SDK

**Exit Criteria:**

- [ ] Email service can be added to existing projects
- [ ] API key provisioned and injected
- [ ] Integration PR includes email client code
- [ ] Test email sends successfully

### Phase 6d: Stats Service (Week 5-6)

**Goal:** Product analytics via PostHog.

**Integration templates:**

- Node: AnalyticsProvider component, posthog-js setup
- Go: Backend event tracking

**Exit Criteria:**

- [ ] PostHog project created per rdev project
- [ ] Frontend tracking code added
- [ ] Events flowing to PostHog dashboard

### Phase 6e: Auth Service (Week 6-7)

**Goal:** User authentication via Clerk.

This is the most complex service - affects routes, middleware, user model.

**Integration templates:**

- Middleware for protected routes
- ClerkProvider component
- User model integration

**Exit Criteria:**

- [ ] Clerk application created per project
- [ ] Auth middleware added
- [ ] Sign-in/sign-up flows working

---

## Phase 7: Dual Environments (Week 6-7)

**Goal:** Add staging/production environment separation.

**Depends on:** Phase 6 (services need to be environment-aware)

### Week 6: Infrastructure

**Deliverables:**

1. DNS: Create both `staging.X` and `X` records per project
2. K8s: Two deployments per project (or two namespaces)
3. Database: Separate staging/production databases in CockroachDB
4. Secrets: Environment-scoped secret management

```sql
ALTER TABLE projects ADD COLUMN environments JSONB NOT NULL DEFAULT '{
  "staging": {"enabled": true, "deployed_at": null},
  "production": {"enabled": false, "deployed_at": null}
}';
```

### Week 7: Publish Flow

**Deliverables:**

1. `POST /projects/{id}/publish` endpoint
2. Publish flow: validate → provision → migrate → deploy → verify
3. Studio UI: Publish button, environment switcher

```
POST /projects/{id}/publish
{
  "fromEnvironment": "staging",
  "toEnvironment": "production"
}
```

**Exit Criteria:**

- [ ] New projects get both environments
- [ ] "Build It" deploys to staging only
- [ ] "Publish" promotes staging to production
- [ ] Services have separate credentials per environment
- [ ] Environment switcher in Preview pane

---

## Milestone Summary

| Phase                | Duration | Key Deliverable                                 |
| -------------------- | -------- | ----------------------------------------------- |
| 0. Engine            | Now      | `slackpath-1` works                             |
| 1. Blueprint API     | Week 1-2 | Chat endpoint + storage + **design references** |
| 2. Operations        | Week 2-3 | SSE streaming + DB tracking                     |
| 3. Integration       | Week 3   | Blueprint → SDLC → Build                        |
| 4. Frontend          | Week 4-5 | Three-pane Studio UI (with reference display)   |
| 5. Demo              | Week 6   | Aeries via Studio                               |
| 6. Platform Services | Week 3-7 | Logging → Email → Stats → Auth (parallel track) |
| 7. Dual Environments | Week 6-7 | Staging + Production, Publish flow              |

**Total:** ~7 weeks to full platform

### Parallel Tracks

```
Week:  1    2    3    4    5    6    7
       │    │    │    │    │    │    │
       ├────┴────┴────┴────┴────┴────┤  Studio Track (Phases 0-5)
       │                              │
       │         ├────┴────┴────┴────┤  Services Track (Phase 6)
       │         │    │    │    │    │
       │         │    │    │    ├────┤  Environments (Phase 7)
```

- **Studio Track:** Core product experience (Blueprint → Build → Preview)
- **Services Track:** Platform capabilities (Logging → Email → Stats → Auth)
- **Environments Track:** Staging/Production separation (depends on Services)

**Design Reference Flow:** Integrated into Phase 1 (backend) and Phase 4 (frontend).

**Service Rollout:** Each service builds on the infrastructure. Start with Logging (simplest), end with Auth (most complex).

---

## Definition of Done: Full Platform

```
┌─────────────────────────────────────────────────────────────────────────────────┐
│  User clicks "Social World" template                                            │
│                           ↓                                                     │
│  Project spawns: K8s namespace, DB, git repo, DNS                               │
│  Live URL: https://aeries-demo.threesix.ai (shows skeleton)                     │
│                           ↓                                                     │
│  User chats with Architect to define agent simulation                           │
│  Plan pane shows data model, endpoints, components                              │
│                           ↓                                                     │
│  User clicks "Build It"                                                         │
│  Progress shows: spec → design → implement → test → deploy                      │
│                           ↓                                                     │
│  Build completes. Preview refreshes.                                            │
│  User sees agents walking and chatting.                                         │
│                           ↓                                                     │
│  User continues: "Add a weather system"                                         │
│  Architect updates plan. Another build cycle.                                   │
│  Weather appears in preview.                                                    │
│                           ↓                                                     │
│  🎉 Software built through conversation.                                        │
└─────────────────────────────────────────────────────────────────────────────────┘
```
