S236 Platform Generation Pipeline Fixes
Documentation for Sessions
S236 — Platform Generation Pipeline Fixes
Date: March 18-19, 2026 Session: 236 Status: Partial (code changes complete, pending live testing) Branch: session-236-237 (shared with concurrent S237)
What Was Wrong
When a user asked Vitto to generate a platform (e.g., "Build me a Parental Management Platform with points, tasks, and rewards"), the generation pipeline ran successfully — 5 modules, 131 files, quality score 70 — but the Live Preview never showed the platform. The user saw a blank "Ready when you are" screen even after refreshing.
Additionally, the Generations panel showed "No generations yet" despite the code existing in GitLab, and the Review panel showed "No pending files to review".
In simple terms: the platform was built but the user couldn't see it.
Timeline of Investigation & Fixes
Phase 1: Understanding the Full Pipeline (Analysis)
Before making any changes, we traced the entire generation flow end-to-end:
- User asks Vitto → Vitto calls
start_module_generationtool - Backend runs 6-phase pipeline (ENHANCE → ANALYZE → DECOMPOSE → PLAN → EXECUTE → INTEGRATE → DEPLOY)
- DEPLOY phase commits code to GitLab on a feature branch and creates a generation manifest
- SSE events flow from backend → plugin → chat engine → dock shell → generation listener
- Generation listener refreshes VFS (Virtual File System) from GitLab
- Preview service compiles VFS content with rspack and shows it in an iframe
We identified 5 distinct failures in this chain, each compounding the others.
Phase 2: Fix #1 — SSE Event Race Condition (Backend)
The Problem: The "generation completed" notification sometimes never reached the user's browser.
Why it happened: The backend set status = 'completed' and then saved a checkpoint to the database (which takes time). During that database save, a polling loop detected the completion, disconnected the event stream, and then the "completed" notification tried to send — but the stream was already disconnected.
The Fix: We now send the notification FIRST, before marking the session as completed. This guarantees the notification goes out while the stream is still connected.
File: APIs/backend-vitto-api/src/app/module-generation/phases/module-gen-phase.executor.ts
Phase 3: Fix #2 — Preview Compiled Too Early (Frontend)
The Problem: Even when the "completed" notification arrived, the preview showed "No modules generated yet."
Why it happened: After receiving the completion notification, the system started loading module files from GitLab (takes 1-5 seconds). But it also immediately told the preview to compile — before the files had actually loaded. The preview looked at the file system, found nothing, and showed an error.
The Fix: The preview now waits for all files to finish loading from GitLab before attempting to compile. We use a proper "all files loaded" signal instead of firing immediately.
File: libs/libs-structural/vitto-studio/src/lib/services/studio-generation-listener.service.ts
Phase 4: Fix #3 — One-Time Subscription Bug (Frontend)
The Problem: Even with correct timing, the "files loaded" signal was being ignored after the first page load.
Why it happened: The code had a take(1) operator that means "listen only once, then stop forever." On a fresh page load, the first signal was "0 modules loaded" (correct — the platform doesn't exist yet). After that, the listener stopped. When the generation completed and modules WERE loaded, the signal fired again but nobody was listening.
The Fix: Removed the take(1) so the listener stays active for the entire page session.
File: libs/libs-structural/vitto-studio/src/lib/components/studio-dock-shell/studio-dock-shell.component.ts
Phase 5: Fix #4 — Reading from the Wrong Branch (Frontend + Backend)
The Problem: Even with all the above fixes, the file system was loading from an empty branch.
Why it happened: Generated code goes to a feature branch (e.g., feature/generation-1773866366723). A separate "activation" process merges it to the develop branch. But activation requires a "generation manifest" to be created — and the manifest wasn't being created (see Fix #5). So develop stayed empty, and the preview had nothing to show.
The Fix: The backend now includes the feature branch name in the completion notification. The frontend switches to that branch before loading files. This bypasses the broken manifest → activation → merge chain entirely.
Files:
- Backend:
module-gen-phase.executor.ts(includesfeatureBranchin event) - Plugin:
module-gen.plugin.ts(forwardsfeatureBranch) - Frontend:
studio-generation-listener.service.ts(switches VFS to feature branch) - Frontend:
vfs-sync.service.ts(newswitchToBranch()method)
Phase 6: Fix #5 — Silent Manifest Failure (Backend Diagnostics)
The Problem: The Generations panel shows "No generations yet" — the manifest is never created.
Why it happened: The API activity logs show that the POST /generations endpoint was never called during the DEPLOY phase. The manifest creation code is gated on successful GitLab commit AND uses optional chaining (backendApiClient?.) which silently returns undefined if the service is null. There was zero logging when this happened.
The Fix: Added comprehensive error-level logging at every point where the manifest creation can be silently skipped:
- When
gitConfigis null (preflight didn't initialize GitLab) - When
backendApiClientis null (DI wiring issue) - When the HTTP call fails (with full endpoint and branch info)
- When the call returns undefined (optional chaining swallowed the result)
This will identify the exact root cause on the next generation run.
File: APIs/backend-vitto-api/src/app/module-generation/phases/module-gen-phase.executor.ts
Summary of All Changes
| File | What Changed | Why |
|---|---|---|
module-gen-phase.executor.ts |
Emit events before status change; include featureBranch; add diagnostic logging | Fixes SSE race condition, enables branch bypass, diagnoses manifest failure |
module-gen.plugin.ts |
Forward featureBranch field in SSE events |
Passes branch info from backend to frontend |
studio-generation-listener.service.ts |
Wait for VFS completion; switch to feature branch | Prevents empty preview; reads from correct branch |
studio-dock-shell.component.ts |
Remove take(1) from modulesLoaded$ subscription |
Allows re-triggering after generation |
vfs-sync.service.ts |
Add switchToBranch() and clearLoadedSlugs() methods |
Enables branch switching and cache clearing |
What's Working Now
- Generated code quality is solid (Angular 20+, standalone, signals, OnPush)
- GitLab auto-activation logic is correct (first generation auto-merges to develop)
- Module path handling is correct (StudioRepoController abstracts category directories)
- All 5 fixes compile clean with no new type errors
What Still Needs Work
Must Do (Next Session)
- Live test — Restart backend-vitto-api and run a new generation to verify the fixes
- Diagnose manifest root cause — The new logging will reveal whether the issue is gitConfig, backendApiClient, or HTTP call failure
- Fix manifest creation — Once diagnosed, fix the underlying cause so the Generations panel works
Should Do (Near-Term)
- Environment placeholders — Generated
environment.tshastest-space-idandtest-universe-idinstead of real values - Two 500 errors — During DEPLOY, 2 of 5
POST /ai-api/modulescalls returned 500. Need to investigate what causes intermittent module persistence failures. - Excessive mood inference calls — The API logs show
/vitto-api/moods/inferbeing called every 15 seconds during generation (~80 calls over 20 minutes). Consider reducing frequency during long-running operations.
Could Do (Future)
- Preview auto-refresh after activation — When a generation IS activated (merged to develop), the VFS should auto-switch back to develop
- Rollback UI — The lastGenerationCommitSha signal enables rollback, but no UI exists yet
Architecture Lessons Learned
Emit events before status changes — When a polling consumer watches for status changes, any
awaitbetween the status change and event emission creates a race condition window.Never use
take(1)on lifecycle-spanning signals — If a Subject needs to fire multiple times (init + post-action),take(1)silently kills the subscription.Wait for completion signals, not timers — Fixed-delay timers (500ms) are fragile. Use proper completion signals (
forkJoin→Subject.next()).Include bypass data in events — When a chain (manifest → activate → merge → VFS) can fail at any link, include the final destination data (branch name) in the event so consumers can skip the chain.
Log the absence, not just the error — When optional chaining silently skips a call, the absence of a log entry is the bug. Always log explicitly when a critical path is skipped.
MCP Decisions Logged
| ID | Type | Description |
|---|---|---|
dev_1773921302780_hkxsxgj |
bugfix | SSE race condition — emit before status change |
dev_1773921315730_oil3e2i |
bugfix | VFS timing — wait for modulesLoaded$ |
dev_1773921324971_tfhpg1m |
bugfix | take(1) removal on modulesLoaded$ |
dev_1773921343702_txsy2r0 |
bugfix | featureBranch in completed event + VFS switch |
dev_1773921357980_vk3b2hm |
investigation | Manifest creation diagnostic logging |
Conventions Added
sse-pipeline: Emit events BEFORE status changesangular-signals: Don't use take(1) on multi-emission Subjectsvfs-pipeline: Wait for completion signals before triggering preview
Generated by Claude Code — Session 236