design-docs/commits-and-variants.md
This document explains how the commit system and non-blocking variant generation work in screenshot-to-code.
Commits represent discrete versions in the application's history. Each commit contains:
nanoid())isCommitted: false) or finalized (isCommitted: true)type CommitType = "ai_create" | "ai_edit" | "code_create";
type Commit = {
hash: CommitHash;
parentHash: CommitHash | null;
dateCreated: Date;
isCommitted: boolean;
variants: Variant[];
selectedVariantIndex: number;
type: CommitType;
inputs: any; // Type-specific inputs
}
type Variant = {
code: string;
status: VariantStatus;
}
type VariantStatus = "generating" | "complete" | "cancelled";
Commits are stored in the project store as a flat record:
commits: Record<CommitHash, Commit>
head: CommitHash | null // Current active commit
The head pointer tracks which commit is currently active. History is reconstructed by following parentHash links.
Start Generation → Wait for ALL variants → Show results
User Experience: [Loading...........................] → Ready
Problems:
Start Generation → Show results as each variant completes
User Experience: [Loading.....] → Ready (Option 1)
[Loading..........] → Ready (Option 2)
Benefits:
App.tsx: Enhanced event handling
// New WebSocket events
onVariantComplete: (variantIndex) => {
updateVariantStatus(commit.hash, variantIndex, 'complete');
}
onVariantError: (variantIndex, error) => {
updateVariantStatus(commit.hash, variantIndex, 'cancelled');
}
Sidebar.tsx: Dual-condition UI
// Show update UI when either condition is true
{(appState === AppState.CODE_READY || isSelectedVariantComplete) && (
<UpdateInterface />
)}
Variants.tsx: Real-time status indicators
generate_code.py: Independent variant processing
# Process each variant independently
async def process_variant_completion(index: int, task: asyncio.Task):
completion = await task # Wait for THIS variant only
# Process images immediately
processed_html = await perform_image_generation(...)
# Send to frontend immediately
await send_message("setCode", processed_html, index)
await send_message("variantComplete", "Variant generation complete", index)
The system uses a hybrid state approach:
INITIAL → CODING → CODE_READY)generating → complete/cancelled)// UI shows update interface when either:
const canUpdate =
appState === AppState.CODE_READY || // All variants done
isSelectedVariantComplete; // Selected variant done
// User can interact immediately when their selected variant completes
type WebSocketResponse = {
type: "chunk" | "status" | "setCode" | "variantComplete" | "variantError";
value: string;
variantIndex: number;
}
Backend: Generate Variant 1 → "setCode" → "variantComplete"
Frontend: Update UI → Allow interaction
Backend: Generate Variant 2 → "setCode" → "variantComplete"
Frontend: Update UI → User can switch to this variant
Backend: Generate Variant 3 → "variantError"
Frontend: Show error → Mark as cancelled
User starts generation
status: "generating"First variant completes
variantComplete event"complete"User switches variants
User starts update
When users start updates, other generating variants are cancelled:
// Cancel generating variants when user updates
currentCommit.variants.forEach((variant, index) => {
if (index !== selectedVariantIndex && variant.status === 'generating') {
wsRef.current.send(JSON.stringify({
type: "cancel_variant",
variantIndex: index
}));
}
});
Each variant handles errors independently:
This architecture enables a responsive, non-blocking user experience while maintaining system reliability and resource efficiency.