HANDOFF S138.6 Generation Process Persistence
Documentation for Handoffs
HANDOFF S138.6 — Generation Process Persistence & Recovery
Date: 2026-02-23 Session: 138.6 Type: Architecture / Feature Priority: HIGH — Every page refresh or tab crash loses 8-10 minutes of generation work
Problem Statement
When a user starts platform generation (an 8-10 minute process), any interruption destroys everything:
- Page refresh → generation lost
- Tab crash (Bug 8) → generation lost
- Accidental navigation → generation lost
- Browser close → generation lost
There is no way to come back to a running or completed generation. Each visit to /vibe starts completely fresh.
Surprising Finding: Backend Is Already 80% Ready
Investigation reveals the backend already has reconnection, resumption, and checkpoint infrastructure — it's just never wired up on the frontend.
What Already Exists (Backend)
| Feature | Endpoint / Code | Status |
|---|---|---|
| Session creation | modgen-${UUID.substring(0,8)} |
✅ Works |
| SSE reconnection | GET /stream/:sessionId |
✅ Built, never called |
| Phase resumption | POST /resume/:sessionId |
✅ Built (Session 137), never called |
| Session status check | GET /status/:sessionId |
✅ Built, never called |
| Checkpoint load | GET /checkpoint/:sessionId |
✅ Built, never called |
| MongoDB persistence | generation_checkpoints collection |
✅ Saves per-phase results |
| Resumable session finder | findResumableSessions(userId) |
✅ Built, never called |
| ReplaySubject buffer | Last 50 SSE events per session | ✅ Late-join gets history |
What Already Exists (Frontend)
| Feature | Location | Status |
|---|---|---|
resumeWorkflow(sessionId) |
WorkflowStateService (lines 313-365) |
✅ Built, never called |
workflowId signal |
WorkflowStateService |
✅ Stores sessionId |
restoreGenerationState() |
VibeUniverseGeneratorComponent (lines 786-837) |
⚠️ Uses sessionStorage only, no backend check |
What's Missing
| Gap | What Needs To Happen |
|---|---|
| Route parameter | Add :processId to vibe route |
| URL persistence | Push sessionId to URL when generation starts |
| Init check | On component init, check route for processId → reconnect |
| Generation history | UI to browse past/active generations |
| Backend session → MongoDB link | In-memory sessions lost on restart; rely on checkpoints |
Implementation Plan
Phase 1: Wire the ProcessID into the Route (Priority: HIGH)
File: vibe-universe.routes.ts
Current:
export const VibeUniverseRoutes: Routes = [
{ path: '', loadComponent: () => import('./components/vibe-universe-generator/...') }
];
Change to:
export const VibeUniverseRoutes: Routes = [
{ path: '', loadComponent: () => import('./components/vibe-universe-generator/...') },
{ path: 'process/:processId', loadComponent: () => import('./components/vibe-universe-generator/...'),
data: { tool: 'vibe', aiEnabled: true, fullScreen: true, resumeMode: true }
}
];
This gives us two routes:
/vibe— fresh start (template picker)/vibe/process/modgen-a1b2c3d4— resume/view specific generation
Phase 2: Capture & Persist the ProcessID (Priority: HIGH)
File: vibe-universe-generator.component.ts
When generation starts and the first SSE event arrives with a sessionId:
// In the SSE event handler, after receiving the first event with sessionId:
private onSessionIdReceived(sessionId: string): void {
// 1. Update the URL without triggering navigation
this.router.navigate(
['process', sessionId],
{ relativeTo: this.route, replaceUrl: true }
);
// 2. Also persist to localStorage as backup
localStorage.setItem(`vibe-generation:${this.universeId}:active`, JSON.stringify({
sessionId,
startedAt: Date.now(),
description: this.platformDescription(),
universeId: this.universeId,
spaceId: this.spaceId
}));
}
File: workflow-state.service.ts
The sessionId is already extracted from SSE events but only stored in a signal. Add an output event:
// In startWorkflow(), when first SSE event arrives:
this.eventSource.addEventListener('module-gen.started', (e: MessageEvent) => {
const raw = JSON.parse(e.data);
const sessionId = raw.sessionId || raw.data?.sessionId;
if (sessionId) {
this.workflowId.set(sessionId);
this.sessionIdReceived.emit(sessionId); // New: notify component
}
});
Phase 3: Reconnect on Page Load (Priority: HIGH)
File: vibe-universe-generator.component.ts
Update ngOnInit to check for processId in route:
ngOnInit(): void {
const processId = this.route.snapshot.params['processId'];
if (processId) {
// Resume mode: reconnect to existing generation
this.reconnectToGeneration(processId);
} else {
// Check localStorage for recent active generation in this universe
const active = this.getActiveGeneration();
if (active && active.sessionId && (Date.now() - active.startedAt) < 3600000) {
// Prompt user: "You have an active generation. Resume or start fresh?"
this.showResumePrompt(active);
} else {
// Fresh start: show template picker
this.mode.set('idle');
}
}
}
private async reconnectToGeneration(sessionId: string): Promise<void> {
this.mode.set('generating');
// Step 1: Check backend status first
try {
const status = await firstValueFrom(
this.http.get<any>(`${this.vittoApiUrl}/module-generation/status/${sessionId}`)
);
if (status.status === 'completed') {
// Generation already finished — restore final state
this.restoreCompletedState(status);
return;
}
if (status.status === 'failed' || status.status === 'interrupted') {
// Offer to resume from checkpoint
this.showResumeFromCheckpoint(sessionId, status);
return;
}
// Still running — reconnect to SSE stream
this.workflowState.resumeWorkflow(sessionId).subscribe({
next: (event) => this.handleWorkflowEvent(event),
error: (err) => this.handleReconnectError(err, sessionId)
});
} catch (err) {
// Session not found in memory — try checkpoint
this.attemptCheckpointResume(sessionId);
}
}
Phase 4: Backend — Ensure Checkpoint Saves SessionId Early (Priority: MEDIUM)
File: module-gen-phase.executor.ts
The checkpoint is already created at generation start. Verify it saves immediately (before any LLM call), so even if the first phase crashes, the sessionId is in MongoDB:
// In orchestrateGeneration():
const session = this.sessionManager.createSession(request);
const sessionId = session.id;
// Checkpoint IMMEDIATELY (before ENHANCE phase)
await this.checkpointService.createCheckpoint({
sessionId,
request: { description: request.description, spaceId: request.spaceId, universeId: request.universeId },
status: 'in_progress',
userId: request.userId,
spaceId: request.spaceId,
universeId: request.universeId,
});
// Now proceed with ENHANCE...
Also ensure the module-gen.started SSE event fires with sessionId BEFORE the first phase begins, so the frontend can capture it.
Phase 5: Generation History UI (Priority: MEDIUM)
Add a small "history" section to the vibe page that shows past generations for this universe:
Backend endpoint (already partially exists via findResumableSessions):
GET /vitto-api/module-generation/history?universeId=xxx&userId=yyy&limit=10
Returns:
[
{
"sessionId": "modgen-a1b2c3d4",
"description": "Fitness studio platform with membership management...",
"status": "completed",
"completedPhases": ["enhance", "analyze", "decompose", "plan", "execute", "integrate", "deploy"],
"modulesGenerated": 11,
"startedAt": "2026-02-23T14:27:54Z",
"completedAt": "2026-02-23T14:38:33Z",
"duration": "10m 39s"
}
]
Frontend: Show as a collapsible panel or sidebar on the vibe page:
Recent Generations
─────────────────────
✅ Fitness Studio (11 modules) — 10 min ago
└ Click to load result
❌ E-Commerce Platform (failed at EXECUTE) — 2 hours ago
└ Click to retry from checkpoint
🔄 Restaurant App (in progress, PLAN phase) — just now
└ Click to reconnect
Phase 6: Multi-Tab Safety (Priority: LOW)
With processId in the URL, add a guard:
// Before starting a new generation, check if one is already running
private async checkForActiveGeneration(): Promise<boolean> {
const active = await firstValueFrom(
this.http.get<any>(`${this.vittoApiUrl}/module-generation/active?universeId=${this.universeId}`)
);
if (active?.sessionId) {
this.toast.warning('A generation is already running. Redirecting...');
this.router.navigate(['vibe', 'process', active.sessionId], { relativeTo: this.universeRoute });
return false; // Don't start new generation
}
return true;
}
File Change Summary
| File | Changes |
|---|---|
vibe-universe.routes.ts |
Add process/:processId child route |
vibe-universe-generator.component.ts |
Add ngOnInit processId check, reconnectToGeneration(), onSessionIdReceived() URL update, resume prompt UI, generation history panel |
workflow-state.service.ts |
Add sessionIdReceived output/callback, ensure resumeWorkflow() is working correctly |
module-generation.controller.ts |
Add /history endpoint, verify /stream/:sessionId returns proper state for late-joins |
module-gen-phase.executor.ts |
Ensure checkpoint saves before first phase, emit module-gen.started SSE event with sessionId early |
checkpoint.service.ts |
Add findByUniverse() query for history endpoint |
Key Design Decisions
ProcessId = SessionId — Reuse the existing
modgen-{uuid}format. No need for a separate concept.URL is the source of truth —
/vibe/process/modgen-a1b2c3d4makes the generation bookmarkable, shareable, and survives refresh. localStorage is backup only.Backend checkpoint is the recovery mechanism — In-memory sessions may be lost on restart, but MongoDB checkpoints persist for 7 days. The resume endpoint rebuilds from checkpoint.
Graceful degradation — If processId in URL points to a dead session with no checkpoint, show a friendly "This generation has expired" message with option to start fresh.
No breaking changes — The existing
/viberoute still works as a fresh start. The new/vibe/process/:processIdis additive.
Testing Checklist
- Start generation → URL updates to
/vibe/process/modgen-xxx - Refresh page during generation → reconnects and resumes progress display
- Tab crash → reopen URL → reconnects or shows checkpoint resume option
- Generation completes → URL still has processId → can revisit result
- Visit
/vibefresh → no processId → shows template picker as before - Visit
/vibe/process/expired-id→ shows friendly "expired" message - Two tabs open same universe → second tab redirected to active generation
- Generation history shows past attempts with status
- Click completed generation in history → loads result
- Click failed generation in history → offers checkpoint resume