2026 03 19 Platform Generation Quality V3

Documentation for Plans

Platform Generation Quality v3 Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Enrich the generation pipeline to produce production-ready platforms using the full libs/core SDK (workflows, filters, entity lookups, module registration) with backend schema registration and seed data.

Architecture: 6 changes across the ANALYZE→EXECUTE→INTEGRATE pipeline. Prompts teach the LLM about building blocks. ANALYZE detects which blocks each entity needs. EXECUTE passes that metadata. INTEGRATE registers schemas and seeds data. The platform-base-template gets the missing workflow-engine library. The Generations tab bug is fixed separately.

Tech Stack: Angular 20+, NestJS (backend-vitto-api), MongoDB, TypeScript, LLM prompts (markdown templates)

Spec: docs/superpowers/specs/2026-03-19-platform-generation-quality-v3-design.md


File Map

Files to Create

File Responsibility
repo-templates/platform-base-template/libs/core/workflow-engine/ Copy of workflow-engine lib for platform template

Files to Modify

File Lines Change
repo-templates/platform-base-template/tsconfig.base.json ~44 Add @libs-core/workflow-engine path mapping
APIs/backend-vitto-api/src/app/module-generation/module-generation.types.ts ~203-254 Add BuildingBlockSpec, SchemaSpec, SeedDataSpec types
APIs/backend-vitto-api/src/prompts/module-gen/system-analyze.prompt.md ~33-76 Add buildingBlocks to entity output schema
APIs/backend-vitto-api/src/prompts/module-gen/implement.prompt.md ~22-24, ~623-646 Add 5 building block sections + schemas/seedData output
APIs/backend-vitto-api/src/app/module-generation/phases/execute.phase.ts ~371-401 Pass buildingBlocks metadata to LLM prompt
APIs/backend-vitto-api/src/app/module-generation/phases/integrate.phase.ts ~31-145 Add schema registration + seed data POST after validation
libs/libs-structural/vitto-studio/src/lib/services/generation.service.ts TBD Fix signal population from SSE events
libs/libs-structural/vitto-studio/src/lib/panels/generations-panel.component.ts ~55-57 Fix isEmpty computed / data flow

Task 0: JIT Compatibility Blocker Check

Purpose: Verify that SDK components using signal input() render correctly in the rspack WASM preview (Angular JIT). This is a blocker — if they fail, Tasks 4-5 need fallback patterns.

Files:

Read the three component files above. Confirm whether they use input() (signal) or @Input() (decorator). Record findings:

Use Playwright to navigate to localhost:6200/space/chrome-space-1/universe/tortuga/studio. In the Code tab, create a minimal test component that imports one of these components. Trigger a compile and check the browser console for JIT errors.

Alternatively, check if Angular JIT (v20+) now supports signal inputs — this was a limitation in earlier versions but may have been resolved.

If JIT supports signal inputs: proceed with all building blocks as designed. If JIT fails: for each failing component, design an inline fallback:

Record decision in the spec's risk table.


Task 1: Add workflow-engine to Platform-Base-Template

Purpose: The template's tsconfig.base.json is missing @libs-core/workflow-engine. Without it, generated code importing WorkflowDefinition or lib-status-transition won't compile.

Files:

