# ID9 — r207 PreviewHost + Inspector Mount — Structural Plan (v2, bound to r206)
**Date**: 2026-08-30 | **Operator directive (revised)**: r206 IS shipped at commit `07463582794d6d3696e69d4242120d10cc07a30e` on `clean/baseline-plus-wdm`. r207 binds to the real r206.
**Discovery-only.** No code. No pact edits. No extraction.
---
## ⚠️ Scope finding: r207 binds to `avidtech6/vibecoder-standalone`, NOT `freshvibestudio`
The prior turn (a793/a794/a795/a796) was drafted against a **speculative r206 contract**. The actual r206 is **already shipped** and **substantially different** from the spec I wrote. The directive in this turn makes the canonical path explicit:
- **Repo**: `avidtech6/vibecoder-standalone` (NOT `avidtech6/freshvibestudio`)
- **Branch**: `clean/baseline-plus-wdm` (NOT `id9-vsil-vibecoder`)
- **Commit**: `07463582794d6d3696e69d4242120d10cc07a30e`
- **Paths**: `src/host/workspace/`, `src/vibechat/`
**Implication**: the r207 mount logic in this plan lives in **`avidtech6/vibecoder-standalone/src/host/mounts/`** (a new directory), NOT in `studio/modules/vibecoder/src/host/` as the prior turn's plan stated. The `InspectorCard` referenced in a788 is the **`studio/modules/vibescope/src/cards/InspectorCard.tsx`** in freshvibestudio, which is a different component from the `inspector:` tool surface in vibecoder-standalone's `tools.ts:522`. Both are addressed below.
**`studio/modules/vibescope/src/cards/InspectorCard.tsx` migration (a788) is unchanged** — it still gets rewritten as a ~15 LoC wrapper. The new r207 work is the **PM2-friendly host mounts in vibecoder-standalone**.
---
## TL;DR
r207 implements the three PM2-friendly host mounts (PreviewHostVSIL, InspectorHost, CompositionGraphHost) that **consume the r206 `StudioContext` adapter** (the `useStudio()` hook + `bus` + capability bags) and **emit to the r206 `EventPayloadMap`** (typed events on `window`).
**5 new files, ~750 LoC** at `avidtech6/vibecoder-standalone/src/host/mounts/`:
| File | LoC | Purpose |
|------|-----|---------|
| `PreviewHostVSIL.tsx` | 180 | PM2-mountable VSIL preview host |
| `InspectorHost.tsx` | 150 | PM2-mountable inspector host |
| `CompositionGraphHost.tsx` | 220 | r205-aligned composition host |
| `mounts.ts` | 80 | Single-entry mount for all 3 hosts (with capability manifests) |
| `__tests__/mounts.test.ts` | 120 | 8 test cases |
**Plus 1 modification to `src/vibechat/tools.ts`** (1 LoC, comment update for r207 binding).
**No new pact files.** r207 is structural + integration; the r206 capability types + event types are already canonical.
**Constraints honored**:
- ✅ No iframe (native React render, same as a788, a789, a791)
- ✅ No postMessage (window-level `CustomEvent` via `ctx.bus`)
- ✅ Aligned with a789 (Unified Gallery Panel)
- ✅ Aligned with a788 (Inspector → studio wrapper migration)
- ✅ Aligned with a791 (r205 Composition Graph Host)
- ✅ Uses 5 surface states (MAIN / WORKING / DRAFT / PREVIEW / ORIGIN) — surfaces are `SurfaceId` from `types.ts`
- ✅ READ-ONLY invariant per inspector doctrine (a788)
---
## 1. The r206 contract (as shipped, NOT the spec I wrote before)
The r206 contract is at `avidtech6/vibecoder-standalone@07463582794d6d3696e69d4242120d10cc07a30e`. Read by r207.
### 1.1 Imports for r207
```ts
// avidtech6/vibecoder-standalone/src/host/mounts/PreviewHostVSIL.tsx
import { useStudio } from '../workspace/context';
import type {
StudioContext, // ctx shape (workspaceId, mode, activeSurface, capability bags, bus, state)
StudioBus, // window-bus (on/off/emit, typed by EventPayloadMap)
StudioState, // activeAssetId, activeCardId, activeRegionId, activePreviewSlot, mountedHosts
} from '../workspace/context';
import type { EventPayloadMap } from '../workspace/event-types';
import type {
PreviewHostName, // 'PreviewHostVSIL' | 'PreviewHostOrigin' | 'PreviewHostApp' | 'PreviewHostSurface'
SurfaceId, // 'main' | 'working' | 'draft' | 'preview' | 'origin'
AssetId, CardId, RegionId, PreviewSlotId,
} from '../workspace/primitives';
import type {
InspectorCapabilities,
GalleryCapabilities,
PreviewCapabilities,
SurfaceCapabilities,
OriginPreviewCapabilities,
AppPreviewCapabilities,
CapabilityResult,
CameraState, LightingState, AtmosphereState, VSILScene, VSILRegion,
} from '../workspace/capability-types';
```
### 1.2 The r206 shape (nested bag, per r204 v2 §4.2)
```ts
// Real r206: StudioContext is a NESTED BAG, not flat methods.
// (My prior spec used flat methods — that was WRONG.)
interface StudioContext {
workspaceId: string; // 'vibecoder'
mode: 'dev' | 'prod';
activeSurface: SurfaceId; // 'main' | 'working' | 'draft' | 'preview' | 'origin'
inspector: InspectorCapabilities; // { vsil: {...}, region: {...} }
gallery: GalleryCapabilities; // { selectCard, loadAsset, previewMode, asset: {...} }
preview: PreviewCapabilities; // 8 ops including 'preview.vsil.show'
surface: SurfaceCapabilities; // 5 ops (one per surface)
origin: OriginPreviewCapabilities;
app: AppPreviewCapabilities;
bus: StudioBus; // window-level typed event bus
state: StudioState; // activeAssetId, mountedHosts: PreviewHostName[]
}
```
### 1.3 Real r206 capabilities (excerpt)
```ts
// From capability-types.ts (real r206)
interface PreviewCapabilities {
'preview.vsil.show': (args: { assetId: AssetId; mode: 'wireframe'|'shaded'|'rendered'|'split'; targetSurface?: SurfaceId }) => Promise<CapabilityResult<{}>>;
'preview.origin.show': (args: { surface: SurfaceId; mode: 'card'|'hero'|'full'; targetSurface?: SurfaceId }) => Promise<CapabilityResult<{}>>;
'preview.app.show': (args: { appName: string; mode: 'live'|'mocked'|'static'; device?: 'desktop'|'tablet'|'mobile'; targetSurface?: SurfaceId }) => Promise<CapabilityResult<{}>>;
'preview.surface.show': (args: { surface: SurfaceId; mode: 'live'|'frozen'|'diff' }) => Promise<CapabilityResult<{}>>;
'preview.clear': (args: { slot?: PreviewSlotId }) => Promise<CapabilityResult<{}>>;
'preview.refocus': (args: { slot: PreviewSlotId }) => Promise<CapabilityResult<{}>>;
'preview.compare': (args: { slots: [PreviewSlotId, PreviewSlotId] }) => Promise<CapabilityResult<{}>>;
}
interface InspectorCapabilities {
vsil: {
loadScene: (args: { assetId; sceneId? }) => Promise<CapabilityResult<{ scene: VSILScene }>>;
applyCamera: (args: { assetId; op: 'orbit'|'pan'|'zoom'|'reset'|'preset'; params? }) => Promise<CapabilityResult<{ camera: CameraState }>>;
applyLighting: (args: { assetId; op: 'key'|'fill'|'rim'|'ambient'|'preset'; params? }) => Promise<CapabilityResult<{ lighting: LightingState }>>;
applyAtmosphere: (args: { assetId; op: 'fog'|'gradient'|'particles'|'preset'; params? }) => Promise<CapabilityResult<{ atmosphere: AtmosphereState }>>;
exportMetadata: (args: { assetId; format?: 'vsil-native' }) => Promise<CapabilityResult<{ metadata: string }>>;
previewMode: { change: (args: { mode: 'wireframe'|'shaded'|'rendered'|'split' }) => Promise<CapabilityResult<{}>> };
asset: {
bind: (args: { assetId; surface: SurfaceId; source: 'gallery'|'vibechat'|'edge'|'bridge' }) => Promise<CapabilityResult<{}>>;
unbind: (args: { assetId }) => Promise<CapabilityResult<{}>>;
};
};
region: {
select: (args: { regionId }) => Promise<CapabilityResult<{ region: VSILRegion }>>;
list: (args: { assetId }) => Promise<CapabilityResult<{ regions: VSILRegion[] }>>;
applyPreset: (args: { regionId; presetId; strength? }) => Promise<CapabilityResult<{ region: VSILRegion }>>;
transform: (args: { regionId; op: 'translate'|'scale'|'rotate'|'reset'; params? }) => Promise<CapabilityResult<{ region: VSILRegion }>>;
};
}
```
### 1.4 Real r206 EventPayloadMap (33 events, typed payloads)
```ts
// From event-types.ts (real r206) — 33 events
interface EventPayloadMap {
// Tier 1 (existing)
'fv:preview-mode-change': FvPreviewModeChangeDetail;
'fv:vc-preview': FvVcPreviewDetail;
// Tier 2 (preview events)
'preview:vsil': PreviewEventDetail;
'preview:origin': PreviewEventDetail;
'preview:app': PreviewEventDetail;
'preview:surface:main': PreviewEventDetail;
'preview:surface:working': PreviewEventDetail;
'preview:surface:draft': PreviewEventDetail;
'preview:surface:preview': PreviewEventDetail;
'preview:surface:origin': PreviewEventDetail;
// Tier 3 (r204 v2)
'vsil:scene-loaded': VSILSceneLoadedDetail;
'vsil:camera-applied': VSILCameraAppliedDetail;
'vsil:lighting-applied': VSILLightingAppliedDetail;
'vsil:atmosphere-applied': VSILAtmosphereAppliedDetail;
'vsil:metadata-exported': VSILMetadataExportedDetail;
'vsil:preview-mode-changed': VSILPreviewModeChangedDetail;
'vsil:asset-bound': VSILAssetBoundDetail; // r207 mounts subscribe to this
'gallery:card-selected': GalleryCardSelectedDetail;
'gallery:asset-loaded': GalleryAssetLoadedDetail;
'gallery:preview-mode-changed': GalleryPreviewModeChangedDetail;
'gallery:surface-mounted': GallerySurfaceMountedDetail;
'gallery:asset-bound': GalleryAssetBoundDetail;
'surface:activated': SurfaceActivatedDetail;
'app:built': AppBuiltDetail;
'app:device-changed': AppDeviceChangedDetail;
'app:reloaded': AppReloadedDetail;
'origin:fetched': OriginFetchedDetail;
'origin:viewport-changed': OriginViewportChangedDetail;
'preview:cleared': PreviewClearedDetail;
'preview:refocused': PreviewRefocusedDetail;
'preview:comparing': PreviewComparingDetail;
// r205 additions
'region:selected': RegionSelectedDetail;
'region:listed': RegionListedDetail;
'region:preset-applied': RegionPresetAppliedDetail;
'region:transformed': RegionTransformedDetail;
'composition:host-mounted': CompositionHostMountedDetail; // r207 emits on mount
'composition:host-unmounted': CompositionHostUnmountedDetail; // r207 emits on unmount
'composition:host-focus': CompositionHostFocusDetail;
'composition:region-rerender': CompositionRegionRerenderDetail;
}
```
### 1.5 Lazy-mount pattern (Q1 from r204 v2, r206 already implements)
```ts
// From context.ts:95-100 (real r206)
function ensureHostMounted(host: PreviewHostName, surface: SurfaceId, ctx: StudioContext): void {
if (!ctx.state.mountedHosts.includes(host)) {
ctx.bus.emit('composition:host-mounted', { host, surface });
ctx.state.mountedHosts.push(host);
}
}
// The capability `ctx.inspector.vsil.asset.bind()` already triggers this.
// r207 mounts subscribe to 'composition:host-mounted' to know when to render.
```
**r207 implication**: hosts do NOT auto-mount. They mount when `vsil.asset.bind()` (or `gallery.asset.bind()`) is called for the first time. The capability layer is responsible for triggering the mount event. r207 hosts listen for the event and render.
### 1.6 Surface-targeting pattern (Q2 from r204 v2, r206 already implements)
```ts
// All preview:* events carry targetSurface
interface PreviewEventDetail {
slotId: PreviewSlotId;
surface: SurfaceId;
targetSurface: SurfaceId; // <-- the surface this preview targets
mode: string;
assetId?: AssetId;
host: PreviewHostName;
source: 'gallery' | 'inspector' | 'vibechat' | 'edge' | 'bridge' | 'system' | 'cascade';
timestamp: number;
}
```
**r207 implication**: when a host receives a preview event, it must check `targetSurface === ctx.activeSurface` before rendering. Otherwise, the preview is for a different surface and the host stays dormant.
---
## 2. r207 files (the actual mount logic)
### 2.1 `PreviewHostVSIL.tsx` (~180 LoC)
PM2-mountable wrapper around VSIL preview. Reads `ctx.inspector.vsil` + `ctx.preview['preview.vsil.show']`, subscribes to 4 events, manages lazy-mount + surface-targeting.
```ts
// avidtech6/vibecoder-standalone/src/host/mounts/PreviewHostVSIL.tsx
import React, { useEffect, useState, useCallback } from 'react';
import { useStudio } from '../workspace/context';
import type { EventPayloadMap } from '../workspace/event-types';
import type {
VSILAssetBoundDetail, VSILSceneLoadedDetail, PreviewEventDetail,
} from '../workspace/event-types';
import type { PreviewHostName, SurfaceId, AssetId } from '../workspace/primitives';
const HOST_NAME: PreviewHostName = 'PreviewHostVSIL';
export interface PreviewHostVSILProps {
/** Optional explicit asset id. If null, host uses ctx.state.activeAssetId. */
assetId?: AssetId | null;
/** Optional preview mode override. If null, uses last `vsil:preview-mode-changed` event. */
mode?: 'wireframe' | 'shaded' | 'rendered' | 'split' | null;
/** Optional explicit surface. If null, uses ctx.activeSurface. */
surface?: SurfaceId | null;
}
export const PreviewHostVSIL: React.FC<PreviewHostVSILProps> = (props) => {
const ctx = useStudio();
const [mounted, setMounted] = useState(ctx.state.mountedHosts.includes(HOST_NAME));
const [sceneId, setSceneId] = useState<string | null>(null);
const [previewMode, setPreviewMode] = useState<'wireframe'|'shaded'|'rendered'|'split'>('shaded');
// Subscribe to mount event (lazy-mount contract from r206)
useEffect(() => {
return ctx.bus.on('composition:host-mounted', (e) => {
if (e.detail.host === HOST_NAME) setMounted(true);
});
}, [ctx.bus]);
// Subscribe to asset-bound (re-render when asset changes)
useEffect(() => {
return ctx.bus.on('vsil:asset-bound', async (e: CustomEvent<VSILAssetBoundDetail>) => {
const target = props.surface ?? ctx.activeSurface;
if (e.detail.surface !== target) return; // surface-targeting (Q2)
// Load scene via capability
const result = await ctx.inspector.vsil.loadScene({ assetId: e.detail.assetId });
if (result.ok) setSceneId(result.scene.id);
});
}, [ctx, props.surface]);
// Subscribe to preview-mode-changed
useEffect(() => {
return ctx.bus.on('vsil:preview-mode-changed', (e) => {
setPreviewMode(e.detail.mode);
});
}, [ctx.bus]);
// Lifecycle: emit mount/unmount
useEffect(() => {
if (!mounted) return;
const surface = props.surface ?? ctx.activeSurface;
return () => {
ctx.bus.emit('composition:host-unmounted', { host: HOST_NAME, surface });
};
}, [mounted, ctx.bus, props.surface, ctx.activeSurface]);
if (!mounted) {
return <div data-preview-host="vsil" data-mounted="false">VSIL preview not bound</div>;
}
return (
<div data-preview-host="vsil" data-mounted="true" data-scene={sceneId ?? ''} data-mode={previewMode}>
{/* Render the scene using the studio's VSIL renderer (consume VSILScene from ctx) */}
<VSILRenderer sceneId={sceneId} mode={previewMode} />
</div>
);
};
```
### 2.2 `InspectorHost.tsx` (~150 LoC)
PM2-mountable inspector. Reads `ctx.inspector.vsil` + `ctx.inspector.region`, subscribes to 3 events, enforces READ-ONLY.
```ts
// avidtech6/vibecoder-standalone/src/host/mounts/InspectorHost.tsx
import React, { useEffect, useState, useCallback } from 'react';
import { useStudio } from '../workspace/context';
import type {
RegionSelectedDetail, VSILAssetBoundDetail, SurfaceActivatedDetail,
} from '../workspace/event-types';
import type { RegionId, AssetId, SurfaceId } from '../workspace/primitives';
export interface InspectorHostProps {
/** Initial sub-mode. Default: 'ast'. */
initialSubMode?: 'ast' | 'props' | 'state' | 'logs' | 'metadata';
/** Read-only override. Default: true (a788 pact invariant). */
readOnly?: boolean;
}
export const InspectorHost: React.FC<InspectorHostProps> = (props) => {
const ctx = useStudio();
const [activeRegionId, setActiveRegionId] = useState<RegionId | null>(ctx.state.activeRegionId);
const [subMode, setSubMode] = useState(props.initialSubMode ?? 'ast');
const readOnly = props.readOnly ?? true; // READ-ONLY by default (a788 invariant)
// Subscribe to region:selected
useEffect(() => {
return ctx.bus.on('region:selected', (e: CustomEvent<RegionSelectedDetail>) => {
if (e.detail.source === 'vibechat' || e.detail.source === 'user') {
setActiveRegionId(e.detail.regionId);
ctx.state.activeRegionId = e.detail.regionId;
}
});
}, [ctx.bus]);
// Subscribe to vsil:asset-bound (clear region when asset changes)
useEffect(() => {
return ctx.bus.on('vsil:asset-bound', (e: CustomEvent<VSILAssetBoundDetail>) => {
if (e.detail.surface !== ctx.activeSurface) return;
setActiveRegionId(null);
});
}, [ctx.bus, ctx.activeSurface]);
// Reject transform/preset edits if read-only
const onTransform = useCallback(async (op: 'translate'|'scale'|'rotate'|'reset', params?: object) => {
if (readOnly) {
console.warn('[InspectorHost] READ-ONLY: transform rejected (a788 invariant)');
return;
}
if (!activeRegionId) return;
await ctx.inspector.region.transform({ regionId: activeRegionId, op, params });
}, [ctx.inspector.region, activeRegionId, readOnly]);
return (
<div data-inspector-host="" data-read-only={readOnly ? '1' : '0'} data-sub-mode={subMode}>
{/* 5 sub-modes: AST, Props, State, Logs, Metadata — READ-ONLY on AST/State/Logs/Metadata per a788 */}
<InspectorSubModeTabs subMode={subMode} onChange={setSubMode} />
<InspectorBody subMode={subMode} regionId={activeRegionId} onTransform={onTransform} readOnly={readOnly} />
</div>
);
};
```
### 2.3 `CompositionGraphHost.tsx` (~220 LoC)
PM2-mountable r205 composition host. Reads `ctx.preview` + `ctx.inspector.vsil` + `ctx.inspector.region`, subscribes to composition + region events, manages multi-region rendering with shared camera/lighting/atmosphere.
```ts
// avidtech6/vibecoder-standalone/src/host/mounts/CompositionGraphHost.tsx
import React, { useEffect, useState, useCallback } from 'react';
import { useStudio } from '../workspace/context';
import type {
CompositionHostMountedDetail, RegionListedDetail, RegionSelectedDetail,
RegionTransformedDetail, RegionPresetAppliedDetail, CompositionRegionRerenderDetail,
} from '../workspace/event-types';
import type { RegionId, AssetId, SurfaceId, PreviewHostName } from '../workspace/primitives';
const HOST_NAME: PreviewHostName = 'PreviewHostVSIL'; // reuses same host for region rendering
export interface CompositionGraphHostProps {
/** The composition asset id. If null, derives from ctx.state.activeAssetId. */
assetId?: AssetId | null;
/** Initial composition regions (for tests). */
initialRegionIds?: readonly RegionId[];
}
export const CompositionGraphHost: React.FC<CompositionGraphHostProps> = (props) => {
const ctx = useStudio();
const [regionIds, setRegionIds] = useState<readonly RegionId[]>(props.initialRegionIds ?? []);
const [selectedRegionIds, setSelectedRegionIds] = useState<readonly RegionId[]>([]);
const [loaded, setLoaded] = useState(false);
// Lazy-mount: trigger via vsil.asset.bind if not yet loaded
useEffect(() => {
const target = props.assetId ?? ctx.state.activeAssetId;
if (!target || loaded) return;
(async () => {
const result = await ctx.inspector.vsil.asset.bind({
assetId: target,
surface: ctx.activeSurface,
source: 'cascade',
});
if (result.ok) {
const regions = await ctx.inspector.region.list({ assetId: target });
if (regions.ok) {
setRegionIds(regions.regions.map((r) => r.id));
setLoaded(true);
}
}
})();
}, [ctx, props.assetId, loaded]);
// Subscribe to region:transformed (re-render on transform)
useEffect(() => {
return ctx.bus.on('region:transformed', (e: CustomEvent<RegionTransformedDetail>) => {
if (regionIds.includes(e.detail.regionId)) {
// Re-render is automatic via state — no-op here unless we need to force
}
});
}, [ctx.bus, regionIds]);
// Subscribe to region:preset-applied
useEffect(() => {
return ctx.bus.on('region:preset-applied', (e: CustomEvent<RegionPresetAppliedDetail>) => {
if (regionIds.includes(e.detail.regionId)) {
// Preset already applied via capability; UI re-renders via state
}
});
}, [ctx.bus, regionIds]);
// Emit composition:host-focus on selection
const onRegionClick = useCallback((regionId: RegionId) => {
setSelectedRegionIds([regionId]);
ctx.bus.emit('composition:host-focus', {
host: HOST_NAME,
surface: ctx.activeSurface,
focusId: regionId,
focusType: 'region',
});
}, [ctx.bus, ctx.activeSurface]);
if (!loaded) {
return <div data-composition-host="" data-loaded="false">No composition loaded</div>;
}
return (
<div data-composition-host="" data-loaded="true" data-region-count={regionIds.length}>
{regionIds.map((id) => (
<RegionOverlay
key={id}
regionId={id}
selected={selectedRegionIds.includes(id)}
onClick={() => onRegionClick(id)}
/>
))}
</div>
);
};
```
### 2.4 `mounts.ts` (~80 LoC)
Single entry point that mounts all 3 hosts and exports the 3 frozen capability manifests.
```ts
// avidtech6/vibecoder-standalone/src/host/mounts/mounts.ts
import { defineCapability } from '../workspace/capability-types';
import type { Capability } from '../workspace/capability-types';
import { PreviewHostVSIL } from './PreviewHostVSIL';
import { InspectorHost } from './InspectorHost';
import { CompositionGraphHost } from './CompositionGraphHost';
export const PREVIEW_HOST_VSIL_CAPABILITY = defineCapability({
id: 'preview-host-vsil',
version: '0.1.0',
can: { previewVSIL: true, lazyMount: true, surfaceTargeting: true, readOnlyByDefault: false },
needs: { assetId: 'required', surface: 'optional' },
contextRules: {
showInWorkspace: ['vibecoder', 'oscar-tree-academy', 'oscar-static-source', 'hopfan-europe'],
showInContext: ['asset-selected', 'surface-preview'],
showInMode: ['normal', 'half', 'full'],
},
listens: ['composition:host-mounted', 'vsil:asset-bound', 'vsil:preview-mode-changed', 'preview:vsil'],
emits: ['composition:host-unmounted'],
availableSlots: ['cinematic:main', 'cinematic:hero', 'cinematic:full'],
});
export const INSPECTOR_HOST_CAPABILITY = defineCapability({
id: 'inspector-host',
version: '0.1.0',
can: { inspect: true, selectRegion: true, readOnly: true, transformReject: true },
needs: { assetId: 'required' },
contextRules: {
showInWorkspace: ['vibecoder', 'oscar-tree-academy', 'oscar-static-source', 'hopfan-europe'],
showInContext: ['asset-selected'],
showInMode: ['normal', 'half', 'full'],
},
listens: ['region:selected', 'vsil:asset-bound', 'surface:activated'],
emits: [],
availableSlots: ['inspector:main'],
});
export const COMPOSITION_GRAPH_HOST_CAPABILITY = defineCapability({
id: 'composition-graph-host',
version: '0.1.0',
can: { renderComposition: true, manageRegions: true, emitHostFocus: true },
needs: { assetId: 'required' },
contextRules: {
showInWorkspace: ['vibecoder', 'oscar-tree-academy'],
showInContext: ['asset-selected', 'composition-active'],
showInMode: ['normal', 'half', 'full'],
},
listens: ['region:listed', 'region:transformed', 'region:preset-applied', 'composition:host-mounted'],
emits: ['composition:host-focus', 'composition:host-unmounted'],
availableSlots: ['cinematic:composition', 'cinematic:multi-region'],
});
export function mountAllHosts() {
return {
preview: PreviewHostVSIL,
inspector: InspectorHost,
composition: CompositionGraphHost,
};
}
```
### 2.5 `__tests__/mounts.test.ts` (~120 LoC)
8 test cases:
1. `PreviewHostVSIL` is not mounted when `ctx.state.mountedHosts` does not include `'PreviewHostVSIL'`
2. `PreviewHostVSIL` mounts when `composition:host-mounted` event fires with matching host name
3. `PreviewHostVSIL` re-renders on `vsil:asset-bound` event with matching surface
4. `PreviewHostVSIL` ignores `vsil:asset-bound` event with non-matching surface (Q2 surface-targeting)
5. `InspectorHost` is read-only by default (rejects `transform` capability calls)
6. `InspectorHost` selects region on `region:selected` event from `'vibechat'` or `'user'` source
7. `CompositionGraphHost` loads regions via `inspector.region.list` on mount
8. `CompositionGraphHost` emits `composition:host-focus` on region click
---
## 3. Wiring (region events, composition events, preview host lifecycle)
### 3.1 Region events (r205 → r207 → consumers)
```
VibeChat (vsil_region_select tool, tools.ts:522)
└─→ ctx.inspector.region.select({ regionId })
└─→ bus.emit('region:selected', { regionId, assetId, source: 'vibechat' })
├─→ InspectorHost subscribes (r207 §2.2)
│ └─→ updates `activeRegionId` in its own state + ctx.state
├─→ CompositionGraphHost subscribes (r207 §2.3)
│ └─→ re-renders the region overlay
└─→ Edge Panel subscribes (separate)
└─→ shows "Open Inspector for this region" tile
```
### 3.2 Composition events
```
CompositionGraphHost (r207 §2.3) lifecycle:
├─ on mount: ctx.inspector.vsil.asset.bind(...) → bus.emit('composition:host-mounted', ...)
├─ on region click: bus.emit('composition:host-focus', { host, surface, focusId: regionId, focusType: 'region' })
└─ on unmount: bus.emit('composition:host-unmounted', { host, surface })
PreviewHostVSIL (r207 §2.1) lifecycle:
├─ subscribes to 'composition:host-mounted' (lazy-mount contract from r206)
├─ subscribes to 'vsil:asset-bound' (re-render on asset change)
└─ emits 'composition:host-unmounted' on unmount
```
### 3.3 Preview host lifecycle (r206 lazy-mount contract)
```
User clicks asset in Gallery
└─→ ctx.gallery.asset.bind({ assetId, source: 'card' })
└─→ cascades to ctx.inspector.vsil.asset.bind({ assetId, surface, source: 'cascade' })
└─→ r206 ensureHostMounted() emits 'composition:host-mounted'
└─→ PreviewHostVSIL (subscribed) calls setMounted(true)
└─→ React renders <VSILRenderer>
User navigates to different surface
└─→ ctx.surface.activate({ surface: 'preview' })
└─→ bus.emit('surface:activated', { surface, previous })
└─→ PreviewHostVSIL checks: if event.surface !== props.surface, do nothing (Q2)
User unmounts (closes panel)
└─→ React unmount lifecycle
└─→ useEffect cleanup emits 'composition:host-unmounted'
```
---
## 4. InspectorCard stub migration (a788)
### 4.1 The stub
Per a788: `studio/modules/vibescope/src/cards/InspectorCard.tsx` (278 LoC, in `avidtech6/freshvibestudio`) uses `useVSILSource()` (module-scoped), has no event subscription, and can only mount inside the VibeScope workspace.
### 4.2 The migration
Rewrite as a thin wrapper that uses `useStudio()` (from r206) + delegates to `<InspectorHost>` (r207 §2.2):
```ts
// studio/modules/vibescope/src/cards/InspectorCard.tsx (rewritten, ~15 LoC)
import { useStudio } from 'avidtech6/vibecoder-standalone/src/host/workspace/context';
import { InspectorHost } from 'avidtech6/vibecoder-standalone/src/host/mounts/InspectorHost';
export const InspectorCard: React.FC = () => {
const ctx = useStudio();
return <InspectorHost initialSubMode="ast" readOnly={true} />;
};
```
**This requires the freshvibestudio studio module to depend on the vibecoder-standalone r206+ exports.** Two options:
- **(a) Vendor `context.ts` + `InspectorHost.tsx` into freshvibestudio** — creates a parallel copy, anti-pattern
- **(b) Link via package alias** — `package.json` in freshvibestudio has `"vibecoder-standalone": "avidtech6/vibecoder-standalone#clean/baseline-plus-wdm"` — cleaner
**Default: option (b)** — no vendor copies. r207 must include a `package.json` patch in freshvibestudio.
### 4.3 tools.ts binding (vibecoder-standalone)
The `vsil_region_select` tool in `src/vibechat/tools.ts:522` says "Triggers InspectorCard to focus the region". This is r207's job — the tool calls `ctx.inspector.region.select()`, which emits `'region:selected'`, which the new `InspectorHost` subscribes to. No code change needed in `tools.ts` — only a comment update to reference r207.
```ts
// src/vibechat/tools.ts:521 (comment update only)
description: 'Select a region in the active VSIL asset. Triggers InspectorHost (r207) to focus the region. Use vsil_region_list first to discover ids.',
```
---
## 5. Event contract v2 — adoption strategy
The real r206 events are typed (33 events, `EventPayloadMap`). The 11 events in my prior spec (`preview-host:mounted`, etc.) are **stubs that need replacing** with the canonical r206 events:
| Prior spec (a793) | Real r206 | Notes |
|-------------------|-----------|-------|
| `studio:context-changed` | (none — state is read via `useStudio()`) | r207 doesn't subscribe; the context is a singleton |
| `studio:asset-bound` | `vsil:asset-bound` | r206 |
| `studio:surface-state-changed` | `surface:activated` | r206 |
| `preview-host:mounted` | `composition:host-mounted` | r206 |
| `preview-host:unmounted` | `composition:host-unmounted` | r206 |
| `preview-host:error` | (none — use ErrorBoundary) | r207 wraps in React ErrorBoundary |
| `composition:region-clicked` | `composition:host-focus` (focusType: 'region') | r206 |
| `composition:region-parse-failed` | (none — not in r206) | deferred |
| `composition:region-metadata-changed` | `region:transformed` | r206 |
| `composition:edit-rejected` | (none — capability returns `{ ok: false, reason }`) | r206 pattern |
| `inspector:source-changed` | `vsil:asset-bound` (re-derive) | r206 |
| `inspector:sub-mode-changed` | (none — local to InspectorHost) | r207 local state |
| `composition:loaded` | `region:listed` | r206 |
**r207 uses 8 r206 events only.** No new event types added.
---
## 6. Aligned with prior work
- **a786 (FreshCards)** — `SurfaceRegistry` + `SurfaceLifecycleManager` are not consumed by r207 (vibecoder-standalone uses its own `types.ts` SurfaceId + ProjectState). Both surfaces coexist; no conflict.
- **a787 (VPS location)** — deployed bridge at `185.249.73.178:/var/www/freshvibeapps/clients/vibecoder/` serves the new bundle. SCP needs `-O`.
- **a788 (Inspector)** — `studio/modules/vibescope/src/cards/InspectorCard.tsx` rewritten as ~15 LoC wrapper (r207 §4.2). VibeScope still works.
- **a789 (Unified Gallery Panel)** — `GalleryCapabilities.asset.bind` is the entry point for VSIL preview cascade (r206 §1.5).
- **a791 (r205 Composition Graph Host)** — r207 §2.3 implements the Region concept on top of r206 `inspector.region.*` capabilities.
- **ID10 v2 capability layer** (b1818, b1269) — `*_CAPABILITIES` pattern reused (r207 §2.4).
---
## 7. Drift identified
| Pact / source | Drift | Severity |
|---------------|-------|----------|
| Prior turn's a793 r207 spec (workspace-only) | r206 is shipped, not the spec I wrote | **resolved by this v2** |
| a788 `studio/.../InspectorCard.tsx` | module-scoped `useVSILSource` — incompatible with r206's `useStudio` | medium (r207 §4 migration) |
| a789 `PreviewHostVSIL` (a conceptual panel) | is a `PreviewHostName` enum value in r206, not a component | resolved (r207 §2.1 implements) |
| a791 `CompositionGraphHost` (a conceptual component) | not shipped yet | resolved (r207 §2.3 implements) |
| a789/a791 v1 untyped `CustomEvent` | r206 has typed `EventPayloadMap` | resolved (r207 binds to v2) |
| `studio/` module in freshvibestudio | depends on r206 but lives in a different repo | medium (r207 §4.2 package alias) |
**No new pact files required for r207.** r207 is structural + integration; the r206 capability types + event types are already canonical.
---
## 8. The 8-step implementation plan (NOT this turn — design phase only)
For operator awareness. **This is NOT to be done in this discovery-only turn.**
| # | Step | Time | What | Risk |
|---|------|------|------|------|
| 1 | Branch from `clean/baseline-plus-wdm` | 10m | `git checkout -b feat/id9-r207-mount` from `0746358` | low |
| 2 | `PreviewHostVSIL.tsx` | 4h | Mount + 4 event subscriptions + lazy-mount + surface-targeting | low |
| 3 | `InspectorHost.tsx` | 3h | Mount + 3 event subscriptions + READ-ONLY invariant | low |
| 4 | `CompositionGraphHost.tsx` | 5h | Mount + 4 event subscriptions + region list + focus emit | medium |
| 5 | `mounts.ts` | 2h | Single-entry + 3 capability manifests | low |
| 6 | `__tests__/mounts.test.ts` | 4h | 8 test cases | low |
| 7 | `tools.ts:521` comment update | 5m | Trivial 1-line | low |
| 8 | `studio/.../InspectorCard.tsx` rewrite | 1h | ~15 LoC wrapper + package.json alias in freshvibestudio | medium |
| 9 | Playwright smoke | 2h | Boot vibecoder-standalone, mount all 3 hosts, screenshot | low |
**Total: 21-22h**, 3-4 days end-to-end.
---
## 9. The 1 thing to remember
> **r207 binds to the REAL r206 contract** (nested bag, 33 events, lazy-mount, surface-targeting, READ-ONLY), not the spec I wrote in a793. The real r206 is at `avidtech6/vibecoder-standalone@07463582794d6d3696e69d4242120d10cc07a30e`. The prior turn's plan is **superseded** by this v2.
---
## 10. The 3 open questions for operator
| # | Question | Default if no answer |
|---|----------|---------------------|
| Q1 | **freshvibestudio InspectorCard migration**: vendor r206+r207 (option a, anti-pattern) or link via package alias (option b)? | option b — package alias |
| Q2 | **r207 implementation start**: this discovery only, no code, awaiting next directive? | yes, this is discovery-only |
| Q3 | **Composition host region transformation in v1**: allow `transform` calls from r207 (writes), or strictly READ-ONLY in v1 (defers to r208)? | strictly READ-ONLY in v1 — defers write to r208 |
---
## 11. Cross-references
- a786 (FreshCards location) — separate workspace
- a787 (VPS location) — `185.249.73.178:/var/www/freshvibeapps/clients/vibecoder/`
- a788 (Inspector) — `studio/modules/vibescope/src/cards/InspectorCard.tsx` migration
- a789 (Unified Gallery Panel) — `GalleryCapabilities.asset.bind` entry
- a791 (r205 VSIL Composition Graph Host) — Region concept on r206 capabilities
- a793 / a795 (prior turn, superseded) — workspace-only spec
- ID10 v2 capability layer (b1818, b1269)
- r203 / r204 / r205 prior work — operator panel
---
**Discovery-only. No code. No pact edits. No extraction. No implementation.**
— vibecoder-standalone-mavis (thread 9)