build the inert context once instead of twice

`WorkspaceLayout` and the `createContext` default each spelled out the same eleven fields — every
interaction a panel can start, switched off. Two hand-written copies of one list is a list you fall
behind: adding a field to the context type only errors at the call site if it is required, and both
copies have to be found.

Named it. `inertInteraction` is what "this tree cannot be rearranged" means, and both places spread
it. `cwd` and `root` stay out of it deliberately — they say where the workspace is rather than what
can be done to it, and the inert renderer has no answer for `root`: its consumers only read it when
`cwd` is scoped, which no caller makes it.
This commit is contained in:
2026-08-07 09:42:25 +00:00
parent d3922bd9d3
commit ca046a3876
2 changed files with 21 additions and 6 deletions
@@ -29,10 +29,18 @@ type WorkspaceContextValue = {
const noop = () => {};
const WorkspaceContext = createContext<WorkspaceContextValue>({
workspace: null,
cwd: '~',
panelConfigs: {},
/**
* The interaction half of the context, switched off — every field a panel uses to rearrange the
* workspace, plus the state those interactions run on. `WorkspaceLayout` renders a tree nobody can
* rearrange (job detail panes, settings, dashboard previews), and it used to say so by repeating
* eleven fields at the call site, which is a list you can silently fall behind: add a field to the
* context and only one of the two providers gets it.
*
* `root` and `cwd` are deliberately not in here. They are facts about where the workspace *is*, not
* interactions, and the inert renderer has no answer for them — its consumers only read `root` when
* `cwd` is scoped, which it never is there.
*/
export const inertInteraction = {
setPanelConfig: noop,
swapSourceId: null,
setSwapSourceId: noop,
@@ -44,6 +52,13 @@ const WorkspaceContext = createContext<WorkspaceContextValue>({
maximizedPanelId: null,
setMaximizedPanelId: noop,
transitioningPanelId: null,
} satisfies Partial<WorkspaceContextValue>;
const WorkspaceContext = createContext<WorkspaceContextValue>({
workspace: null,
cwd: '~',
panelConfigs: {},
...inertInteraction,
isMobile: false,
onMobileBack: null,
});
@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import type { LayoutNode, AppRegistryMap, PanelComponents } from './types';
import { updateSizes, collectPanelConfigs } from './layout-utils';
import { WorkspaceProvider } from './WorkspaceContext';
import { WorkspaceProvider, inertInteraction } from './WorkspaceContext';
import { WorkspaceRenderer } from './WorkspaceRenderer';
import { useAppRegistry } from '../../AppRegistry/useAppRegistry';
@@ -36,7 +36,7 @@ export const WorkspaceLayout = ({ layout, onLayoutChange, registry: registryProp
// dashboard previews. It is not a dashboard and not a screen, and a panel that asks should be told so
// rather than handed something that parses.
return (
<WorkspaceProvider value={{ workspace: null, cwd: cwd ?? '~', panelConfigs: collectPanelConfigs(layout), setPanelConfig: noop, swapSourceId: null, setSwapSourceId: noop, onSwap: noop, dragSourceId: null, setDragSourceId: noop, onMove: noop, onSetZoom: noop, maximizedPanelId: null, setMaximizedPanelId: noop, transitioningPanelId: null, isMobile: isMobile ?? false, onMobileBack: onMobileBack ?? null }}>
<WorkspaceProvider value={{ workspace: null, cwd: cwd ?? '~', panelConfigs: collectPanelConfigs(layout), ...inertInteraction, isMobile: isMobile ?? false, onMobileBack: onMobileBack ?? null }}>
<WorkspaceRenderer
layout={layout}
registry={registry}