Open /Users/fguijarro/developer/repos-gspace/repo-templates/platform-base-template/tsconfig.base.json. In the paths section (~line 44, near the other @libs-core/* entries), add:

"@libs-core/workflow-engine": ["libs/core/workflow-engine/src/index.ts"],
"@libs-core/workflow-engine/*": ["libs/core/workflow-engine/src/*"]

Copy the entire directory from gspace1:

cp -r /Users/fguijarro/developer/repos-gspace/gspace1/libs/core/workflow-engine /Users/fguijarro/developer/repos-gspace/repo-templates/platform-base-template/libs/core/workflow-engine
cd /Users/fguijarro/developer/repos-gspace/repo-templates/platform-base-template
cat libs/core/workflow-engine/src/index.ts

Confirm it exports WorkflowEngineService, GgsStatusTransitionComponent, etc.

grep -r "WorkflowDefinition" /Users/fguijarro/developer/repos-gspace/repo-templates/platform-base-template/libs/core/shared-models/src/

If WorkflowDefinition is NOT in the template's shared-models, copy the workflow types file too.

git add repo-templates/platform-base-template/tsconfig.base.json repo-templates/platform-base-template/libs/core/workflow-engine/
git commit -m "[S244.1] feat: add workflow-engine library to platform-base-template"

Task 2: Add Types to module-generation.types.ts

Purpose: Define TypeScript types for building block metadata, schema specs, and seed data that flow through the pipeline.

Files:

Read /Users/fguijarro/developer/repos-gspace/gspace1/APIs/backend-vitto-api/src/app/module-generation/module-generation.types.ts, specifically lines 200-260 (around ModuleGenAnalyzeResult and EntitySpec).

After the EntitySpec interface (around line 230), add:

export interface ForeignKeySpec {
  field: string;
  targetEntity: string;
  targetModule: string; // 'same' if within this module, or module slug
}

export interface BuildingBlockSpec {
  hasWorkflow: boolean;
  workflowField?: string;
  workflowStates?: string[];
  foreignKeys: ForeignKeySpec[];
  hasMedia: boolean;
  filterableFields: string[];
}

Find the EntitySpec interface and add:

buildingBlocks?: BuildingBlockSpec;

After the GeneratedFileSpec interface (~line 490), add:

export interface SchemaFieldSpec {
  name: string;
  type: 'string' | 'number' | 'boolean' | 'date' | 'object' | 'array' | 'reference';
  required?: boolean;
  label?: string;
  validation?: {
    enum?: string[];
    min?: number;
    max?: number;
    minLength?: number;
    maxLength?: number;
  };
}

export interface SchemaSpec {
  collection: string;
  displayName: string;
  fields: SchemaFieldSpec[];
}

export interface SeedDataSpec {
  [collection: string]: Record<string, unknown>[];
}

Find the ModuleGenExecuteResult interface (NOT GeneratedModule — that's the visual-path type). Add:

schemas?: SchemaSpec[];
seedData?: SeedDataSpec;

These fields hold the LLM-generated schema and seed data extracted alongside files during the EXECUTE phase.

CRITICAL: The normalizeAnalyzeResult() function explicitly maps each entity to only name, description, fields, and relationships. Any buildingBlocks from the LLM response is silently dropped. Find this function and add buildingBlocks to the entity mapping:

// In normalizeAnalyzeResult, find the coreEntities mapping:
coreEntities: (raw.coreEntities || []).map((e: any) => ({
  name: e.name,
  description: e.description,
  fields: e.fields || e.attributes || [],
  relationships: e.relationships || [],
  buildingBlocks: e.buildingBlocks || undefined,  // ADD THIS LINE
})),

Without this change, the entire buildingBlocks pipeline is broken — the ANALYZE LLM produces the metadata, the normalizer strips it, and EXECUTE never sees it.

git add APIs/backend-vitto-api/src/app/module-generation/module-generation.types.ts
git commit -m "[S244.2] feat: add building block, schema, and seed data types to generation pipeline"

Task 3: Enrich system-analyze.prompt.md

Purpose: Teach the ANALYZE phase LLM to detect which building blocks each entity needs (workflow, foreign keys, media, filterable fields).

Files:

Read /Users/fguijarro/developer/repos-gspace/gspace1/APIs/backend-vitto-api/src/prompts/module-gen/system-analyze.prompt.md (84 lines).

In the output JSON format section (after the relationships array in the coreEntities object), add:

"buildingBlocks": {
  "hasWorkflow": true,
  "workflowField": "status",
  "workflowStates": ["pending", "in-progress", "completed"],
  "foreignKeys": [
    { "field": "categoryId", "targetEntity": "Category", "targetModule": "same" }
  ],
  "hasMedia": false,
  "filterableFields": ["status", "priority", "categoryId"]
}

After the output format section, before the validation rules, add:

## Building Block Detection Rules

For each entity, analyze its fields to populate `buildingBlocks`:

1. **Workflow detection**: If the entity has a field named `status` (or `state`, `phase`) with enum/string values representing lifecycle stages → set `hasWorkflow: true`, `workflowField` to the field name, `workflowStates` to the enum values
2. **Foreign key detection**: If a field name ends with `Id` (e.g., `categoryId`, `assigneeId`, `parentId`) AND references another entity in the system → add to `foreignKeys[]` with `targetEntity` (PascalCase) and `targetModule` ("same" if same module, or the target module's slug)
3. **Media detection**: If a field name contains `image`, `photo`, `media`, `file`, `attachment`, `avatar`, `thumbnail`, or `logo` → set `hasMedia: true`
4. **Filterable fields**: Include ALL fields that have enum/select values, plus any `*Id` foreign key fields, plus `createdAt`/`updatedAt` date ranges. Minimum 2 filterable fields per entity.
git add APIs/backend-vitto-api/src/prompts/module-gen/system-analyze.prompt.md
git commit -m "[S244.3] feat: add building block detection to ANALYZE phase prompt"

Task 4: Enrich implement.prompt.md — Building Block Patterns

Purpose: Teach the EXECUTE phase LLM to generate workflow definitions, filter definitions, entity lookups, module registration, and seed data — using verified component APIs.

Files:

Read /Users/fguijarro/developer/repos-gspace/gspace1/APIs/backend-vitto-api/src/prompts/module-gen/implement.prompt.md. Focus on:

After the existing golden examples section (and before the output format), insert a new section titled ## BUILDING BLOCK PATTERNS. This section is CONDITIONAL — only relevant patterns are included based on the entity's buildingBlocks metadata in the spec JSON.

## BUILDING BLOCK PATTERNS

Use these patterns when the entity spec's `buildingBlocks` metadata indicates the need. Check `buildingBlocks.hasWorkflow`, `buildingBlocks.foreignKeys`, `buildingBlocks.filterableFields`, etc.

### Workflow Definition (when buildingBlocks.hasWorkflow === true)

Generate `workflow/{{entity}}-workflow.definition.ts`:

\`\`\`typescript
import { WorkflowDefinition } from '@libs-core/shared-models';

export const {{ENTITY}}_STATUS_WORKFLOW: WorkflowDefinition = {
  id: '{{entity}}-status',
  entityType: '{{entity}}',
  statusField: '{{buildingBlocks.workflowField}}',
  states: [
    // Map each value in buildingBlocks.workflowStates to a state object.
    // First state is type: 'initial', last is type: 'final', rest are 'intermediate'.
    // Colors: initial→'warning', intermediate→'info', final→'success', cancelled→'neutral'
  ],
  transitions: [
    // Generate logical transitions between states.
    // Each transition needs: id, from, to, label, permissions: ['{{entities}}:update']
    // Terminal/destructive transitions should have requireConfirmation: true
  ],
};

export function provide{{Entity}}Workflow() {
  return [{ provide: '{{ENTITY}}_WORKFLOW', useValue: {{ENTITY}}_STATUS_WORKFLOW }];
}
\`\`\`

In detail component template, use:
\`\`\`html
<lib-status-transition
  [entityType]="'{{entity}}'"
  [entity]="crud.entity()"
  (transitionCompleted)="onStatusChanged($event)"
/>
\`\`\`

In routes, inject the workflow provider:
\`\`\`typescript
{
  path: '',
  providers: [provide{{Entity}}Workflow()],
  children: [...]
}
\`\`\`

### Filter Definitions (ALWAYS — every list component)

In the list component, add `filterDefinitions` to `useCrudList` config:

\`\`\`typescript
filterDefinitions: [
  // For each field in buildingBlocks.filterableFields:
  // - enum fields → type: 'select' with options array
  // - *Id foreign key fields → type: 'select' with options loaded from related entity
  // - date fields → type: 'date-range'
  // - boolean fields → type: 'select' with [{ value: 'true', label: 'Yes' }, { value: 'false', label: 'No' }]
],
syncWithUrl: true,
\`\`\`

In the list template, place filters ABOVE the shell:
\`\`\`html
<ggs-filter-panel
  [filters]="crud.filterDefinitions()"
  [activeFilters]="crud.activeFilters()"
  (filtersChange)="crud.setFilters($event)"
  (clearAll)="crud.clearFilters()"
/>
<ggs-filter-chips
  [activeFilters]="crud.activeFilters()"
  (removeFilter)="crud.removeFilter($event.field)"
  (clearAll)="crud.clearFilters()"
/>
\`\`\`

Import: `GgsFilterPanelComponent, GgsFilterChipsComponent` from `@libs-core/ui-framework`.

### Entity Lookup for Foreign Keys (when buildingBlocks.foreignKeys is non-empty)

For each foreign key in `buildingBlocks.foreignKeys`, generate a lookup function and template in the FORM component:

\`\`\`typescript
// For foreignKey { field: 'categoryId', targetEntity: 'Category' }
readonly categoryLoadFn = (query: string): Observable<Category[]> =>
  this.categoryApi.getList({ page: 1, limit: 20, search: query }).pipe(map(r => r.items));

readonly categoryDisplayFn = (cat: Category): string => cat.name;

readonly categoryInitialLoadFn = (): Observable<Category[]> =>
  this.categoryApi.getList({ page: 1, limit: 20 }).pipe(map(r => r.items));

onCategorySelected(category: Category | null): void {
  this.crud.form().patchValue({ categoryId: category?.id ?? '' });
}
\`\`\`

Template:
\`\`\`html
<ggs-entity-lookup
  [loadFn]="categoryLoadFn"
  [displayFn]="categoryDisplayFn"
  [initialLoadFn]="categoryInitialLoadFn"
  placeholder="Search categories..."
  (selectionChange)="onCategorySelected($event)"
/>
\`\`\`

Import: `GgsEntityLookupComponent` from `@libs-core/ui-framework`.

### Module Registration (ALWAYS)

Generate `{{module}}.module.ts`:

\`\`\`typescript
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { ModuleDecorator, BrowserModuleContract, ModuleManifest } from '@libs-core/module-base';
import { {{ModuleName}}Routes } from './{{module}}.routes';

@ModuleDecorator({
  id: '{{moduleSlug}}',
  version: '1.0.0',
  name: '{{Module Name}}',
  description: '{{description}}',
  basePath: '{{moduleSlug}}',
  repositories: [],
  permissions: [
    // Generate 5 standard CRUD permissions: list, read, create, update, delete
    // Format: { id: '{{entities}}:action', name: '{{entities}}:action', resource: '{{entities}}', action: 'action' }
  ],
  navigation: {
    menuItems: [{
      id: '{{moduleSlug}}-main',
      label: '{{Entities}}',
      icon: '/* appropriate FontAwesome icon */',
      route: 'list',
      order: 100,
      permissions: ['{{entities}}:list'],
    }],
  },
  tags: ['{{moduleSlug}}', 'crud'],
})
@Injectable({ providedIn: 'root' })
export class {{ModuleName}}Module implements BrowserModuleContract {
  readonly manifest: ModuleManifest = {
    id: '{{moduleSlug}}',
    version: '1.0.0',
    name: '{{Module Name}}',
    description: '{{description}}',
    basePath: '{{moduleSlug}}',
    permissions: [/* same as decorator */],
    embeddableComponents: [],
  };

