({
scene,
leftRail,
commandTrailing,
rightRail,
bottomRail,
}: EditShellProps)
| 72 | * the previous two-branch design caused (PR3a rearch). |
| 73 | */ |
| 74 | export function EditShell({ |
| 75 | scene, |
| 76 | leftRail, |
| 77 | commandTrailing, |
| 78 | rightRail, |
| 79 | bottomRail, |
| 80 | }: EditShellProps) { |
| 81 | const surface = sceneEditorRegistry.resolve(scene.type) ?? NOOP_SURFACE; |
| 82 | // Surface state is published from a child runner (keyed by sceneType so it |
| 83 | // remounts when the surface identity changes — that's the boundary at which |
| 84 | // rules-of-hooks naturally allows a different hook signature). The chrome |
| 85 | // around it stays mounted and consumes state via these props. |
| 86 | const [state, setState] = useState<SurfaceState | null>(null); |
| 87 | const SurfaceComponent = surface.SurfaceComponent; |
| 88 | |
| 89 | return ( |
| 90 | <> |
| 91 | {/* `key={scene.type}` is the remount boundary. We can't use |
| 92 | `surface.sceneType` here because NOOP_SURFACE deliberately reuses |
| 93 | 'slide' as a placeholder (the SceneType union is closed and NOOP |
| 94 | isn't a real type). The scene's own `type` is the actual signal |
| 95 | that the hook signature inside `useSurfaceState` is about to |
| 96 | change — so we remount the runner exactly when it does, keeping |
| 97 | rules-of-hooks happy across the slide ↔ read-only surface swap |
| 98 | while the rest of the chrome stays mounted. */} |
| 99 | <SurfaceStateRunner key={scene.type} surface={surface} onChange={setState} /> |
| 100 | <Frame |
| 101 | title={scene.title} |
| 102 | leftRail={leftRail} |
| 103 | history={state?.history} |
| 104 | commands={state?.commands} |
| 105 | trailing={commandTrailing} |
| 106 | rightRail={rightRail} |
| 107 | bottomRail={bottomRail} |
| 108 | > |
| 109 | <SurfaceComponent /> |
| 110 | {state?.insertItems && state.insertItems.length > 0 && ( |
| 111 | <FloatingInsertToolbar items={state.insertItems} /> |
| 112 | )} |
| 113 | {state?.hasSelection && <FloatingToolbar actions={state.floatingActions} />} |
| 114 | <HintRail hints={state?.hints} /> |
| 115 | </Frame> |
| 116 | </> |
| 117 | ); |
| 118 | } |
| 119 | |
| 120 | /** |
| 121 | * Hidden runner that owns the surface state hook. `key={surface.sceneType}` |
nothing calls this directly
no outgoing calls
no test coverage detected