# 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)