Floating dock panels are silently lost on reload #102

Closed
opened 2026-09-02 22:28:50 +02:00 by lz · 0 comments
Owner

Pre-existing bug, split out of issue #96 as design D8 (the spec explicitly scoped it to its own issue and PR rather than letting it ride a feature branch).

Symptom

Shift-drag a panel out of the dock to float it, reload the page — the panel is gone. No error, no watermark clue, nothing in the console. The gesture works; the result does not survive.

Cause

stripArtifactPanels (nexus/src/lib/dock/layout.ts) rebuilds a SerializedDockview with only grid / panels / activeGroup on the way to localStorage, deliberately dropping floatingGroups:

// floatingGroups / popoutGroups / edgeGroups can reference stripped panels
// and are not worth repairing — dropped, not just left undefined.

disableFloatingGroups is never set in Dock.svelte, so shift-drag floats a panel today. The combination is the worst one available: floating is enabled and unpersisted.

Why nobody noticed

layout.test.ts pinned the dropping behaviour and passed. A green test was actively concealing the bug — it took an audit reading the spec against the code to surface it.

Fix

Repair floats instead of dropping them, the same treatment grid already gets: a float loses only the views actually stripped, one that loses all of them is removed, one that keeps some is kept with the survivors.

Three details a naive .data.views filter misses:

  • dockview 7 serializes a float in two mutually exclusive formsdata for a single group, grid for a nested layout of several (see SerializedFloatingGroup). Handling only data silently discards every multi-group float.
  • activeGroup can name a floating group, so surviving ids must be collected from kept floats too.
  • A float in neither form must be dropped rather than trusted, since it may reference a stripped panel.

popoutGroups / edgeGroups keep being dropped — popouts need a static/popout.html that does not exist, and neither is reachable from the UI, so nothing is silently lost there.

Known limitation, not fixed

The float's position. dockview passes the stored position to addFloatingGroup, but constrainBounds() runs immediately after deserialization and calls overlay.setBounds(), re-clamping against a container that may not be measured yet. In a harness the restored float landed at the dock origin and the next save then persisted 0,0. Whether that reproduces for a float created by a real shift-drag needs a manual check; it is dockview-side and outside what D8 prescribes.

Fixed by PR #103.

Pre-existing bug, split out of issue #96 as design **D8** (the spec explicitly scoped it to its own issue and PR rather than letting it ride a feature branch). ## Symptom Shift-drag a panel out of the dock to float it, reload the page — the panel is gone. No error, no watermark clue, nothing in the console. The gesture works; the result does not survive. ## Cause `stripArtifactPanels` (`nexus/src/lib/dock/layout.ts`) rebuilds a `SerializedDockview` with only `grid` / `panels` / `activeGroup` on the way to localStorage, deliberately dropping `floatingGroups`: ``` // floatingGroups / popoutGroups / edgeGroups can reference stripped panels // and are not worth repairing — dropped, not just left undefined. ``` `disableFloatingGroups` is never set in `Dock.svelte`, so shift-drag floats a panel today. The combination is the worst one available: floating is enabled and unpersisted. ## Why nobody noticed `layout.test.ts` pinned the *dropping* behaviour and passed. A green test was actively concealing the bug — it took an audit reading the spec against the code to surface it. ## Fix Repair floats instead of dropping them, the same treatment `grid` already gets: a float loses only the views actually stripped, one that loses all of them is removed, one that keeps some is kept with the survivors. Three details a naive `.data.views` filter misses: - **dockview 7 serializes a float in two mutually exclusive forms** — `data` for a single group, `grid` for a nested layout of several (see `SerializedFloatingGroup`). Handling only `data` silently discards every multi-group float. - **`activeGroup` can name a floating group**, so surviving ids must be collected from kept floats too. - **A float in neither form** must be dropped rather than trusted, since it may reference a stripped panel. `popoutGroups` / `edgeGroups` keep being dropped — popouts need a `static/popout.html` that does not exist, and neither is reachable from the UI, so nothing is silently lost there. ## Known limitation, not fixed The float's **position**. dockview passes the stored position to `addFloatingGroup`, but `constrainBounds()` runs immediately after deserialization and calls `overlay.setBounds()`, re-clamping against a container that may not be measured yet. In a harness the restored float landed at the dock origin and the next save then persisted `0,0`. Whether that reproduces for a float created by a real shift-drag needs a manual check; it is dockview-side and outside what D8 prescribes. Fixed by PR #103.
lz closed this issue 2026-09-04 17:25:42 +02:00
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lz/agent-nexus#102
No description provided.