# VibeScope Master Plan — From VSIL Viewer to True VSIL Editor > **Plan ID:** VSM-PLAN-v1.0 > **Date:** 2026-07-08 > **Owner:** Mavis (`413419604156710`, FreshVibe Studio root) > **Repo:** `avidtech6/freshvibestudio` > **Workspace:** VibeScope > **Status:** DRAFT — Awaiting operator ratification --- ## 0. Executive Summary VibeScope today is a **read-only VSIL viewer**: 6 cards mounted in a 2×2 grid that render mock VSIL scenes as static SVG, with no edit primitives, no paint surface, no layer manipulation, no animation controls, no Monaco/CodeMirror editor, and no WebGL2 shaders (only a probe). The Pact has always demanded a true paint/visual editor with three engines, two layouts, six engine subsystems (brush/transform/selection/layer/mask/animation), and live VSIL + VSIL-A round-trip. This plan transforms VibeScope from viewer to editor across **9 Phases, 38 Tasks, 12 Tracks**, executed by a coordinated multi-agent team, with drift-check v3.3.x + AMEND-006 + DC-VC-FALLBACK-N as the continuous validation surface. **Outcome at DONE**: VibeScope delivers a Photoshop-class visual editor in the browser — paint on ``, manipulate layers live, two-way-bind properties, scrub a VSIL-A timeline with bezier interpolation, edit VSIL source in a real code editor with live re-evaluation, save/load/export to canonical VSIL IR — and ships with full Pact alignment (zero drift, zero stub markers, all engine subsystems implemented). --- ## 1. Pact Alignment Charter (Constitutional Requirements) These are **law**, not suggestions. Every phase must satisfy them. ### 1.1 DNA Declarations (from `pact/workspaces/vibescope/workspace.md`) | DNA Tag | Value | Phase Commitment | |---|---|---| | `@workspace` | `vibescope` | All work stays in `studio/modules/vibescope/src/` | | `@layout` | `cinema,dev` | Phase 7+8 builds both layouts distinctively | | `@layouts_count` | `2` | Cinema + Dev only — no third layout | | `@default_layout` | `cinema` | First paint = Cinema | | `@dev_layout_gate` | `super_admin` | Phase 8 hardens `DevLayoutGate` with real OAuth removal of `FUTURE-OAUTH-INTEGRATION` marker | | `@engine_family` | `cinema_procedural,cinema_ultra,render` | All 3 engines implemented, no stubs | | `@canonical_ir` | `vsil` | All edits round-trip through VSIL (no shadow state) | | `@animation_ir` | `vsil-a` | Timeline drives the `evaluator.ts` engine, not local component state | | `@chips` | `[editor,visual,timeline,inspector,docs,replay,diff,tests]` | All 8 fragments surface real editor functionality (not 6 mock cards + 2 stubs) | ### 1.2 Engine Subsystem Fragments (from `pact/platform/engines/cinema-procedural-engine.md`) Six subsystems must each be implemented and routed through Cinema Procedural Engine: | Subsystem Fragment | Phase | Implementation Surface | |---|---|---| | `cinema.engine.procedural.subsystem.brush.001` | Phase 4 | Brush engine in `paint/BrushEngine.ts` (real-time input → stroke) | | `cinema.engine.procedural.subsystem.transform.001` | Phase 4 | Transform engine in `paint/TransformEngine.ts` (move/scale/rotate with snap-to guides) | | `cinema.engine.procedural.subsystem.selection.001` | Phase 4 | Selection engine in `paint/SelectionEngine.ts` (marquee + lasso) | | `cinema.engine.procedural.subsystem.layer.001` | Phase 5 | Layer model in `vsil/layerOps.ts` (reorder/group/composite) | | `cinema.engine.procedural.subsystem.mask.001` | Phase 6 | Mask engine in `paint/MaskEngine.ts` (paint/link/mode-switch) | | `cinema.engine.procedural.subsystem.animation.001` | Phase 6 | Animation engine in `paint/AnimationEngine.ts` (live keyframe drag, linear interp) | ### 1.3 Engine Quality Tiers (per cinema-procedural-engine.md §1) - **Procedural Engine**: 16ms frame budget, Draft/Good quality, **MUST never block UI**, MUST degrade gracefully on failure. - **Ultra Engine**: 33ms frame budget, Best quality, GPU-accelerated (WebGL2), bezier + AA + post-processing. MUST honestly fall back to procedural when WebGL2 unavailable (already implemented; verifier must confirm). - **Render Engine**: Frame-by-frame, full quality, CLI-compatible bytes. MUST NOT require DOM. ### 1.4 Drift-Check Invariants (per `pact/scripts/audit-chip-system-v3.3.x.sh`) These MUST stay at 0 drift violations through every phase: | Rule | Severity | What it locks | |---|---|---| | DC-VC-1 | critical | Vibe Card is the only always-mounted Panel Card | | DC-VC-2 | major | Each fragment maps to ≤3 surfaces | | DC-VC-3 | major | Inner Card stack cap = 3 | | DC-VC-7 | major | Surface grammar locked to canonical names | | DC-VC-9 | minor | Fragment grammar canonical to DNA `@chips` | | **DC-VC-FALLBACK-N** | critical | VibeScope must have **exactly 1** dispatcher emitting `data-vibescope-active-card` | | AMEND-006 §3.1 | critical | Lowercase chip ids in registry (Title-Case display only) | ### 1.5 Read-Only Invariant (Inspector) Per `pact/platform/inspector/inspector.md`: Inspector is a **read-only viewer**. Modifications happen in source surface. Phase 7 introduces Inspector *edit affordances* (Properties panel) **as a separate concern** bound to the source surface, NOT inside the Inspector card itself. --- ## 2. Current State vs DONE State | Capability | Current | DONE | |---|---|---| | Paint surface (`` 2d context) | ❌ none | ✅ Full brush engine, layer-aware compositing | | Layer add/delete/reorder via UI | ❌ none | ✅ DOM panel + drag-to-reorder + visibility/blend toggle | | Properties two-way binding | ❌ Inspector is read-only | ✅ Inspector Props mode reads + writes to live VSILSource | | Selection (marquee + lasso) | ❌ none | ✅ Click-hit + marquee drag + multi-select via shift | | Transform handles (move/scale/rotate) | ❌ none | ✅ DOM handles anchored to selection bounds | | Mask painting | ❌ none | ✅ Brush engine routed to mask channel | | Animation keyframe drag | ❌ static dots | ✅ Drag-to-move keyframes on timeline, live re-eval | | VSIL-A scrubber (frame, bezier interp) | ❌ visual preview has range slider | ✅ Timeline scrubber drives `evaluator.evaluateFrame()`, scene re-renders | | Live source editor (Monaco/CM) | ❌ pretty-print + contenteditable | ✅ Monaco with VSIL syntax + live-eval-on-blur | | Layer subsystem in engine | ❌ CinemaProcedural just renders | ✅ Layer ordering / grouping / compositing via VSIL ops | | Cinema Ultra WebGL2 shader | ❌ probe + fallback only | ✅ Real shader pass for layer compositing | | Render Engine CLI bytes | ✅ renders 1 frame | ✅ Multi-frame batch CLI driver | | Cinema vs Dev layout differentiation | ❌ both 2×2 same cards | ✅ Cinema = paint-first, Dev = data-first | | Save/load/export VSIL doc | ❌ no persistence | ✅ CodeX-backed save + JSON + SVG export | | VSIL ↔ scene graph round-trip | partial (read only) | ✅ every edit mutates `VSILSource`, parser re-parses | | All 8 chips work (Editor/Visual/Timeline/Inspector/Docs/Replay/Diff/Tests) | 6 work, Replay+Diff thin | ✅ All 8 fragments drive real editor functionality | --- ## 3. The 9 Phases ### Phase 1 — Foundation Lock + Studio Rename Hygiene **Duration estimate**: 0.5 day **Goal**: Lock the foundation: file-system shape, rename `` placeholders in pact templates, drift-check pristine. **Inputs**: - Current pact at `pact/workspaces/vibescope/` (5 files) - Repo HEAD at `833d6c3` **Outputs**: - All `pact/workspaces/vibescope/*.md` files (already done — verified 2026-07-07 audit) - Drift-check v3.3.x baseline = `32/0` PASS (verified) - `studio/modules/vibescope/src/` directory tree confirmed **Acceptance Criteria**: - `bash pact/scripts/audit-chip-system-v3.3.x.sh` → 32/0 PASS (no regression) - All 5 workspace pact files contain the VibeScope-specialised template (not the raw `` placeholder version) - `@chips` array matches `studio/modules/vibescope/src/registry/fragmentRegistry.ts` `FRAGMENTS` exactly **Drift-Checks**: DC-VC-1..9, DC-VC-FALLBACK-N, AMEND-006 §3.1 **Pact Compliance**: `vibescope.cockpit.scope.001`, `vibescope.workspace.behaviour.scope.001`, `vibescope.workspace.surfaces.definition.001` **Risks**: Low. Risks already mitigated by Helper Mavis's prior pact-alignment work. **Mitigations**: Pre-flight drift-check before any subsequent phase. --- ### Phase 2 — Editor Store: VSIL as Live Mutable Source of Truth **Duration estimate**: 2-3 days **Goal**: Replace mock JSON imports with a live `VSILSource` that flows through every edit. Every paint stroke, every Inspector tweak, every keyframe drag mutates the same store. The engines consume from the store; no shadow state. **Inputs**: - Current `vsil/parser.ts` (read-only) - Current `vsil/sceneGraph.ts` (read-only) - 8 mock JSON files at `studio/modules/vibescope/src/mocks/*.vsil.json` **Outputs**: - New `studio/modules/vibescope/src/state/VSILStore.ts` — Zustand-style store with `useVSILStore()` hook - New `studio/modules/vibescope/src/vsil/vsilOps.ts` — pure mutation primitives (`addLayer`, `removeLayer`, `reorderLayer`, `updateTransform`, `setBlendMode`, `setVisibility`) - All 6 mocks retained as initial state seeds (`useVSILStore.getState().loadVSIL('editor', editorMock)`) - 6 cards (Editor/Visual/Inspector/Timeline/Docs/Replay) refactored to read from store instead of importing mocks directly - Mutation log at `studio/modules/vibescope/src/state/mutationLog.ts` — every op pushed for undo/redo and audit **Acceptance Criteria**: - `useVSILStore` has 1 global instance per workspace session (proven via DevTools) - All 6 cards reflect a single change in <16ms (procedural budget) when a mutation is dispatched - Mutations are pure + immutable (Object.freeze per `vsil.a.layer.state.node.001`) - `mutationLog.length` increases by 1 per op, never decreases until undo - All existing tests still pass (parser, sceneGraph, evaluator, keyframeExtractor, assetRegistry) **Drift-Checks**: DC-VC-1 (only 1 always-mounted Card), DC-VC-2 (≤3 surfaces per fragment), DC-VC-7 (canonical surface names) **Pact Compliance**: `pipeline.vsil.definition.001`, `pipeline.vsil.scene_structure.001`, `cinema.engine.procedural.no.ui.block.001`, `cinema.engine.procedural.fidelity.draft.001`, `vsil.a.layer.state.node.001` (immutable) **Risks**: - **High**: Subtle aliasing between store state and derived scene graph. If a consumer reads from scene graph instead of store, mutations won't propagate. - **Med**: React 18 concurrent mode + Zustand selectors can drop updates if selector shape is wrong. **Mitigations**: - Convention: all reads from `useVSILStore(s => s.layers)`. All writes go through `vsilOps.*` functions. - Tests: `vsilOps.test.ts` proves mutation idempotence + scene graph re-derivation produces equivalent tree. - Add `data-vsil-source-version` attribute on store consumer root for debugging. --- ### Phase 3 — Scrubber + VSIL-A Wired to Engine Pipeline **Duration estimate**: 2 days **Goal**: Replace the local `useState(0)` time slider in VisualCard with the real Scrubber. Scrubber drives `evaluator.evaluateFrame(source, tracks, anchor)` from `vsil/evaluator.ts`. Result feeds CinemaProcedural as the new FrameState. **Inputs**: - `src/scrubber/` types + hooks (`useFrameAt`, ScrubberAnchor, ScrubberBar, ScrubberControls) - `src/vsil/evaluator.ts` (already implemented, deterministic + immutable + reversible) - `src/vsil/keyframeExtractor.ts` (already implemented) - New `src/mocks/timeline.tracks.json` (3-5 sample animation tracks) **Outputs**: - New `src/scrubber/ScrubberContext.tsx` — React context provider wrapping the workspace - All 6 cards consume `useScrubber()` instead of `useState` for time - CinemaProcedural accepts `FrameState` (post-evaluation) instead of raw `VSILSource` - New `src/engines/CinemaProcedural.eval.ts` — engine variant that renders evaluated frame state with animated transforms - New `src/__tests__/scrubber-evaluator-integration.test.tsx` — proves scrub → evaluate → render pipeline **Acceptance Criteria**: - Scrubbing Timeline triggers `evaluateFrame` per frame - Procedural engine renders at <16ms (Draft) on 60-frame composition with 4 animated layers (Playwright measured) - Evaluation is deterministic (same source + same tracks + same time = same frame, verified by snapshot test) - Bezier interpolation works for `transform.translate` track (visible curve in motion) - `useFrameAt(frame)` hook returns same value on identical input (no clock-drift) **Drift-Checks**: DC-VC-1, DC-VC-7 **Pact Compliance**: `vsil.a.composition.union.001`, `vsil.a.interpolation.001`, `cinema.engine.procedural.vsil.reduced.001`, `cinema.engine.procedural.vsil.a.001`, `cinema.engine.procedural.latency.16ms.001` **Risks**: - **High**: Evaluator + Procedural engine coupling could break the 16ms budget at high track count. - **Med**: Scrubber state must NOT live in the local `useState` of VisualCard — must be global. **Mitigations**: - Performance test: 100 tracks × 10 keyframes < 5ms (per evaluator's own perf comment). - Lint rule (custom ESLint): disallow `useState` in `modules/vibescope/src/cards/` for time values. - Document the Scrubber context shape in `src/scrubber/README.md`. --- ### Phase 4 — Paint Surface + Brush + Transform + Selection Engines **Duration estimate**: 4-5 days (largest phase) **Goal**: Implement the 3 interaction subsystems (brush, transform, selection) on a real HTML5 ``. The paint surface becomes the primary editing surface in VisualCard. **Inputs**: - Phase 2's `vsilOps` (mutations) - Phase 3's evaluated FrameState (so animated transforms are visible during paint) - `pact/platform/engines/cinema-procedural-engine.md` subsystem fragments **Outputs**: - New `studio/modules/vibescope/src/paint/PaintSurface.tsx` — `` mount with pointer event dispatch - New `studio/modules/vibescope/src/paint/BrushEngine.ts` — implements `cinema.engine.procedural.subsystem.brush.001`: stamp-based brush with size/opacity/hardness, 60fps, linear interp on `transform.translate` - New `studio/modules/vibescope/src/paint/TransformEngine.ts` — implements `cinema.engine.procedural.subsystem.transform.001`: move/scale/rotate handles with snap-to guides - New `studio/modules/vibescope/src/paint/SelectionEngine.ts` — implements `cinema.engine.procedural.subsystem.selection.001`: marquee rect + lasso via Path tool - New `studio/modules/vibescope/src/paint/HitTest.ts` — point-in-layer test (rect / ellipse / path / text) - VisualCard refactored: `` replaces the SVG dump; brush + selection drive `vsilOps` mutations - Selection state in `state/SelectionStore.ts` (separate from VSILStore — selection is ephemeral) - New tests: `paint/brush.test.ts`, `paint/transform.test.ts`, `paint/selection.test.ts`, `paint/hitTest.test.ts` **Acceptance Criteria**: - Mouse down on canvas → brush stamp at pointer → drag → continuous strokes - Each stroke dispatched as `vsilOps.addPaintLayer` or `vsilOps.updateLayer(path)` (depending on mode) - Click on a layer → SelectionStore.selected = layerId → handles render - Drag a handle → real-time `vsilOps.updateTransform` → Procedural re-renders <16ms - Marquee drag → multi-select → Inspector updates to multi-selection state - Hit-test: clicking inside a rect's bounds selects it; clicking outside deselects - All 3 engines emit `data-paint-engine-output` attribute with engine name - Brush + transform + selection work in 2D space (camera is 2D for now; 3D camera deferred to Phase 9) **Drift-Checks**: DC-VC-1, DC-VC-2, DC-VC-7, AMEND-006 **Pact Compliance**: - `cinema.engine.procedural.subsystem.brush.001` (real-time, 60fps, low-latency) - `cinema.engine.procedural.subsystem.transform.001` (60fps, snap-to) - `cinema.engine.procedural.subsystem.selection.001` (marquee + lasso, real-time preview) - `cinema.engine.procedural.no.ui.block.001` - `cinema.engine.procedural.latency.16ms.001` - `cinema.engine.procedural.failure.budget.001` - `cinema.engine.procedural.security.sandbox.001` **Risks**: - **Critical**: Canvas performance — paint at 60fps with 50+ layers risks dropped frames. The Procedural engine MUST degrade gracefully per `failure.budget.001`. - **High**: Pointer event semantics differ between mouse, touch, pen — must handle all three. - **Med**: Transform handles need to know layer bounds; getting bounds from procedural SVG (already in DOM) vs from VSILStore (canonical) is a tension. **Mitigations**: - Procedural engine MUST be the source of truth for render — bounds derived from `vsilOps.getBounds(layer)`, not from DOM measurement. - Pen pressure wired via `PointerEvent.pressure` (browser-supported, falls back to 0.5 if absent). - Performance test: 50 layers + active brush = <16ms/frame (Playwright measured, fail test if >33ms). - Hit-test runs against scene graph (pre-computed per-frame), not against live DOM. --- ### Phase 5 — Layer Panel UI + Layer Ops **Duration estimate**: 2 days **Goal**: A real layer panel that lets the user add/delete/reorder layers, toggle visibility, change blend mode, group/ungroup. Lives in the Inspector's AST mode and the Cinema layout's Structure cell. **Inputs**: - Phase 2's `vsilOps` - Phase 4's SelectionStore **Outputs**: - New `studio/modules/vibescope/src/cards/layer-panel/LayerPanel.tsx` — DOM tree with drag-to-reorder, visibility toggle, blend-mode dropdown - New `studio/modules/vibescope/src/cards/layer-panel/LayerRow.tsx` - New `studio/modules/vibescope/src/vsil/layerOps.ts` — `addLayer`, `removeLayer`, `reorderLayer`, `groupLayers`, `ungroupLayer`, `setBlendMode`, `setVisibility`, `renameLayer`, `duplicateLayer` - Inspector AST mode shows the live tree (already exists; rewires to read from VSILStore) - CinemaLayout's Structure cell now points at LayerPanel (instead of duplicating InspectorCard) - DevLayout's Structure cell uses DataCard (renamed) showing layer state in JSON form - New tests: `vsil/layerOps.test.ts`, `cards/layer-panel/LayerPanel.test.tsx` **Acceptance Criteria**: - Click "+" → `vsilOps.addLayer('rect')` → new layer appears in tree + in canvas within 16ms - Drag layer row → `vsilOps.reorderLayer(fromIndex, toIndex)` → z-order updates → render reflects - Toggle visibility eye → `vsilOps.setVisibility(id, false)` → layer hidden in canvas - Group selection → `vsilOps.groupLayers([id1, id2])` → new group layer with children - Inspector AST mode shows the live tree reflecting all ops - All ops are reversible via mutationLog (undo button) **Drift-Checks**: DC-VC-1, DC-VC-2, DC-VC-3 (inner stack cap) **Pact Compliance**: `cinema.engine.procedural.subsystem.layer.001` (ordering, grouping, basic compositing), `pipeline.vsil.scene_structure.001`, `vsil.a.composition.union.001` **Risks**: - **Med**: Group operations can produce invalid parent-child states (e.g., group within group). - **Med**: Blend mode values not in canonical enum (e.g., `multiply-strong`). **Mitigations**: - `layerOps` enforces `VALID_BLEND_MODES` enum; invalid input → throws VSILParseError. - Group operations validate: cannot group a parent with its descendant (would create cycle). - Layer ops produce versioned operations (`vsil.layer.op.v1`) for the mutationLog. --- ### Phase 6 — Mask + Animation Subsystems **Duration estimate**: 3 days **Goal**: Two more engine subsystems. Mask paints a mask channel on a layer. Animation makes the brush engine write to a `float-keyframe-track` (live keyframe drag). **Inputs**: - Phase 4's BrushEngine (now also writes to mask channels) - Phase 3's Scrubber + Evaluator pipeline - `pact/platform/vsil/vsil-a.md` track types **Outputs**: - New `studio/modules/vibescope/src/paint/MaskEngine.ts` — implements `cinema.engine.procedural.subsystem.mask.001`: paint to mask, link mask to layer, switch mask mode (add/subtract/intersect) - New `studio/modules/vibescope/src/paint/AnimationEngine.ts` — implements `cinema.engine.procedural.subsystem.animation.001`: live keyframe drag → writes new keyframe to track → linear interp during scrub - New `studio/modules/vibescope/src/vsil/animationOps.ts` — `addKeyframe`, `moveKeyframe`, `removeKeyframe`, `setInterpolation`, `setBezierHandles` - New `studio/modules/vibescope/src/cards/timeline/KeyframeRow.tsx` — draggable keyframe marker per track - TimelineCard refactored: shows per-layer animated properties with draggable keyframes - VisualCard mask mode toggle: brush routed to mask channel instead of layer channel - New tests: `paint/mask.test.ts`, `paint/animation.test.ts`, `vsil/animationOps.test.ts` **Acceptance Criteria**: - Mask mode: paint strokes appear as mask only (not visible directly); linked layer uses mask for visibility - Animation: select layer → choose "Animate Position" → timeline shows track at frame 0; drag the keyframe to frame 30 → Procedural engine interpolates `transform.translate` linearly - Multiple keyframes on same track → drag one between two existing → ordering preserved, time clamped - Bezier handles draggable on keyframes with `interpolation: 'bezier'` - Scrubber playback animates position over 30 frames at 60fps **Drift-Checks**: DC-VC-1, DC-VC-2, DC-VC-7 **Pact Compliance**: - `cinema.engine.procedural.subsystem.mask.001` (paint, link, mode-switch) - `cinema.engine.procedural.subsystem.animation.001` (live keyframe drag, linear interp; bezier = Ultra) - `vsil.a.keyframe.tracks.001` (7 canonical track kinds — Phase 6 implements float + vec2; rest in Phase 9) - `vsil.a.interpolation.001` (linear default for float-keyframe-track) **Risks**: - **High**: Animation engine performance during scrub — if every frame triggers full keyframe re-sort, will not hit 16ms budget. - **Med**: Mask channel storage: where does the mask paint data live? Need a `maskChannel: Uint8Array` per layer. **Mitigations**: - Track ops are sorted once on insertion; subsequent reads are O(log n) binary search. - Mask data stored as base64-encoded PNG on `layer.maskChannel` (1 channel, 8-bit). - Performance test: scrub a 60-frame animation with 4 animated layers = <16ms/frame. --- ### Phase 7 — Properties Panel: Two-Way Binding to Live VSIL **Duration estimate**: 2 days **Goal**: Inspector's Props mode becomes a real two-way binding surface. Changes there mutate the live VSILSource; canvas re-renders in <16ms. **Inputs**: - Phase 2's `vsilOps` - Phase 4's SelectionStore - Existing `inspector/PropsMode.tsx` (currently read-only) **Outputs**: - PropsMode refactored: each property is a controlled input bound to `vsilOps.updateLayer` - Per-property validators (e.g., `opacity` clamps 0..1, `scale` clamps >0) - New `studio/modules/vibescope/src/inspector/propertyBindings.ts` — declarative property-to-op mapping - New `studio/modules/vibescope/src/__tests__/props-two-way.test.tsx` - Undo/redo wired to `mutationLog` **Acceptance Criteria**: - Change a layer's `fill` color → VSILStore updates → CinemaProcedural re-renders with new color in <16ms - Change `opacity` slider → VSILStore updates → Procedural re-renders - Multi-selection: changing common property (e.g., visible) applies to all selected layers - Invalid input (e.g., opacity = -0.5) is clamped to valid range, never silently dropped - Inspector remains read-only for ALL OTHER MODES (AST, State, Logs, Metadata) per pact **Drift-Checks**: DC-VC-1, DC-VC-2 **Pact Compliance**: `cinema.engine.procedural.subsystem.adjustment.001` (Levels/Exposure/Saturation deferred to Phase 9; Phase 7 implements transform + fill/stroke) **Risks**: - **High**: Breaking the Inspector read-only invariant — if Props mode writes, does AST mode? It must NOT. - **Med**: Two-way binding with controlled inputs + React 18 batching — fast typing could thrash. **Mitigations**: - Hard rule: only Props mode writes. AST, State, Logs, Metadata are read-only by static type + lint. - Property writes are debounced 8ms (one frame at 60fps × 0.5) inside PropsMode for slider drags. - Snapshot test: AST mode never emits a write op (verify via mock spy on `vsilOps`). --- ### Phase 8 — Cinema vs Dev Layout Differentiation + Editor Source View **Duration estimate**: 3 days **Goal**: Make Cinema and Dev layouts visibly distinct. Cinema = paint-first (PaintSurface + Properties + LayerPanel + Timeline). Dev = data-first (Monaco/CodeMirror with VSIL source + DataCard state tree + DevDesignerCard + LogsCard). **Inputs**: - All prior phases - Existing `DevLayoutGate.tsx` (already gates super_admin) **Outputs**: - New `studio/modules/vibescope/src/dev/DesignerCard.tsx` — Monaco editor with VSIL JSON syntax; live-eval on blur (or 500ms debounce) - New `studio/modules/vibescope/src/dev/DataCard.tsx` — live state tree (already named this way but needs real content) - New `studio/modules/vibescope/src/dev/LogsCard.tsx` — mutation log + Procedural Engine telemetry (frame time, drop frames) - New `studio/modules/vibescope/src/dev/useMonacoVSIL.ts` — Monaco language definition for VSIL (JSON schema-aware autocomplete) - CinemaLayout: 4 cells = PaintSurface + PropertiesPanel + LayerPanel + TimelineCard (replaces current VisualCard/InspectorCard duplication) - DevLayout: 4 cells = DesignerCard + DataCard + LogsCard + InspectorCard (single inspector, AST mode) - `DevLayoutGate` hardened: production role check via `useUser().isSuperAdmin`; `FUTURE-OAUTH-INTEGRATION` marker **removed** (now: real role backend OR explicit deferral marker `OAUTH-DEFERRED-VS-2026Q3`) - Footer-level layout switcher in CentreStage (per `vibescope.layout.switching.001`) - New tests: `layouts/CinemaLayout.test.tsx`, `layouts/DevLayout.test.tsx`, `dev/DesignerCard.test.tsx` **Acceptance Criteria**: - CinemaLayout cells: paint surface (PaintSurface) + properties (Inspector Props) + structure (LayerPanel) + timeline (TimelineCard) - DevLayout cells: designer (Monaco) + data (DataCard) + logs (LogsCard) + inspector (InspectorCard AST) - Layout switcher in CentreStage footer toggles between Cinema + Dev without remounting chips - Monaco editor: editing a layer's `transform.translate` in JSON re-evaluates → cinema_procedural re-renders in <16ms - Monaco errors (invalid VSIL schema) → inline gutter markers, no console error storm - Dev access denied to non-super-admin users with explicit fragment_id reference (already there) **Drift-Checks**: DC-VC-1, DC-VC-2, DC-VC-3, DC-VC-7, DC-VC-FALLBACK-N (still 1 dispatcher) **Pact Compliance**: - `vibescope.layout.cinema.definition.001` (Preview · Properties · Structure · Timeline — now real cells, not stubs) - `vibescope.layout.dev.definition.001` (Designer · Structure · Data · Logs) - `vibescope.layout.cinema.access.001` (public) - `vibescope.layout.dev.access.001` (super_admin only) - `vibescope.layout.switching.001` (footer-level, not chip-driven) **Risks**: - **High**: Monaco bundle size — adds ~3MB to the studio bundle. Acceptable for VibeScope-only path. - **High**: Cinema + Dev being so different increases the surface area of bug-finding. - **Med**: Layout switcher persistence across sessions requires localStorage — verify no conflict with prior Chip-id persistence. **Mitigations**: - Monaco is dynamically `import()`'d inside DesignerCard (code-split). Not in main bundle. - DevLayout only mounts when `useUser().isSuperAdmin === true`; no eager work for non-admins. - Layout preference stored under `vibescope.layout.user_pref.v1` localStorage key (namespaced). --- ### Phase 9 — Cinema Ultra Real WebGL2 + Render CLI + Final Docs **Duration estimate**: 3 days **Goal**: Replace Cinema Ultra's probe-with-fallback with a real WebGL2 shader pass. Add a CLI driver for Render Engine. Final Pact documentation + drift-check sealing. **Inputs**: - Phase 2-8 outputs - `pact/platform/engines/cinema-ultra-engine.md` (procedural best quality + bezier interp) - Render engine already implemented (single frame only) **Outputs**: - New `studio/modules/vibescope/src/engines/CinemaUltra.webgl.ts` — actual WebGL2 program (vertex + fragment shaders) for layer compositing - `CinemaUltra.tsx` refactored: WebGL2 path uses shader program; procedural path still falls back honestly - New `studio/modules/vibescope/src/engines/renderCli.ts` — CLI driver that loops frames 0..duration*fps calling `RenderEngine.render`, writes to `dist/` as PNG (via canvas conversion) or SVG (already done) - New `studio/modules/vibescope/src/engines/__tests__/ultra-webgl.test.ts` — proves WebGL2 path produces different output than procedural fallback (not just labeled) - New `studio/modules/vibescope/src/engines/__tests__/render-cli.test.ts` — proves CLI driver produces N frames - All 6 remaining VSIL-A track kinds implemented in `animationOps` (vec2/vec3/colour/path/string/discrete) - Pact doc additions: `pact/workspaces/vibescope/editor-getting-started.md` (operator onboarding) - Drift-check v3.3.x → still 32/0 PASS (now exercising real implementation) **Acceptance Criteria**: - WebGL2 detected → CinemaUltra emits `data-vibescope-ultra-gpu="true"` + uses real shader (output pixels differ from procedural fallback by measurable amount: e.g., AA on edges, bezier curve smoothness) - WebGL2 absent → emits `data-vibescope-ultra-fallback="true"` (already works) - Render CLI: `npx ts-node engines/renderCli.ts --source=mock --frames=60 --out=dist/` writes 60 SVG files - All 7 track kinds have working `addKeyframe`/`moveKeyframe` ops - Drift-check v3.3.x = 32/0 PASS, no new stubs, no `STUB:` markers in shipped code **Drift-Checks**: DC-VC-1..9, DC-VC-FALLBACK-N, AMEND-006 **Pact Compliance**: - `cinema.engine.procedural.subsystem.timeline.001` (already there, Phase 6 verifies) - `cinema.engine.ultra.definition.001` (real shader) - `cinema.engine.ultra.fallback.honest.001` (already there) - `vsil.a.engine.bindings.001` (3 engines all implemented) - `pipeline.vsil.definition.001` (VSIL never produces pixels — engines do) **Risks**: - **Critical**: WebGL2 shader bugs are hard to diagnose. Need strong test coverage of the shader output. - **High**: CLI driver must work in jsdom-free Node (use `node-canvas` or write SVG only). - **Med**: 7 track kinds × multiple ops = lots of new code. Risk of off-by-one in time clamping. **Mitigations**: - Shader output compared pixel-by-pixel to procedural fallback on a 4×4 test scene; difference > ε = GPU path proven. - Render CLI ships with SVG-only output (deterministic, no native deps). PNG conversion deferred to separate ticket. - Each track kind has its own `animationOps.test.ts` for time clamping, sort order, and out-of-range. --- ## 4. Dependency Graph (Phase-level) ``` PHASE 1 Foundation Lock ↓ PHASE 2 Editor Store (VSIL as live mutable source) ↓ PHASE 3 Scrubber + Evaluator wired to engines ↓ PHASE 4 Paint Surface + Brush + Transform + Selection engines ↓ PHASE 5 Layer Panel + Layer Ops ↓ PHASE 6 Mask + Animation engines + Timeline interaction ↓ PHASE 7 Properties Panel two-way binding ↓ PHASE 8 Cinema vs Dev differentiation + Monaco source editor ↓ PHASE 9 Cinema Ultra WebGL2 + Render CLI + Final Pact docs ↓ DONE ``` **Serial dependencies are mostly hard** — Phases 2→3→4→5→6→7 are all coupled by the `VSILStore` becoming real and paint operations starting to mutate it. Phases 8 and 9 are independent from each other but both depend on Phase 7. --- ## 5. Multi-Agent Track Map 12 Tracks. Multiple tracks can run concurrently within a single phase where their concerns don't overlap. | Track ID | Owner role | Phase coverage | Parallel-safe with | |---|---|---|---| | **T1 Pact Hygiene** | pact-alignment agent | Phase 1 | nothing (foundation) | | **T2 VSIL Store** | typescript-engineer | Phase 2 | T3 (different files) | | **T3 Scrubber + Evaluator** | typescript-engineer (specialist in deterministic + immutable) | Phase 3 | T2 once Phase 2 lands | | **T4 Brush Engine** | graphics-engineer (canvas + pointer events) | Phase 4 | T5 (separate module) | | **T5 Transform Engine** | graphics-engineer | Phase 4 | T4 | | **T6 Selection Engine** | graphics-engineer | Phase 4 | T4, T5 | | **T7 Layer Panel** | react-engineer | Phase 5 | T8 (different cards) | | **T8 Mask + Animation** | graphics-engineer + animation-specialist | Phase 6 | T7 (after Phase 5) | | **T9 Properties Two-Way** | react-engineer + form-specialist | Phase 7 | T10 (different files) | | **T10 Monaco + Dev Layout** | dev-tools-engineer | Phase 8 | T9 | | **T11 WebGL2 + Render CLI** | webgl-engineer + node-engineer | Phase 9 | T12 | | **T12 Tests + Drift-Check + Docs** | verifier (always-on) | All phases | everything | **Always-on**: - T12 (Verifier) runs drift-check after every producer commit. - Cross-track integration tests (3-substrate CardMount parity, VibeScope render parity) run before each phase-end. **Sequence within a phase**: 1. Producer track(s) write code + producer-self-tests. 2. T12 verifier runs drift-check + reviewer pass. 3. Phase gate: drift 32/0, no new STUB markers, integration test passes. 4. Phase commit → next phase begins. --- ## 6. Cross-Track Dependencies (Explicit) | Producer | Consumer | Artifact | |---|---|---| | T2 (VSIL Store) | T3, T4, T5, T6, T7, T9 | `useVSILStore` hook + `vsilOps` | | T3 (Scrubber + Evaluator) | T4, T6, T8 | `useScrubber` hook + `CinemaProcedural.eval` | | T4 (Brush) | T6 (Mask), T9 (Properties exposure) | `BrushEngine` API | | T5 (Transform) | T9 | `TransformEngine` API | | T6 (Selection) | T7 (Layer Panel), T9 | `SelectionStore` | | T7 (Layer Panel) | T8 (Mask visibility), T9 (multi-select) | `LayerPanel` component | | T8 (Animation) | T9 (keyframe inspector) | `KeyframeRow` component | | T9 (Properties) | T10 (Designer reads live state) | `propertyBindings` | | T10 (Monaco) | T11 (CLI reads same source format) | VSIL JSON schema | | T11 (WebGL2) | none (terminal) | shader output | | T12 (Verifier) | all | drift-check report | **Critical Path**: T2 → T3 → T4 → T6 → T7 → T8 → T9 → T10 → T11 = 9 sequential producer phases + verifier on every step. **Parallelizable**: T4 + T5 + T6 (within Phase 4); T8's Mask + Animation sub-tracks (within Phase 6); T10 + T11 (Phases 8 + 9 are independent of each other). --- ## 7. World-Class Acceptance Checklist For each phase, ALL items must hold: ### 7.1 Architectural Clarity - [ ] All new code lives under `studio/modules/vibescope/src/` (no leakage to other workspaces) - [ ] New components follow the CardMount factory shape (`{Component, fragmentId}`) - [ ] All data flows through VSILStore; no shadow state in components - [ ] React context providers co-located with consumers (not at App root) ### 7.2 Pact Alignment - [ ] Every new file references its governing pact fragment_id in the header comment - [ ] Engine subsystems each implement their declared fragment (brush/transform/selection/layer/mask/animation/timeline) - [ ] Three engines (procedural/ultra/render) all implement `VSILEngine` interface — no stubs - [ ] Two layouts (cinema/dev) distinct and gate-correct ### 7.3 Drift-Check Alignment - [ ] `bash pact/scripts/audit-chip-system-v3.3.x.sh` → 32/0 PASS - [ ] No new `STUB:` markers in shipped code (only in test fixtures if any) - [ ] No new `// TODO:` without a tracked ticket - [ ] DC-VC-FALLBACK-N: exactly 1 dispatcher emitting `data-vibescope-active-card` - [ ] AMEND-006 §3.1: lowercase chip ids in registry, Title-Case display in UI ### 7.4 Deterministic Acceptance Criteria - [ ] All new tests are deterministic (no `Math.random`, no `Date.now` in assertions) - [ ] Snapshot tests for evaluable artifacts (VSIL → FrameState → SVG) - [ ] Property-based test for `vsilOps` (idempotence + inverse) - [ ] Engine output reproducible across runs (same source → same pixels) ### 7.5 Multi-Agent Executability - [ ] Each Track has a single owner - [ ] Each Track can run in isolation (no hidden coupling to other Tracks except documented cross-track deps) - [ ] Each Track produces a mergeable commit + a verifier-readable report ### 7.6 Zero Ambiguity - [ ] No `any` types in shipped code - [ ] No commented-out code in shipped code - [ ] No "placeholder" strings that survive into DONE - [ ] Every public function has JSDoc + at least one test - [ ] Every CSS variable used (no hardcoded colors except engine output markings) --- ## 8. Final DONE Definition **VibeScope is DONE when ALL of the following are true:** ### 8.1 Functional DONE - [ ] User opens VisualCard in Cinema layout → sees real canvas with at least one layer rendered via CinemaProcedural Engine - [ ] User clicks on canvas → selects a layer (SelectionEngine) - [ ] User drags a transform handle → layer moves in real-time, <16ms per frame - [ ] User changes opacity in Properties panel → layer fades in real-time - [ ] User opens Layer Panel → adds a new rect layer → appears in canvas - [ ] User toggles visibility eye → layer hides - [ ] User toggles Cinema → Dev → DevLayout shows Monaco with live VSIL source - [ ] User edits source in Monaco → cinema_procedural re-evaluates on blur, canvas updates - [ ] User scrubs Timeline → frames advance, animated layers interpolate - [ ] User adds a keyframe via Animation engine → drags it → motion updates - [ ] User paints with brush → new layer OR mask channel updated - [ ] User clicks "Render" → CLI driver produces 60 SVG frames to `dist/` - [ ] User switches engine quality: Draft (procedural) / Good (procedural + sampling) / Best (Ultra WebGL2) - [ ] User saves → VSIL doc written to Codex storage - [ ] User reloads → doc loaded back, identical to saved state (deterministic round-trip) ### 8.2 Pact DONE - [ ] All 8 chips work: Editor/Visual/Timeline/Inspector/Docs/Replay/Diff/Tests - [ ] All 3 engines implemented: cinema_procedural, cinema_ultra, render - [ ] Both layouts differentiated: Cinema (paint-first) + Dev (data-first) - [ ] All 6 engine subsystem fragments have implementation: brush, transform, selection, layer, mask, animation - [ ] All 7 VSIL-A track kinds have addKeyframe/moveKeyframe ops: float, vec2, vec3, colour, path, string, discrete - [ ] 4 Cinema cells = paint + properties + structure + timeline - [ ] 4 Dev cells = designer + data + logs + inspector ### 8.3 Drift DONE - [ ] `bash pact/scripts/audit-chip-system-v3.3.x.sh` → 32/0 PASS (no regression) - [ ] No `STUB:` markers in shipped code - [ ] No `// TODO:` without a tracked ticket - [ ] No `FUTURE-OAUTH-INTEGRATION` marker (replaced with explicit deferral or real OAuth) - [ ] `studio/modules/vibescope/src/registry/fragmentRegistry.ts` registry has 8 entries matching DNA `@chips` - [ ] All 5 pact workspace files are VibeScope-specialised (no `` placeholders) ### 8.4 Test DONE - [ ] All existing tests still pass: parser, sceneGraph, evaluator, keyframeExtractor, assetRegistry, diff - [ ] New tests: - `vsilOps.test.ts` — 50+ tests covering all mutations - `paint/brush.test.ts` — 20+ tests for brush behavior - `paint/transform.test.ts` — 20+ tests for transform handles - `paint/selection.test.ts` — 15+ tests for selection - `paint/mask.test.ts` — 15+ tests for mask - `paint/animation.test.ts` — 15+ tests for keyframe ops - `vsil/animationOps.test.ts` — 50+ tests for 7 track kinds - `inspector/propertyBindings.test.tsx` — 20+ tests for two-way binding - `dev/DesignerCard.test.tsx` — 10+ tests for Monaco integration - `engines/ultra-webgl.test.ts` — 5+ tests for shader output - `engines/render-cli.test.ts` — 5+ tests for CLI driver - `layouts/CinemaLayout.test.tsx` — 5+ tests for cell composition - `layouts/DevLayout.test.tsx` — 5+ tests for Dev access - [ ] Visual regression tests: snapshot of full Cinema layout render (golden file) - [ ] Visual regression tests: snapshot of full Dev layout render (golden file) - [ ] Performance tests: 60fps paint with 50 layers, scrub with 4 animated layers - [ ] Integration test: 3-substrate CardMount parity (VibeCoder + VibeScope + FreshCards) — already passing in Phase 1, must hold through DONE ### 8.5 Build DONE - [ ] `vite build` clean, <30s - [ ] Bundle size: VibeScope route < 1.5MB (Monaco is dynamic-imported) - [ ] All 3 engines tree-shake correctly (procedural/ultra/render all in bundle) - [ ] CF Pages deploy succeeds, canonical URL `https://freshvibestudio.pages.dev/vibescope/...` works - [ ] 0 page errors, 0 console errors in Playwright smoke test ### 8.6 Operational DONE - [ ] `pact/workspaces/vibescope/editor-getting-started.md` written (operator onboarding) - [ ] `studio/modules/vibescope/README.md` updated with Phase 9 state - [ ] All tracked follow-up tickets from earlier sessions reviewed and either closed or explicitly deferred - [ ] Codex v0031.0 published with VibeScope Editor invariants (carry forward prior invariants + 10 new from this plan) --- ## 9. Estimated Effort + Risk Summary | Phase | Days | Risk | Critical-path | |---|---|---|---| | 1. Foundation Lock | 0.5 | Low | yes | | 2. Editor Store | 2-3 | Med | yes | | 3. Scrubber + Evaluator | 2 | High | yes | | 4. Paint + Brush + Transform + Selection | 4-5 | Critical | yes | | 5. Layer Panel | 2 | Med | yes | | 6. Mask + Animation | 3 | High | yes | | 7. Properties Two-Way | 2 | High | yes | | 8. Cinema vs Dev + Monaco | 3 | High | yes | | 9. WebGL2 + Render CLI + Docs | 3 | Critical | yes | | **Total** | **22-26 days** | | | **Critical risk concentration**: Phase 4 (canvas + pointer events + 3 engines) and Phase 6 (animation performance). Both have hard 16ms budgets. If either fails, the whole plan slips. **Risk concentration mitigation**: Use T12 verifier after every commit in Phases 4 + 6. Fail fast. Do not advance to next phase if performance gate fails. --- ## 10. Ratification This plan requires operator ratification before execution. Mavis will not begin Phase 1 implementation until the operator signals GO. **Signal options**: - "GO" — execute all 9 phases sequentially - "GO PHASES 1-3 ONLY" — execute foundation phases, pause for review - "MODIFY" — operator specifies changes to phases or tracks - "PARK" — save plan, defer to next session **Operator context**: The VibeScope thread is currently `413419604156710` (FreshVibe Studio root). Helper Mavis (`412100071272671`) is pact-alignment track. Coordination via `communicate` tool. --- ## 11. Appendices ### Appendix A — Drift-check rules (full list) Re-verified from `pact/scripts/audit-chip-system-v3.3.x.sh`: - DC-VC-1 critical — only 1 always-mounted Panel Card - DC-VC-2 major — ≤3 surfaces per fragment - DC-VC-3 major — Inner Card stack cap = 3 - DC-VC-4..6 major — (other cockpit invariant checks) - DC-VC-7 major — Surface grammar canonical - DC-VC-8 major — Workspace scope - DC-VC-9 minor — Fragment grammar canonical to DNA @chips - DC-VC-FALLBACK-N critical — exactly 1 dispatcher - AMEND-006 §3.1 critical — lowercase chip ids in registry ### Appendix B — Pact fragment inventory (excerpt) Per `pact/v3/freshvibe-cockpit-constitution-vibescope.md` §5, 10 canonical Cards collapse to 8 doctrinal fragments per workspace DNA. All 8 must surface real editor functionality at DONE. ### Appendix C — Engine subsystem fragment cross-reference Per `pact/platform/engines/cinema-procedural-engine.md`, 6 subsystems (brush, transform, selection, layer, mask, animation). Phase 4 + Phase 6 cover all 6. The 6th one — adjustment subsystem (Levels/Exposure/Saturation) — is **not in scope for this plan**; tracked as future ticket VS-009. ### Appendix D — Track owner assignments | Track | Owner | Specialty | |---|---|---| | T1 Pact Hygiene | Helper Mavis (`412100071272671`) | pact alignment | | T2 VSIL Store | Producer A | TypeScript + React state management | | T3 Scrubber + Evaluator | Producer A | Determinism + immutable systems | | T4 Brush Engine | Producer B | Canvas + pointer events | | T5 Transform Engine | Producer B | Canvas + bounds math | | T6 Selection Engine | Producer B | Canvas + hit-testing | | T7 Layer Panel | Producer C | React + drag-and-drop | | T8 Mask + Animation | Producer D | Animation + keyframes | | T9 Properties Two-Way | Producer C | React forms + controlled inputs | | T10 Monaco + Dev Layout | Producer E | Monaco + dev tools | | T11 WebGL2 + Render CLI | Producer F | WebGL shaders + Node CLI | | T12 Verifier | Helper Mavis + Producer G | Drift-check + integration tests | ### Appendix E — Code paths that will change ``` studio/modules/vibescope/src/ ├── cards/ │ ├── VisualCard.tsx # Phase 2 (reads store), Phase 4 (paint surface) │ ├── EditorCard.tsx # Phase 8 (Monaco) — replaced by DesignerCard │ ├── InspectorCard.tsx # Phase 5 (live tree), Phase 7 (props binding) │ ├── TimelineCard.tsx # Phase 6 (draggable keyframes) │ ├── DocsCard.tsx # Phase 9 (real VSIL reference) │ ├── ReplayCard.tsx # Phase 9 (real replay playback) │ ├── DiffCard.tsx # Phase 9 (real diff from VSILStore versions) │ ├── TestsCard.tsx # Phase 9 (real test runner) │ ├── dev/ │ │ ├── DesignerCard.tsx # NEW (Phase 8) — Monaco │ │ ├── DataCard.tsx # Phase 8 — real state tree │ │ └── LogsCard.tsx # NEW (Phase 8) — engine logs │ └── layer-panel/ │ ├── LayerPanel.tsx # NEW (Phase 5) │ └── LayerRow.tsx # NEW (Phase 5) ├── dev/ │ └── useMonacoVSIL.ts # NEW (Phase 8) ├── engines/ │ ├── CinemaProcedural.tsx # Phase 4 (re-routes to PaintSurface) │ ├── CinemaProcedural.eval.ts # NEW (Phase 3) │ ├── CinemaUltra.tsx # Phase 9 (real WebGL2 path) │ ├── CinemaUltra.webgl.ts # NEW (Phase 9) │ ├── Render.tsx # Phase 9 (CLI driver glue) │ └── renderCli.ts # NEW (Phase 9) ├── inspector/ │ ├── PropsMode.tsx # Phase 7 (two-way binding) │ ├── ASTMode.tsx # Phase 5 (live tree) │ ├── StateMode.tsx # Phase 8 (real FrameState) │ ├── LogsMode.tsx # Phase 8 (engine telemetry) │ ├── MetadataMode.tsx # Phase 8 (real asset metadata) │ └── propertyBindings.ts # NEW (Phase 7) ├── layouts/ │ ├── CinemaLayout.tsx # Phase 8 (real cell composition) │ ├── DevLayout.tsx # Phase 8 (Monaco + data + logs) │ └── DevLayoutGate.tsx # Phase 8 (OAuth marker removed) ├── paint/ │ ├── PaintSurface.tsx # NEW (Phase 4) │ ├── BrushEngine.ts # NEW (Phase 4) │ ├── TransformEngine.ts # NEW (Phase 4) │ ├── SelectionEngine.ts # NEW (Phase 4) │ ├── MaskEngine.ts # NEW (Phase 6) │ ├── AnimationEngine.ts # NEW (Phase 6) │ └── HitTest.ts # NEW (Phase 4) ├── state/ │ ├── VSILStore.ts # NEW (Phase 2) │ ├── SelectionStore.ts # NEW (Phase 4) │ └── mutationLog.ts # NEW (Phase 2) ├── vsil/ │ ├── parser.ts # unchanged │ ├── sceneGraph.ts # unchanged │ ├── evaluator.ts # unchanged │ ├── keyframeExtractor.ts # unchanged │ ├── assetRegistry.ts # unchanged │ ├── diff.ts # Phase 9 (real diff consumer) │ ├── vsilOps.ts # NEW (Phase 2) │ ├── layerOps.ts # NEW (Phase 5) │ └── animationOps.ts # NEW (Phase 6) ├── scrubber/ │ ├── types.ts # unchanged │ ├── useFrameAt.ts # unchanged │ ├── ScrubberAnchor.tsx # unchanged │ ├── ScrubberBar.tsx # Phase 6 (draggable keyframes) │ ├── ScrubberControls.tsx # unchanged │ └── ScrubberContext.tsx # NEW (Phase 3) └── registry/ └── fragmentRegistry.ts # unchanged (already aligned) ``` ### Appendix F — Plan execution logging Every phase completion writes a short report to `/workspace/.mavis/plans/vibescope-master-plan/phase-NN-report.md` with: drift-check output, new tests added, screenshots of new functionality, and any drift / stub markers remaining. Verifier (T12) signs off in the same file. --- ## END OF PLAN