# Atelier Refonte « Workbench Console » Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Refondre l'UI de `apps/atelier` : retirer la simulation 3D, appliquer un thème néo-rétro « Workbench » (palette firmware), et remplacer la colonne 3D par un Banc d'essai (aperçu écran device fidèle + vue flux du scénario, split vertical). Blockly est conservé.

**Architecture:** Trois phases séquentielles — A) soustraction 3D (build doit rester vert), B) thème Workbench (CSS + thème Blockly + re-skin), C) Banc d'essai (`WorkbenchPane` = `DeviceScreen` + `FlowGraph` pilotés par un `selectionStore`, lisant l'IR de `runtimeStore`).

**Tech Stack:** React 19, TypeScript (strict, `exactOptionalPropertyTypes`), Zustand, react-resizable-panels, Blockly, Vite. Verif par tâche : `pnpm exec tsc --noEmit` + `pnpm exec vite build` verts (pas de tests unitaires pour le rendu visuel ; les hooks e2e existants sont mis à jour en phase A).

**Réfs :** spec `docs/superpowers/specs/2026-06-13-atelier-refonte-workbench-design.md` ; types IR `frontend-v3/packages/scenario-engine/src/runtime-ir.ts` (`RuntimeIr.steps: RuntimeStep[]`, `RuntimeStep.id`, `RuntimeStep.scene?: StepScene{title,subtitle,symbol,effect}`, `RuntimeTransition.target_step_id`).

**Convention commit :** `git -c user.name=electron-rare -c user.email=c.saillant@gmail.com commit`. Toutes les commandes depuis `frontend-v3/apps/atelier` sauf indication.

---

## File Structure

| Fichier | Responsabilité | Phase |
|---|---|---|
| `src/components/StagePane.tsx` + `src/scene/*` + `src/puzzles/*` + `src/modes/*` + `src/stores/simStore.ts` | **supprimés** (3D) | A |
| `src/components/Layout.tsx` | layout : Blockly + `WorkbenchPane` + console ; toggle `⌘B` du banc | A, C |
| `src/components/ConsolePane.tsx` | console validation seule (retrait des onglets de modes) | A |
| `src/App.tsx` | hooks dev sans `sim` | A |
| `vite.config.ts` | retrait `manualChunks` three/r3f | A |
| `package.json` | retrait deps three/r3f | A |
| `src/theme/workbench.ts` | constantes palette/fonts (source unique) | B |
| `src/main.css` | shell + chrome Workbench | B |
| `src/components/BlocklyWorkspace.tsx` | thème Blockly Workbench | B |
| `src/components/MenuBar.tsx`, panneaux flottants | re-skin | B |
| `src/lib/screenSpec.ts` | déjà présent (P3a) — source des couleurs/limites écran | B (réf) |
| `src/components/DeviceScreenView.tsx` | rendu LCD fidèle réutilisable (extrait de `ScreenEditorPanel`) | C |
| `src/stores/selectionStore.ts` | `selectedStepId` | C |
| `src/lib/irSteps.ts` | parse `currentIr` → `{steps, edges}` | C |
| `src/components/FlowGraph.tsx` | graphe d'étapes cliquable | C |
| `src/components/DeviceScreen.tsx` | écran de l'étape sélectionnée | C |
| `src/components/WorkbenchPane.tsx` | split vertical Écran/Flux | C |

---

## PHASE A — Retrait de la 3D

### Task A1: Supprimer les fichiers 3D et le simStore

**Files:**
- Delete: `src/components/StagePane.tsx`, `src/scene/` (NpcBubble, PuzzleStation, PuzzleStations, Room, RoomScene, RtcPhone), `src/puzzles/` (P1Sound, P5Morse, P6Symbols, P7Coffre), `src/modes/` (DemoMode, SandboxMode, TestMode), `src/stores/simStore.ts`

- [ ] **Step 1: Supprimer les répertoires/fichiers 3D**

