workspace layout: two empty panels left, the game right

The arrangement the rest of the app uses for controls beside a surface, and
the same shape as Atelier's. 25/75 rather than Atelier's 30/70, because the
board is drawn at a whole-number scale and a quarter of the width is enough to
drop it a step.

The left panels are `appType: null`, which is the framework's empty-panel
state rather than a missing app. That distinction is load-bearing here:
PluginScreen passes `appTypes.allowed` built from the plugin's own panel keys,
and this plugin has exactly one. Anything not on that list is rewritten to the
fallback — so if `null` were not explicitly allowed by `normalizeLayout`, this
layout would render three Minesweepers. It is allowed, and there is a test for
it.

The screen is `locked`, so an empty panel renders as a plain bordered box with
no app picker. That is the intended look until something goes in them.

The game panel keeps the id `minesweeper` it had as a single panel, so any
per-panel state keyed on it survives.

Checked before writing it: no persisted layout exists for this screen in
`user_state`, so the new default actually applies rather than being shadowed by
a saved one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: de3ed4e6-f88a-429c-94e6-a760336ba83b
This commit is contained in:
2026-08-23 23:07:56 +00:00
co-authored by Claude Opus 5
parent 977448868c
commit b94c1a6557
+31 -5
View File
@@ -1,9 +1,35 @@
import type { LayoutNode } from 'officerdev'; import type { LayoutNode } from 'officerdev';
// One panel. The game is one fixed-size stage and there is nothing to put beside it — a split here // Two panels stacked on the left, the game on the right — the same arrangement Atelier uses, and the one
// would be a second panel of empty space. // the rest of the app reads as "controls beside a surface".
//
// The left column is EMPTY on purpose. `appType: null` is a state of the framework rather than a missing
// app, and `normalizeLayout` allows it explicitly, so these survive the plugin's own allow-list instead of
// being rewritten to the fallback — which on a one-panel plugin would have rendered three Minesweepers.
// The screen is `locked`, so an empty panel here is a plain bordered box with no app picker. That is the
// intended look until something goes in them.
//
// The game panel keeps the id `minesweeper` it had when this was a single panel, so any per-panel state
// already keyed on it survives the change.
export const defaultLayout: LayoutNode = { export const defaultLayout: LayoutNode = {
type: 'panel', type: 'group',
id: 'minesweeper', id: 'minesweeper-root',
appType: 'minesweeper', direction: 'horizontal',
children: [
{
node: {
type: 'group',
id: 'minesweeper-side',
direction: 'vertical',
children: [
{ node: { type: 'panel', id: 'minesweeper-side-top', appType: null }, size: 50 },
{ node: { type: 'panel', id: 'minesweeper-side-bottom', appType: null }, size: 50 },
],
},
// 25 rather than Atelier's 30: the board is drawn at a whole-number scale, so the panel losing a
// quarter of its width can drop it a step. 75% of a typical panel still clears 2x.
size: 25,
},
{ node: { type: 'panel', id: 'minesweeper', appType: 'minesweeper' }, size: 75 },
],
}; };