2026 03 14 Vitto Studio Live Platform Engine Design

Documentation for Plans

Vitto Studio Live Platform Engine

Session: S224 Date: 2026-03-14 Status: Design Author: Claude Code (S224.2)


Problem Statement

Vitto generates full Angular 20 platforms with feature modules (match-center, club-members, etc.), but the generated platforms cannot render in the Studio Live Preview. The root cause is a missing initialization pipeline: generated modules depend on 39 shared internal libraries (@libs-foundational/*, @libs-core/*, @libs-ui-core/*, etc.) that the rspack in-browser compiler cannot resolve. There is no yarn install, no build step, and no serve step after generation.

Secondary issues include generation template bugs (broken route imports, wrong export names) and an incomplete visual editing loop.

Competitive Context

Platform Architecture Preview Latency Limitation
Lovable ($6.6B) Cloud Firecracker MicroVMs 2-5s (network) Per-session cloud cost, React/Supabase only
Bolt.new WebContainers (WASM Node.js) <1s cached Memory ceiling kills monorepos, commercial license
v0.dev Vercel Sandbox VMs 1-3s React/Next.js only
GoGrab (target) ESM SDK + rspack WASM <2s Framework-native Angular, no per-session cost

None of these competitors handle Angular monorepos with 39+ shared libraries. GoGrab's architecture is more complex, which is both the challenge and the moat.


Architecture Overview

The Key Insight: Synthesized Preview

The generated platform is for deployment (full Angular app with all files, CI/CD pipeline). The studio preview is synthesized (app shell + module routes, compiled in-browser via rspack).

The studio never needs to compile the entire generated platform. It compiles ONLY the module code and wraps it in a synthesized app shell. All 39 shared libraries are pre-built as ESM bundles on CDN, resolved via browser-native import maps.

Three-Tier Preview Architecture

Tier 1: INSTANT PREVIEW (Primary — <2s)
┌─────────────────────────────────────────────────────┐
│ VFS (module files)                                  │
│   ↓                                                 │
│ BundlerFilesystemService (synthesize app shell)     │
│   ↓                                                 │
│ rspack WASM (compile ONLY module code)              │
│   ↓                                                 │
│ Service Worker (serve compiled assets)              │
│   ↓                                                 │
│ iframe + import map (resolve SDK from CDN)          │
│   ↓                                                 │
│ Live Angular app in preview                         │
└─────────────────────────────────────────────────────┘

Tier 2: FULL BUILD (Validation — 30-60s, future)
┌─────────────────────────────────────────────────────┐
│ Cloud MicroVM (Hetzner sandbox-orchestrator)        │
│   → yarn install → ng build → serve                 │
│   → iframe points at VM URL                         │
└─────────────────────────────────────────────────────┘

Tier 3: PRODUCTION DEPLOY (60-90s)
┌─────────────────────────────────────────────────────┐
│ GitLab CI → Cloudflare Pages                        │
│   → Full AOT build, production optimizations        │
└─────────────────────────────────────────────────────┘

Why This Beats Every Competitor

  1. Faster than Lovable: No VM boot per session. Libraries cached on CDN, only module code compiles.
  2. More robust than Bolt.new: Not limited by browser memory. 39 libraries are pre-built, not compiled in-browser.
  3. Framework-native: Real Angular JIT compilation, not JSX-in-iframe like v0.
  4. Full editing loop: Click component → Vitto AI modifies → rspack recompile → instant re-render.
  5. Real data: Preview connects to actual backend APIs via provideApiConfig.
  6. Composable: Generated modules are self-contained, shareable across platforms.

Phase 1: Platform SDK Pipeline

Goal

Build all 39 shared libraries into ESM bundles, host on Cloudflare R2 with versioned paths, and generate import maps for runtime resolution.

Why ESM + Import Maps (Not WebContainers or Module Federation)

Approach Verdict Reason
WebContainers Eliminated Memory ceiling for 39+ libs, commercial license, untested for Angular/Nx at scale
Module Federation Eliminated Webpack dependency, complex runtime negotiation, overkill for pre-built libs
Sandpack/Nodebox Eliminated Poor Angular support, designed for small examples
Cloud MicroVMs Tier 2 only Network latency per change, per-session cost
ESM + Import Maps Selected Browser-native, zero per-session cost, CDN-cached, <2s compile

Library Inventory (39 libraries, 4 tiers)

Tier 0 — Pure Types (no Angular, no runtime deps):

Tier 1 — Foundational (depends on Angular, Tier 0):

Tier 2 — Core Framework (depends on Tier 1):

Tier 3 — UI/UX (depends on Tiers 0-2):

Third-Party Dependencies

SDK libraries depend on non-Angular third-party packages that MUST be handled:

Package Used By Handling
@angular/cdk drag-drop, ui-core External — add to import map (esm.sh)
@angular/animations Several UI libs External — add to import map (esm.sh). MUST be singleton to avoid double registration.
socket.io-client connectivity-core External — add to import map (esm.sh). CommonJS internals handled by esm.sh conversion.
dexie storage-system External — add to import map (esm.sh)
tinycolor2 theme-runtime External — add to import map (esm.sh)
uuid auth-ui, others External — add to import map (esm.sh)
@fortawesome/* ux-icons External — add to import map (esm.sh)
yjs, y-websocket connectivity-core External — add to import map (esm.sh)

Rule: ALL runtime dependencies are externalized and resolved via import map. Zero inlining. This prevents module identity conflicts (especially with Angular's DI system) and keeps SDK bundles small.

Build Pipeline

platform-base-template/libs/
  ↓
tools/scripts/build-platform-sdk.ts (SWC + esbuild)
  ↓
dist/platform-sdk/v{version}/
  ├── manifest.json                    (library catalog + dependency graph)
  ├── import-map.json                  (browser-native import map)
  ├── core-models/index.js             (Tier 0)
  ├── shared-models/index.js           (Tier 0)
  ├── api-config/index.js              (Tier 1)
  ├── core-angular/index.js            (Tier 1)
  ├── module-base/index.js             (Tier 2)
  ├── ui-core/index.js                 (Tier 3)
  ├── ui-framework/index.js            (Tier 2)
  └── ... (39 total)
  ↓
tools/scripts/deploy-platform-sdk.ts (upload to Cloudflare R2)
  ↓
https://sdk.gograb.space/v{version}/{lib}/index.js

Build Script Design (tools/scripts/build-platform-sdk.ts)

// Pseudocode — actual implementation will be a proper NX library
interface SdkBuildConfig {
  sourceRoot: string;           // platform-base-template/libs/
  outputDir: string;            // dist/platform-sdk/v{version}/
  version: string;              // semver from package.json
  externalAngular: boolean;     // true — Angular resolved from esm.sh
  externalRxjs: boolean;        // true — RxJS resolved from esm.sh
  externalThirdParty: string[]; // ['socket.io-client', 'dexie', 'tinycolor2', '@angular/cdk', ...]
}

// Steps:
// 1. Read tsconfig.base.json paths → build library registry
// 2. Analyze imports → topological sort by dependency order
// 3. For each library (in dependency order):
//    a. Use SWC to transpile TypeScript → JavaScript (preserving decorators for JIT)
//    b. Use esbuild to bundle into single ESM file
//    c. Mark @angular/*, rxjs, zone.js, third-party deps, AND other @libs-* as externals
//    d. Output: {lib}/index.js + {lib}/index.js.map
// 4. Generate manifest.json (library catalog + deps + exports)
// 5. Generate import-map.json (all libs + third-party → CDN URLs)

SDK Manifest (manifest.json)

{
  "version": "1.0.0",
  "angularVersion": "20",
  "rxjsVersion": "7",
  "buildDate": "2026-03-14T00:00:00Z",
  "libraries": [
    {
      "name": "@libs-foundational/core-angular",
      "path": "core-angular/index.js",
      "size": 45000,
      "dependencies": ["@libs-foundational/api-config", "@libs-foundational/core-models"],
      "exports": ["PlatformBootstrapService", "providePlatformBootstrap", "provideApiConfig"],
      "tier": 1
    }
  ],
  "importMap": {
    "imports": {
      "@angular/core": "https://esm.sh/@angular/core@20",
      "@libs-foundational/core-angular": "https://sdk.gograb.space/v1/core-angular/index.js",
      "@libs-core/module-base": "https://sdk.gograb.space/v1/module-base/index.js"
    }
  }
}

Local Development Mode

For development without CDN:

New NX Library: @libs-structural/platform-sdk

libs/libs-structural/platform-sdk/
├── src/
│   ├── lib/
│   │   ├── sdk-builder.service.ts      # Orchestrates build
│   │   ├── sdk-manifest.ts             # Manifest types + loader
│   │   ├── sdk-import-map.service.ts   # Generate/inject import maps
│   │   └── sdk-resolver.service.ts     # Runtime SDK URL resolution
│   └── index.ts
├── project.json
└── tsconfig.lib.json

Files to Create/Modify

New:

Modified:


Phase 2: Generation Pipeline Fixes

Goal

Fix the code generation templates so generated modules compile correctly against the SDK and produce valid Angular code.

Current Bugs (Verified — Confidence: Certain)

Bug 1: Route imports point to non-existent files

// WRONG (app.routes.ts):
loadChildren: () => import('../../../modules/structural/match-center/src/routes')
  .then(m => m.MatchCenterRoutes)
// File 'routes.ts' does not exist. Actual: 'match-center.routes.ts'
// Export name mismatch: actual export is 'matchCenterRoutes', not 'MatchCenterRoutes'

Bug 2: Module index.ts exports from non-existent lib/ directory

// WRONG (match-center/src/index.ts):
export * from './lib/match-center.module';
// Directory 'lib/' does not exist. Files are at root of src/

Bug 3: Inconsistent route export naming

Bug 4: Collection name typos

Fixes Required

These fixes go into the module generation prompts/templates in backend-vitto-api, NOT in the generated code itself (fixing generated code is a band-aid; fixing the generation is the cure).

  1. Standardize route file naming: {module-slug}.routes.ts with export {ModuleName}Routes
  2. Fix index.ts template: Export from ./ not ./lib/
  3. Standardize collection naming: Use correct English plurals
  4. Fix lazy-load import paths: Match actual file structure

Files to Modify

For Preview (Not Deployment)

Preview doesn't need these fixes because the preview app shell is synthesized by BundlerFilesystemService. The synthesized routes import directly from the module files in VFS, bypassing the buggy app.routes.ts entirely.

But these fixes ARE needed for deployment (GitLab CI → Cloudflare Pages) where the generated app.routes.ts must work.


Phase 3: Complete rspack Preview Integration

Goal

Wire up the full pipeline: VFS hydration → synthesized app shell → rspack compile → import maps → iframe preview. Make it work end-to-end.

Architecture

Studio loads project
  ↓
VfsSyncService.hydrateFromSpace() loads module files into VFS
  ↓
User opens Live Preview (or generation completes)
  ↓
PreviewService.compileRspack()
  ↓
BundlerFilesystemService.syncFromVfs(files):
  1. Write module source files to rspack memfs
  2. Synthesize app shell: main.ts, app.component.ts, app.routes.ts, app.config.ts
  3. Synthesize index.html with import map (all SDK libs → CDN URLs)
  4. Inject preview bridge client script
  ↓
BundlerService.compile():
  rspack config has all SDK libs as externals
  → Only module code is compiled (TypeScript → JavaScript)
  → Output: main.js, styles.css (small, module-only bundle)
  ↓
PreviewServerService.updateAssets(sessionId, assets)
  → Service Worker stores compiled assets
  ↓
iframe loads /_preview/{sessionId}/index.html
  → Browser parses import map
  → Loads main.js (compiled module code)
  → main.js imports SDK libs → browser resolves via import map → fetches from CDN
  → Angular JIT bootstraps the app
  → Preview renders

Synthesized App Shell

BundlerFilesystemService already generates synthetic package.json and tsconfig.json. We extend it to synthesize the entire app shell from module manifests in VFS.

Synthesized main.ts:

import 'zone.js';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { appConfig } from './app.config';

bootstrapApplication(AppComponent, appConfig)
  .catch(err => console.error('Preview bootstrap failed:', err));

Synthesized app.config.ts:

import { ApplicationConfig } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { provideAnimations } from '@angular/platform-browser/animations';
import { appRoutes } from './app.routes';
import { provideApiConfig, ENVIRONMENT_PRESETS } from '@libs-foundational/api-config';
import { providePlatformBootstrap } from '@libs-foundational/core-angular';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(appRoutes, withComponentInputBinding()),
    provideHttpClient(),
    provideAnimations(),
    provideApiConfig(ENVIRONMENT_PRESETS.localDirect),
    providePlatformBootstrap({ spaceId: '{spaceId}', universeId: '{universeId}' }),
  ],
};

Synthesized app.routes.ts (dynamic, based on VFS modules):

import { Routes } from '@angular/router';

export const appRoutes: Routes = [
  { path: '', redirectTo: '{firstModule}', pathMatch: 'full' },
  // One entry per module detected in VFS /modules/{slug}/
  {
    path: '{moduleSlug}',
    loadChildren: () => import('./modules/{moduleSlug}/src/{moduleSlug}.routes')
      .then(m => m.{ModuleName}Routes),
  },
  { path: '**', redirectTo: '{firstModule}' },
];

Synthesized app.component.ts:

import { Component, ChangeDetectionStrategy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterModule],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="preview-shell">
      <nav class="preview-nav">
        <!-- Dynamic nav links per module -->
      </nav>
      <main class="preview-main">
        <router-outlet />
      </main>
    </div>
  `,
  styles: [`
    .preview-shell { display: flex; flex-direction: column; height: 100vh; }
    .preview-nav { display: flex; gap: 8px; padding: 8px 16px; background: var(--surface-1, #111); border-bottom: 1px solid var(--border, #222); }
    .preview-nav a { color: var(--text-1, #e0e0e0); text-decoration: none; padding: 4px 12px; border-radius: 4px; }
    .preview-nav a:hover { background: var(--surface-2, #1a1a2e); }
    .preview-main { flex: 1; overflow: auto; }
  `],
})
export class AppComponent {
  title = 'Preview';
}

Import Map Injection

The synthesized index.html includes a <script type="importmap"> block:

Important notes:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Platform Preview</title>
  <script type="importmap">
  {
    "imports": {
      "@angular/core": "https://esm.sh/@angular/core@20",
      "@angular/common": "https://esm.sh/@angular/common@20",
      "@angular/common/http": "https://esm.sh/@angular/common@20/http",
      "@angular/router": "https://esm.sh/@angular/router@20",
      "@angular/forms": "https://esm.sh/@angular/forms@20",
      "@angular/animations": "https://esm.sh/@angular/animations@20",
      "@angular/platform-browser": "https://esm.sh/@angular/platform-browser@20",
      "@angular/platform-browser/animations": "https://esm.sh/@angular/platform-browser@20/animations",
      "@angular/platform-browser-dynamic": "https://esm.sh/@angular/platform-browser-dynamic@20",
      "@angular/compiler": "https://esm.sh/@angular/compiler@20",
      "@angular/cdk": "https://esm.sh/@angular/cdk@20",
      "rxjs": "https://esm.sh/rxjs@7",
      "rxjs/operators": "https://esm.sh/rxjs@7/operators",
      "zone.js": "https://esm.sh/[email protected]",
      "socket.io-client": "https://esm.sh/socket.io-client@4",
      "dexie": "https://esm.sh/dexie@4",
      "tinycolor2": "https://esm.sh/tinycolor2@1",
      "uuid": "https://esm.sh/uuid@13",
      "@fortawesome/fontawesome-svg-core": "https://esm.sh/@fortawesome/fontawesome-svg-core@6",
      "@fortawesome/angular-fontawesome": "https://esm.sh/@fortawesome/[email protected]",
      "@fortawesome/free-solid-svg-icons": "https://esm.sh/@fortawesome/free-solid-svg-icons@6",
      "yjs": "https://esm.sh/yjs@13",
      "y-websocket": "https://esm.sh/y-websocket@3",
      "@libs-foundational/api-config": "https://sdk.gograb.space/v1/api-config/index.js",
      "@libs-foundational/core-angular": "https://sdk.gograb.space/v1/core-angular/index.js",
      "@libs-core/module-base": "https://sdk.gograb.space/v1/module-base/index.js",
      "@libs-core/data-access": "https://sdk.gograb.space/v1/data-access/index.js",
      "@libs-core/ui-framework": "https://sdk.gograb.space/v1/ui-framework/index.js",
      "@libs-core/security": "https://sdk.gograb.space/v1/security/index.js"
    }
  }
  </script>
</head>
<body>
  <app-root></app-root>
  <script src="./main.js" type="module"></script>
</body>
</html>

Note: The full import map is generated dynamically from sdk-manifest.json. The example above shows the key entries. The SdkImportMapService generates the complete map at runtime including all 39 SDK libraries and their third-party dependencies.

SCSS Handling

Generated modules currently use SCSS. Two options:

Option A (Selected): CSS-only output for generated modules

Option B (Deferred): sass.js WASM in-browser

rspack Config Updates

// rspack-angular.config.ts changes:
// 1. resolve.alias maps module paths within VFS
// 2. All SDK libs added to externals
// 3. Import map injection handled by BundlerFilesystemService

export function createRspackConfig(config: BundlerConfig) {
  return {
    // ... existing config ...
    resolve: {
      extensions: ['.ts', '.js', '.html', '.css', '.json'],
      alias: config.moduleAliases ?? {},  // NEW: VFS module path aliases
    },
    externals: buildExternalsConfig(config.cdnMap),
  };
}

VFS Path Mapping for rspack

Module files are loaded into rspack's memfs under /modules/{slug}/src/. The synthesized app.routes.ts imports modules using relative paths that resolve naturally within memfs:

// Synthesized app.routes.ts:
loadChildren: () => import('./modules/match-center/src/match-center.routes')
  .then(m => m.MatchCenterRoutes)
// Resolves to /modules/match-center/src/match-center.routes.ts in memfs

No resolve.alias is needed for intra-module imports (they use relative paths like ./components/match-list/match-list.component). Inter-library imports (e.g., @libs-core/module-base) are handled by the externals config — rspack outputs them as bare import specifiers which the browser's import map resolves.

Files to Create/Modify

Modified:


Phase 4: Visual Editing Loop

Goal

Enable click-to-select in the preview → Vitto AI modifies the component → rspack recompiles → preview updates. The "vibe coding" experience.

Current State (S205)

Architecture

User clicks component in preview iframe
  ↓
PreviewBridgeService receives STUDIO_ELEMENT_SELECTED
  { componentName: 'MatchListComponent', selector: 'app-match-list', ... }
  ↓
StudioInterconnectionService updates focusContext signal
  ↓
VisualEditorService reads focus context + VFS file content
  → Builds rich context: component source, template, styles, model
  ↓
User says: "Add a status badge to each match row"
  ↓
Vitto receives: user message + focus context (component + source + template)
  ↓
Vitto uses visual editing tools:
  modify_component_template({ componentName, newTemplate })
  modify_component_styles({ componentName, newStyles })
  add_import({ filePath, importStatement })
  ↓
Tools write changes to VFS via VfsService.updateFileContent()
  ↓
VFS change triggers PreviewService rebuild (debounced 500ms)
  ↓
rspack recompiles module code → Service Worker updates → iframe reloads
  ↓
Updated component renders in preview (<2s total)

Vitto Visual Editing Tools

New tools added to the Vitto Engine for studio context:

modify_component_template: Modify a component's HTML template

{
  name: 'modify_component_template',
  description: 'Modify the HTML template of an Angular component in the current module',
  input: {
    componentName: string,     // e.g., 'MatchListComponent'
    modulePath: string,        // e.g., 'match-center'
    newTemplate: string,       // Full new template HTML
  },
  // Implementation: Find .component.html in VFS, update content
}

modify_component_styles: Modify a component's CSS

{
  name: 'modify_component_styles',
  description: 'Modify the CSS styles of an Angular component',
  input: {
    componentName: string,
    modulePath: string,
    newStyles: string,
  },
}

modify_component_class: Modify a component's TypeScript class

{
  name: 'modify_component_class',
  description: 'Modify the TypeScript class of an Angular component (add fields, methods, etc.)',
  input: {
    componentName: string,
    modulePath: string,
    newClassContent: string,
  },
}

add_component: Create a new component in a module

{
  name: 'add_component',
  description: 'Add a new Angular component to a module',
  input: {
    componentName: string,
    modulePath: string,
    template: string,
    styles: string,
    classContent: string,
  },
}

modify_model: Update a data model

{
  name: 'modify_model',
  description: 'Modify a TypeScript interface/model in a module',
  input: {
    modelName: string,
    modulePath: string,
    newModelContent: string,
  },
}

New Service: VisualEditorService

libs/libs-structural/vitto-studio/src/lib/services/visual-editor.service.ts

Responsibilities:

  1. Listen to StudioInterconnectionService.focusContext changes
  2. When a component is selected, read its source files from VFS (.ts, .html, .css)
  3. Build a VisualEditingContext with full component source
  4. Provide this context to Vitto's prompt via StudioVittoBridgeService
  5. When Vitto tools execute modifications, write changes back to VFS
  6. Changes trigger automatic rspack recompile → preview update

Properties Panel Integration

Beyond AI editing, users should be able to directly manipulate component properties:

  1. Selected component's @Input() properties shown in the Properties panel
  2. User changes a property value → VisualEditorService updates the component source
  3. Same recompile flow as AI editing

This is Phase 4b (stretch goal) — requires introspection of compiled component metadata.

Files to Create/Modify

New:

Modified:


Data Flow: End-to-End Generation → Preview → Edit

1. USER: "Create a platform for a soccer club"
   ↓
2. VITTO: 6-phase generation pipeline
   → ENHANCE → ANALYZE → DECOMPOSE → PLAN → EXECUTE → INTEGRATE
   → Outputs: match-center module, club-members module
   → Files committed to GitLab repo
   ↓
3. STUDIO: Generation listener receives SSE events
   → VfsSyncService.refreshAllModules() loads new files into VFS
   → Auto-opens Live Preview panel
   ↓
4. PREVIEW: PreviewService.compileRspack()
   → BundlerFilesystemService synthesizes app shell
   → rspack compiles module code (SDK libs are externals)
   → Service Worker serves compiled assets
   → iframe loads with import map → Angular bootstraps
   ↓
5. USER: Clicks "Match List" component in preview
   → PreviewBridge sends STUDIO_ELEMENT_SELECTED
   → Properties panel shows component details
   → Vitto chat gains component context
   ↓
6. USER: "Add a team logo next to each match name"
   → Vitto uses modify_component_template to update MatchListComponent
   → VFS updates → rspack recompiles → preview refreshes (<2s)
   ↓
7. USER: Clicks "Deploy"
   → GitLab CI runs full AOT build → Cloudflare Pages
   → Live URL returned

Risk Assessment

Critical Risks

Risk Mitigation Confidence
esm.sh CDN downtime breaks preview Self-host Angular ESM bundles on same R2 bucket as SDK. Fallback to bundled mode. Certain — single CDN dependency is fragile
Angular JIT deprecation in future versions Monitor Angular roadmap. JIT still works in v20. Explore Angular Wiz/incremental DOM if JIT drops. Likely — Angular team has been deprecating JIT since v13, but hasn't removed it
rspack WASM memory limits for large modules Set module file count guardrails. Split very large modules. Monitor memory usage. Likely — untested with 50+ files
Import map browser compatibility Chrome/Edge: full support. Safari 16.4+: supported. Firefox: supported. No IE11 concern. Certain — all modern browsers support import maps

Non-Critical Risks

Risk Mitigation
SDK build breaks on library API change Version SDK independently. Pin generated platforms to SDK version.
Preview differs from production build Accept JIT vs AOT differences for preview. Full Build (Tier 2) covers fidelity.
Inter-library circular dependencies Detected at build time by topological sort. Break cycles during SDK build.

Success Criteria

Phase 1 (ESM SDK)

Phase 2 (Generation Fixes)

Phase 3 (rspack Preview)

Phase 4 (Visual Editing)


Implementation Order & Dependencies

Phase 1 (ESM SDK) ←── BLOCKER for Phase 3
  ↓
Phase 2 (Gen Fixes) ←── Independent, can parallel with Phase 1
  ↓
Phase 3 (rspack Integration) ←── Requires Phase 1 complete
  ↓
Phase 4 (Visual Editing) ←── Requires Phase 3 complete

Phases 1 and 2 can run in parallel. Phase 3 depends on Phase 1. Phase 4 depends on Phase 3.


Out of Scope (Deferred)