  readonly routes = {{ModuleName}}Routes;

  configure<T = Record<string, unknown>>(_config: T): Observable<void> { return of(void 0); }
  initialize(): Observable<void> { return of(void 0); }
  start(): Observable<void> { return of(void 0); }
  stop(): Observable<void> { return of(void 0); }
  destroy(): Observable<void> { return of(void 0); }
}
\`\`\`

Find the output format JSON section (~lines 623-646). Add two new top-level fields to the expected output:

{
  "files": [...],
  "summary": {...},
  "schemas": [
    {
      "collection": "{{entities}}",
      "displayName": "{{Entities}}",
      "fields": [
        { "name": "fieldName", "type": "string|number|boolean|date|object|array|reference", "required": true, "label": "Display Label" },
        { "name": "status", "type": "string", "required": true, "label": "Status", "validation": { "enum": ["pending", "active"] } }
      ]
    }
  ],
  "seedData": {
    "{{entities}}": [
      { "_id": "seed-{{entity}}-001", "name": "Example Record 1", "status": "pending" },
      { "_id": "seed-{{entity}}-002", "name": "Example Record 2", "status": "active" }
    ]
  }
}

Add instruction: "Generate 3-5 realistic seed records per entity. Each seed record MUST include a stable _id field with format seed-{entity}-NNN. Use realistic domain-appropriate data, not lorem ipsum."

In the type mapping rules section (~line 652), add:

## Schema Field Type Rules (for schemas[] output)
- Use LOWERCASE types only: 'string', 'number', 'boolean', 'date', 'object', 'array', 'reference'
- Enum fields use type: 'string' with validation.enum (NOT type: 'enum')
- Date fields use type: 'date' (NOT 'Date')
- Foreign key fields use type: 'reference'
git add APIs/backend-vitto-api/src/prompts/module-gen/implement.prompt.md
git commit -m "[S244.4] feat: add building block patterns and schema/seed output to implement prompt"

Task 5: Pass Building Block Metadata to EXECUTE Phase

Purpose: Wire the ANALYZE phase's buildingBlocks output into the EXECUTE phase's LLM prompt so the LLM knows which patterns to apply per entity.

Files:

Read /Users/fguijarro/developer/repos-gspace/gspace1/APIs/backend-vitto-api/src/app/module-generation/phases/execute.phase.ts, lines 360-420.

Find where specJson is constructed (around line 389). Before the this.promptBuilder.buildModuleGenPrompt() call, enrich the spec with building block data:

// Enrich entity specs with building block metadata from ANALYZE phase
const enrichedSpec = {
  ...moduleSpec,
  entities: (moduleSpec.entities || []).map(entity => {
    const analyzeEntity = session.analyzeResult?.coreEntities?.find(
      ce => ce.name === entity.name
    );
    return {
      ...entity,
      buildingBlocks: analyzeEntity?.buildingBlocks || {
        hasWorkflow: false,
        foreignKeys: [],
        hasMedia: false,
        filterableFields: [],
      },
    };
  }),
};
const specJson = JSON.stringify(enrichedSpec, null, 2);

Check that the session object passed to execute() contains analyzeResult from the ANALYZE phase. Look at the phase executor (~`module-gen-phase.executor.ts`) to confirm the session accumulates results.

If the session doesn't carry analyzeResult, trace how phase results are stored and retrieve it from the session's checkpoint data.

CRITICAL: The extractCodeFiles() method only extracts the files array from the LLM JSON response. schemas and seedData are discarded. Find extractCodeFiles() in execute.phase.ts and update it to also capture these fields.

After parseLlmJson() succeeds and the parsed object is available, extract:

// After parsing the LLM response JSON:
const parsedOutput = parseLlmJson(responseContent);

// Extract files (existing logic)
const files = parsedOutput.files || [];

// Extract schemas and seedData (NEW)
const schemas = parsedOutput.schemas || [];
const seedData = parsedOutput.seedData || {};

Then attach these to the ModuleGenExecuteResult:

// When building the execute result:
executeResult.schemas = schemas;
executeResult.seedData = seedData;
git add APIs/backend-vitto-api/src/app/module-generation/phases/execute.phase.ts
git commit -m "[S244.5] feat: pass building block metadata and extract schemas/seed from EXECUTE phase"

Task 6: Seed Data in INTEGRATE Phase

Purpose: After code generation and validation, seed initial data so generated platforms have real data on first load. Note: schema registration is already handled by BackendSetupPhaseService (runs between EXECUTE and INTEGRATE). This task adds ONLY seed data seeding.

Important context:

Files:

Read both files. From backend-setup.phase.ts, note:

Add to the constructor of IntegratePhaseService:

import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import { firstValueFrom } from 'rxjs';

// In constructor (follow BackendSetupPhaseService's pattern):
constructor(
  private readonly promptBuilder: PromptBuilderService,
  private readonly providerFactory: ProviderFactoryService,
  private readonly httpService: HttpService,
  private readonly configService: ConfigService,
) {}

After the existing validation/repair logic, before the return, add seed data seeding. Use the method's actual parameters (request and executeResult):

// Seed initial data with backend-api
const spaceId = request.spaceId;
const universeId = request.universeId;
const apiBaseUrl = this.configService.get('BACKEND_API_URL', 'http://localhost:3000');

if (spaceId && universeId && executeResult.seedData) {
  for (const [collection, records] of Object.entries(executeResult.seedData)) {
    if (records?.length) {
      try {
        await firstValueFrom(
          this.httpService.post(
            `${apiBaseUrl}/api/spaces/${spaceId}/universes/${universeId}/data/${collection}/seed`,
            { records }
          )
        );
        this.logger.log(`Seeded ${records.length} records for ${collection}`);
      } catch (err) {
        this.logger.warn(`Seed data failed for ${collection}: ${err.message}`);
        // Non-blocking — seed failure shouldn't stop generation
      }
    }
  }
}

Check that HttpModule from @nestjs/axios is imported in the NestJS module that provides IntegratePhaseService. If not, add it. Also check ConfigModule.

git add APIs/backend-vitto-api/src/app/module-generation/phases/integrate.phase.ts
git commit -m "[S244.6] feat: seed initial data in INTEGRATE phase"

Task 7: Fix Generations Tab

Purpose: The Generations tab shows "No generations yet" after a successful generation. Trace the data flow and fix.

Files:

Read /Users/fguijarro/developer/repos-gspace/gspace1/libs/libs-structural/vitto-studio/src/lib/panels/generations-panel.component.ts. Understand what signals it reads and what conditions trigger the empty state.

Read /Users/fguijarro/developer/repos-gspace/gspace1/libs/libs-structural/vitto-studio/src/lib/services/generation.service.ts. Focus on:

Read /Users/fguijarro/developer/repos-gspace/gspace1/libs/libs-structural/vitto-studio/src/lib/services/studio-generation-listener.service.ts. Find:

Map the complete flow:

  1. Backend emits module-gen.completed SSE event with modules data
  2. StudioGenerationListenerService receives it → does it call GenerationService.addGeneration()?
  3. GenerationService.generations signal → does it update?
  4. GenerationsPanel.isEmpty() → does it recompute?

Identify where the chain breaks.

Based on findings, apply the appropriate fix. Common causes:

Navigate to localhost:6200/space/chrome-space-1/universe/tortuga/studio, switch to Generations tab, and confirm:

git add <specific-files-changed>
git commit -m "[S244.7] fix: populate Generations tab with completed generation data"

Task 8: End-to-End Verification

Purpose: Run a full generation through Vitto and verify all new features work together.

Files: None to modify — this is a verification task.

# Check ports 3000 (backend-api) and 3003 (backend-vitto-api)
lsof -i :3000 | head -5
lsof -i :3003 | head -5

If not running, start them via yarn serve:backend-api and yarn serve:backend-vitto.

Navigate to localhost:6200/space/chrome-space-1/universe/tortuga/studio. In the Vitto chat, ask:

"Generate a task management system with tasks (name, description, status: pending/in-progress/completed, priority: low/medium/high, assigneeId, dueDate) and categories (name, description, color)"

After generation completes, check the Code tab. Verify ALL 10 success criteria:

  1. API services extend UniverseDataCrudService<T> (not raw UniverseDataService)
  2. Module file exists with @ModuleDecorator + BrowserModuleContract
  3. Workflow definition exists for Task entity (has status field)
  4. List component has filterDefinitions for status and priority
  5. Form component has GgsEntityLookupComponent for categoryId foreign key
  6. Seed data file exists with 3-5 realistic records per entity
  7. All files follow the expected directory structure
  8. No any types, no Promises, no Material imports

Check that the preview shows "Compiled (N assets, Xms)" and renders the modules with tabs/navigation.

curl -s http://localhost:3000/api/spaces/<spaceId>/universes/<universeId>/data/schemas | jq .

Confirm schemas for tasks and categories are registered.

curl -s -X POST http://localhost:3000/api/spaces/<spaceId>/universes/<universeId>/data/tasks/query \
  -H 'Content-Type: application/json' \
  -d '{"page": 1, "pageSize": 10}' | jq .

Confirm 3-5 seed records exist.

In the live preview:

  1. Click "Tasks" in navigation
  2. Verify seed data appears in the list with filter panel
  3. Click "Create Task" — verify form has entity lookup for category
  4. Create a new task — verify it appears in the list
  5. Click a task — verify detail view shows status transition component
  6. Edit a task — verify form populates correctly
  7. Delete a task — verify it disappears from list

Switch to the Generations tab. Confirm it shows the completed generation with module names, timestamp, and status.