```bash
cd frontend-v3/apps/atelier
git rm -r src/scene src/puzzles src/modes src/components/StagePane.tsx src/stores/simStore.ts
```

- [ ] **Step 2: Lister les imports résiduels à corriger**

Run: `grep -rnE "simStore|StagePane|/scene/|/puzzles/|/modes/" src/ || echo "AUCUN"`
Expected: occurrences dans `Layout.tsx`, `App.tsx`, `ConsolePane.tsx`, `StagePane`-consumers — traitées aux tâches suivantes. (Ne pas committer encore : le build est cassé jusqu'à A4.)

### Task A2: Layout sans StagePane (placeholder banc en phase A)

**Files:**
- Modify: `src/components/Layout.tsx`

- [ ] **Step 1: Remplacer l'import + le panneau droit**

Dans `Layout.tsx`, retirer `import { StagePane } from './StagePane.js';` et remplacer le `<Panel ...><StagePane /></Panel>` droit par un placeholder neutre (le vrai `WorkbenchPane` arrive en phase C) :

```tsx
// en tête : remplacer l'import StagePane par :
// (phase C remplacera ce placeholder par WorkbenchPane)

// dans le PanelGroup horizontal, panneau droit :
<Panel
  ref={stageRef}
  defaultSize={45}
  minSize={20}
  collapsible
  collapsedSize={0}
>
  <div className="atelier-pane" style={{ display: 'grid', placeItems: 'center', color: '#9a9a9d' }}>
    Banc d'essai (à venir)
  </div>
</Panel>
```

Garder le toggle `⌘B` et `stageRef` inchangés (ils piloteront le banc).

- [ ] **Step 2: Vérifier qu'aucune réf StagePane ne subsiste dans Layout**

Run: `grep -n "StagePane" src/components/Layout.tsx || echo "OK"`
Expected: `OK`

### Task A3: ConsolePane sans onglets de modes

**Files:**
- Modify: `src/components/ConsolePane.tsx`

- [ ] **Step 1: Inspecter les dépendances sim de la console**

Run: `grep -nE "simStore|mode|Mode|SandboxMode|DemoMode|TestMode" src/components/ConsolePane.tsx`
Expected: identifie les imports/usages liés aux modes.

- [ ] **Step 2: Retirer l'état mode + l'onglet de modes, garder la validation**

Retirer de `ConsolePane.tsx` tout import de `simStore`/`modes/*` et le sélecteur d'onglets de modes. Conserver le rendu de la liste de validation (entries de `validationStore`). Le composant ne garde qu'un titre « Validation » + la liste des entrées. Remplacer toute lecture `simStore.getState().mode` par rien (supprimer la branche).

- [ ] **Step 3: Vérifier**

Run: `grep -nE "simStore|/modes/" src/components/ConsolePane.tsx || echo "OK"`
Expected: `OK`

### Task A4: App.tsx — hooks dev sans `sim`, retrait deps, build vert

**Files:**
- Modify: `src/App.tsx`, `vite.config.ts`, `package.json`

- [ ] **Step 1: Retirer `sim` des hooks dev de App.tsx**

Dans `src/App.tsx`, retirer `import { useSimStore } from './stores/simStore.js';` et la clé `sim: useSimStore,` de l'objet `window.__atelierStores`. Laisser `editor`, `runtime`, `validation`.

- [ ] **Step 2: Retirer le manualChunks three/r3f de vite.config.ts**

Run: `grep -nE "three|fiber|drei|r3f" vite.config.ts`
Puis supprimer les entrées `manualChunks` correspondantes (three / r3f) du `build.rollupOptions.output`. Garder le chunk blockly.

- [ ] **Step 3: Retirer les deps 3D de package.json**

```bash
cd frontend-v3/apps/atelier
pnpm remove three @types/three @react-three/fiber @react-three/drei
```
Expected: met à jour `package.json` + lockfile.

- [ ] **Step 4: Typecheck + build verts**

Run: `pnpm exec tsc --noEmit && pnpm exec vite build`
Expected: PASS, aucun import three/r3f restant. Si erreur « cannot find module ./stores/simStore » ou scene/puzzles/modes → corriger le consommateur résiduel signalé.

- [ ] **Step 5: Nettoyer les specs e2e pilotant la sim**

Run: `grep -rnlE "__atelierStores.*sim|simStore|SandboxMode|DemoMode|TestMode" ../../../e2e ../../e2e 2>/dev/null || echo "AUCUN e2e sim"`
Pour chaque spec trouvée : retirer/mettre à jour les assertions sur `sim`/modes (la sim n'existe plus). Si aucune, passer.

- [ ] **Step 6: Commit phase A**

```bash
cd frontend-v3/apps/atelier
git add -A && cd ../../.. && git add -A
git -c user.name=electron-rare -c user.email=c.saillant@gmail.com commit -m "refactor(atelier): remove 3D simulation (StagePane/scene/puzzles/modes/simStore, three deps)"
```

---

## PHASE B — Thème Workbench

### Task B1: Module de thème (source unique)

**Files:**
- Create: `src/theme/workbench.ts`

- [ ] **Step 1: Créer les constantes de thème**

```ts
// src/theme/workbench.ts
// Palette néo-rétro « Workbench » — alignée sur ESP32_ZACUS display_ui.cpp
// (COL_BG/COL_CODE/COL_VALUE/COL_LABEL) et lib/screenSpec.ts.
export const WB = {
  bg: '#0a1722',          // surface profonde (dérivée du bleu Workbench assombri)
  panel: '#0f2233',       // panneaux
  panelBorder: '#1c3b52',
  workbenchBlue: '#0055AA', // COL_BG firmware (accents/écran)
  orange: '#FF8800',       // COL_CODE — accent principal
  value: '#FFFFFF',        // COL_VALUE
  label: '#AAAAAA',        // COL_LABEL
  led: { ok: '#34d399', warn: '#fbbf24', err: '#fca5a5', off: '#3b4a57' },
  fontTitle: "'Orbitron','Arial Narrow',sans-serif",
  fontMono: "'IBM Plex Mono',ui-monospace,monospace",
} as const;
```

- [ ] **Step 2: Typecheck**

Run: `pnpm exec tsc --noEmit`
Expected: PASS.

### Task B2: Polices locales + shell CSS Workbench

**Files:**
- Modify: `src/main.css`

- [ ] **Step 1: Charger Orbitron + IBM Plex Mono**

Les polices sont déjà bundlées par le firmware mais pas le front. Utiliser `@fontsource` (offline, pas de CDN) :

```bash
cd frontend-v3/apps/atelier
pnpm add @fontsource/orbitron @fontsource/ibm-plex-mono
```
Puis en tête de `src/main.css` :
```css
@import '@fontsource/orbitron/400.css';
@import '@fontsource/orbitron/700.css';
@import '@fontsource/ibm-plex-mono/400.css';
```

- [ ] **Step 2: Appliquer le thème au shell**

Dans `src/main.css`, mettre à jour `.atelier-shell`, `.atelier-pane`, `.atelier-resizer*` :
```css
.atelier-shell { background: #0a1722; color: #e8eef3; font-family: 'IBM Plex Mono',ui-monospace,monospace; }
.atelier-pane { background: #0f2233; border: 1px solid #1c3b52; }
.atelier-resizer { background: #1c3b52; }
.atelier-resizer:hover { background: #FF8800; }
/* légère grille de fond façon plan de travail */
.atelier-shell::before {
  content: ''; position: fixed; inset: 0; pointer-events: none; opacity: 0.05;
  background-image: linear-gradient(#FF8800 1px,transparent 1px),linear-gradient(90deg,#FF8800 1px,transparent 1px);
  background-size: 32px 32px;
}
```

- [ ] **Step 3: Build vert**

Run: `pnpm exec tsc --noEmit && pnpm exec vite build`
Expected: PASS (les fonts apparaissent comme assets).

### Task B3: Barre-titre labo (MenuBar)

**Files:**
- Modify: `src/components/MenuBar.tsx`

- [ ] **Step 1: Re-skin de la barre**

Ajouter à gauche un titre `▦ ZACUS · ATELIER` en `WB.fontTitle`, couleur `WB.orange`, letter-spacing 2px ; fond `WB.panel`, bordure basse `WB.panelBorder`. Conserver toutes les actions existantes (ouvrir/charger/enregistrer/partager) — seulement restyler (boutons : fond `WB.panel`, bordure `WB.panelBorder`, texte `#e8eef3`, hover bordure `WB.orange`). Importer `WB` depuis `../theme/workbench.js`.

- [ ] **Step 2: Build vert**

Run: `pnpm exec tsc --noEmit && pnpm exec vite build`
Expected: PASS.

### Task B4: Thème Blockly Workbench

**Files:**
- Modify: `src/components/BlocklyWorkspace.tsx`

- [ ] **Step 1: Définir + appliquer un thème Blockly**

Avant `Blockly.inject`, créer un thème et le passer dans les options :
```ts
import * as Blockly from 'blockly';
const workbenchTheme = Blockly.Theme.defineTheme('zacus-workbench', {
  name: 'zacus-workbench',
  base: Blockly.Themes.Classic,
  componentStyles: {
    workspaceBackgroundColour: '#0a1722',
    toolboxBackgroundColour: '#0f2233',
    toolboxForegroundColour: '#e8eef3',
    flyoutBackgroundColour: '#0f2233',
    flyoutForegroundColour: '#AAAAAA',
    scrollbarColour: '#1c3b52',
    insertionMarkerColour: '#FF8800',
    gridColour: '#1c3b52',
  },
  fontStyle: { family: "'IBM Plex Mono',monospace", size: 11 },
});
// dans les options d'inject : theme: workbenchTheme, grid: { spacing: 24, colour: '#13283a', snap: true }
```

- [ ] **Step 2: Build vert + sanity**

Run: `pnpm exec tsc --noEmit && pnpm exec vite build`
Expected: PASS. (Vérif visuelle ultérieure.)

- [ ] **Step 3: Commit phase B**

```bash
cd ../../.. && git add -A
git -c user.name=electron-rare -c user.email=c.saillant@gmail.com commit -m "feat(atelier): Workbench theme (palette firmware, Orbitron/IBM Plex Mono, Blockly theme)"
```

---

## PHASE C — Banc d'essai (Écran + Flux)

### Task C1: Extraire le rendu LCD fidèle réutilisable

**Files:**
- Create: `src/components/DeviceScreenView.tsx`
- Modify: `src/components/ScreenEditorPanel.tsx` (importer la vue partagée)

- [ ] **Step 1: Créer `DeviceScreenView` à partir du `ScreenPreview` de P3a**

Déplacer le composant `ScreenPreview` (et `symbolAnimation`) de `ScreenEditorPanel.tsx` vers un fichier partagé, paramétré par l'échelle :

```tsx
// src/components/DeviceScreenView.tsx
import { SCREEN, type SceneDraft, type ScreenEffect } from '../lib/screenSpec.js';

function symbolAnimation(effect: ScreenEffect): string | undefined {
  if (effect === 'pulse') return 'zacus-pulse 2.4s ease-in-out infinite';
  if (effect === 'glitch') return 'zacus-glitch 0.45s steps(2) infinite';
  return undefined;
}

export function DeviceScreenView({ scene, scale = 0.66 }: { scene: SceneDraft; scale?: number }) {
  return (
    <div style={{ width: SCREEN.width * scale, height: SCREEN.height * scale, alignSelf: 'center' }}>
      <div style={{
        width: SCREEN.width, height: SCREEN.height, transform: `scale(${scale})`, transformOrigin: 'top left',
        background: SCREEN.bg, position: 'relative', borderRadius: 6, overflow: 'hidden',
        boxShadow: 'inset 0 0 0 2px rgba(0,0,0,0.4)',
      }}>
        <div style={{ position: 'absolute', top: 24, left: 12, right: 12, textAlign: 'center',
          color: SCREEN.title.color, font: `700 40px ${SCREEN.title.font}`, letterSpacing: 2,
          whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{scene.title}</div>
        <div style={{ position: 'absolute', top: '50%', left: 0, right: 0, transform: 'translateY(-50%)',
          display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
          {scene.effect === 'gyro' && scene.symbol && (
            <span style={{ position: 'absolute', width: 120, height: 120, borderRadius: '50%',
              border: `3px solid ${SCREEN.symbol.color}`, borderTopColor: 'transparent',
              animation: 'zacus-gyro-spin 3s linear infinite' }} />
          )}
          <span style={{ color: SCREEN.symbol.color, font: `700 40px ${SCREEN.symbol.font}`, letterSpacing: 3,
            animation: symbolAnimation(scene.effect) }}>{scene.symbol}</span>
        </div>
        <div style={{ position: 'absolute', bottom: 10, left: 8, right: 8,
          color: SCREEN.subtitle.color, font: `18px ${SCREEN.subtitle.font}`,
          whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{scene.subtitle}</div>
      </div>
    </div>
  );
}
```

- [ ] **Step 2: Réutiliser dans `ScreenEditorPanel`**

Dans `ScreenEditorPanel.tsx`, supprimer le `ScreenPreview`/`symbolAnimation` locaux et importer `import { DeviceScreenView } from './DeviceScreenView.js';` ; remplacer `<ScreenPreview scene={scene} />` par `<DeviceScreenView scene={scene} />`.

- [ ] **Step 3: Typecheck + build**

Run: `pnpm exec tsc --noEmit && pnpm exec vite build`
Expected: PASS.

### Task C2: selectionStore

**Files:**
- Create: `src/stores/selectionStore.ts`

- [ ] **Step 1: Créer le store**

```ts
// src/stores/selectionStore.ts
import { create } from 'zustand';

interface SelectionState {
  selectedStepId: string | null;
  selectStep: (id: string | null) => void;
}

export const useSelectionStore = create<SelectionState>((set) => ({
  selectedStepId: null,
  selectStep: (selectedStepId) => set({ selectedStepId }),
}));
```

- [ ] **Step 2: Typecheck**

Run: `pnpm exec tsc --noEmit`
Expected: PASS.

### Task C3: Parser IR → steps + edges

**Files:**
- Create: `src/lib/irSteps.ts`

- [ ] **Step 1: Écrire le parseur**

```ts
// src/lib/irSteps.ts
// Parse le currentIr (string JSON) du runtimeStore en nœuds/arêtes pour le FlowGraph.
import type { SceneDraft } from './screenSpec.js';

export interface FlowNode {
  id: string;
  scene: SceneDraft; // title/subtitle/symbol/effect (defaults vides + effect 'pulse')
}
export interface FlowEdge { from: string; to: string; }
export interface FlowGraphData { nodes: FlowNode[]; edges: FlowEdge[]; entry: string | null; }

interface RawStep {
  id?: string;
  scene?: { title?: string; subtitle?: string; symbol?: string; effect?: SceneDraft['effect'] };
  transitions?: { target_step_id?: string }[];
}

export function parseIr(irJson: string | null): FlowGraphData {
  if (!irJson) return { nodes: [], edges: [], entry: null };
  let raw: { entry_step_id?: string; steps?: RawStep[] };
  try { raw = JSON.parse(irJson); } catch { return { nodes: [], edges: [], entry: null }; }
  const steps = raw.steps ?? [];
  const nodes: FlowNode[] = steps.filter((s) => s.id).map((s) => ({
    id: s.id as string,
    scene: {
      title: s.scene?.title ?? '',
      subtitle: s.scene?.subtitle ?? '',
      symbol: s.scene?.symbol ?? '',
      effect: s.scene?.effect ?? 'pulse',
    },
  }));
  const ids = new Set(nodes.map((n) => n.id));
  const edges: FlowEdge[] = [];
  for (const s of steps) {
    if (!s.id) continue;
    for (const t of s.transitions ?? []) {
      if (t.target_step_id && ids.has(t.target_step_id)) edges.push({ from: s.id, to: t.target_step_id });
    }
  }
  return { nodes, edges, entry: raw.entry_step_id ?? nodes[0]?.id ?? null };
}
```

- [ ] **Step 2: Typecheck**

Run: `pnpm exec tsc --noEmit`
Expected: PASS.

### Task C4: FlowGraph (liste ordonnée cliquable + arêtes)

**Files:**
- Create: `src/components/FlowGraph.tsx`

- [ ] **Step 1: Écrire le composant (V1 : colonne de nœuds + libellés de transition)**

```tsx
// src/components/FlowGraph.tsx
import { useMemo } from 'react';
import { useRuntimeStore } from '../stores/runtimeStore.js';
import { useSelectionStore } from '../stores/selectionStore.js';
import { parseIr } from '../lib/irSteps.js';
import { WB } from '../theme/workbench.js';

export function FlowGraph() {
  const ir = useRuntimeStore((s) => s.currentIr ?? s.pendingIr);
  const { selectedStepId, selectStep } = useSelectionStore();
  const { nodes, edges, entry } = useMemo(() => parseIr(ir), [ir]);

  const outgoing = (id: string) => edges.filter((e) => e.from === id).map((e) => e.to);

  if (nodes.length === 0) {
    return <div style={{ color: WB.label, padding: 8, fontFamily: WB.fontMono }}>Aucune étape — édite le scénario puis lance (Run).</div>;
  }
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6, padding: 8, overflowY: 'auto', fontFamily: WB.fontMono, fontSize: 12 }}>
      {nodes.map((n) => {
        const sel = n.id === selectedStepId;
        return (
          <div key={n.id}>
            <button type="button" onClick={() => selectStep(n.id)}
              style={{ width: '100%', textAlign: 'left', padding: '6px 8px', cursor: 'pointer',
                       background: sel ? WB.orange : WB.panel, color: sel ? '#1a1a1d' : '#e8eef3',
                       border: `1px solid ${sel ? WB.orange : WB.panelBorder}`, borderRadius: 5 }}>
              {n.id === entry ? '▶ ' : ''}{n.id}
              {n.scene.symbol ? <span style={{ color: sel ? '#1a1a1d' : WB.orange }}>  ⟦{n.scene.symbol}⟧</span> : null}
            </button>
            {outgoing(n.id).map((to) => (
              <div key={to} style={{ color: WB.label, paddingLeft: 16 }}>↳ {to}</div>
            ))}
          </div>
        );
      })}
    </div>
  );
}
```

- [ ] **Step 2: Typecheck + build**

Run: `pnpm exec tsc --noEmit && pnpm exec vite build`
Expected: PASS.

### Task C5: DeviceScreen (écran de l'étape sélectionnée)

**Files:**
- Create: `src/components/DeviceScreen.tsx`

- [ ] **Step 1: Écrire le composant**

```tsx
// src/components/DeviceScreen.tsx
import { useMemo } from 'react';
import { useRuntimeStore } from '../stores/runtimeStore.js';
import { useSelectionStore } from '../stores/selectionStore.js';
import { parseIr } from '../lib/irSteps.js';
import { EMPTY_SCENE } from '../lib/screenSpec.js';
import { DeviceScreenView } from './DeviceScreenView.js';
import { WB } from '../theme/workbench.js';

export function DeviceScreen() {
  const ir = useRuntimeStore((s) => s.currentIr ?? s.pendingIr);
  const selectedStepId = useSelectionStore((s) => s.selectedStepId);
  const { nodes, entry } = useMemo(() => parseIr(ir), [ir]);
  const node = nodes.find((n) => n.id === selectedStepId) ?? nodes.find((n) => n.id === entry) ?? null;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6, padding: 8, alignItems: 'center' }}>
      <span style={{ color: WB.label, fontFamily: WB.fontMono, fontSize: 11, alignSelf: 'flex-start' }}>
        ÉCRAN · {node ? node.id : '—'}
      </span>
      <DeviceScreenView scene={node ? node.scene : EMPTY_SCENE} scale={0.7} />
    </div>
  );
}
```

- [ ] **Step 2: Typecheck + build**

Run: `pnpm exec tsc --noEmit && pnpm exec vite build`
Expected: PASS.

### Task C6: WorkbenchPane (split vertical) + branchement Layout

**Files:**
- Create: `src/components/WorkbenchPane.tsx`
- Modify: `src/components/Layout.tsx`

- [ ] **Step 1: Écrire le split vertical**

```tsx
// src/components/WorkbenchPane.tsx
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
import { DeviceScreen } from './DeviceScreen.js';
import { FlowGraph } from './FlowGraph.js';
import { WB } from '../theme/workbench.js';

export function WorkbenchPane() {
  return (
    <div className="atelier-pane" style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
      <div style={{ padding: '4px 8px', color: WB.orange, fontFamily: WB.fontTitle, letterSpacing: 1,
                    borderBottom: `1px solid ${WB.panelBorder}`, fontSize: 12 }}>BANC D'ESSAI</div>
      <PanelGroup direction="vertical" style={{ flex: 1 }}>
        <Panel defaultSize={45} minSize={20}><DeviceScreen /></Panel>
        <PanelResizeHandle className="atelier-resizer atelier-resizer--horizontal" />
        <Panel defaultSize={55} minSize={20}><FlowGraph /></Panel>
      </PanelGroup>
    </div>
  );
}
```

- [ ] **Step 2: Brancher dans Layout (remplacer le placeholder de A2)**

Dans `src/components/Layout.tsx` : `import { WorkbenchPane } from './WorkbenchPane.js';` et remplacer le `<div ...>Banc d'essai (à venir)</div>` par `<WorkbenchPane />`.

- [ ] **Step 3: Typecheck + build**

Run: `pnpm exec tsc --noEmit && pnpm exec vite build`
Expected: PASS.

- [ ] **Step 4: Commit phase C**

```bash
cd ../../.. && git add -A
git -c user.name=electron-rare -c user.email=c.saillant@gmail.com commit -m "feat(atelier): Workbench pane — faithful device screen + scenario flow graph"
```

---

## Self-review (couverture spec)

- Retrait 3D (spec §4 supprimés) → Phase A ✓
- Thème Workbench palette/fonts/chrome/Blockly (spec §3) → Phase B ✓
- DeviceScreen réutilisant P3a (spec §4 nouveaux) → C1/C5 ✓
- FlowGraph depuis IR (spec §5) → C3/C4 ✓
- selectionStore + wiring (spec §5) → C2, FlowGraph/DeviceScreen lisent `selectionStore` ✓
- Split vertical (spec §2, décision) → C6 ✓
- Tests = tsc+build verts par tâche (spec §7) ✓
- Hors périmètre (Blockly conservé, pas de playtest, pas dashboard) respecté ✓

## Notes d'exécution

- Le `FlowGraph` V1 est une **liste ordonnée de nœuds + arêtes sortantes** (le plus sûr) ; un layout graphe spatial (positions/SVG) est une amélioration ultérieure hors de ce plan.
- Vérif visuelle finale recommandée via `pnpm --filter @zacus/atelier dev` (hors CI) après C6.
- Déploiement prod (`ops/deploy-frontend.sh atelier`) **hors de ce plan** — à faire après revue visuelle.
