Skip to content

fix(sail-finance): drop empty grid branches when restoring a Dockview layout - #392

Open
patrickboneill wants to merge 2 commits into
finos:feat/v3-betafrom
patrickboneill:fix/388-normalize-dockview-layout
Open

patrickboneill wants to merge 2 commits into
finos:feat/v3-betafrom
patrickboneill:fix/388-normalize-dockview-layout

Conversation

@patrickboneill

@patrickboneill patrickboneill commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #388.

Problem

Dockview collapses a branch left with one child (Gridview.normalize), but not one left with none. An empty branch keeps its share of the grid, so the workspace shows a blank column (or row), and because the emptied layout is what gets saved, the gap survives reloads.

A branch can only reach zero children if it held exactly one leaf, which Dockview's own drag/split UI never builds — so this only affects layouts written outside Dockview: pre-seeded, imported or hand-written dockviewLayout state, exactly what a default-workspace hook like #313 would produce.

Fix

New packages/sail-finance/src/components/layout-grid/normalize-layout.ts exporting a pure normalizeDockviewLayout(layout: unknown): unknown, called in the restore effect of Layout.tsx:

const savedLayoutState = normalizeDockviewLayout(getDockviewLayout(activeWorkspaceId))

It walks layout.grid.root and:

  • keeps leaves;
  • drops a branch left without leaves;
  • replaces a branch holding a single leaf with that leaf, carrying the branch's size — the leaf's own size measures the branch's cross axis and means nothing in the slot the branch vacated;
  • lifts the grandchildren of a child that is itself a one-branch branch (orientations alternate, so they already lie along the parent's axis);
  • flips grid.orientation when the root is left holding a single branch, re-sizing the root to grid.height for HORIZONTAL and grid.width for VERTICAL;
  • returns null when no leaves are left, passes non-layout values through, and returns the same reference when nothing changed, so restore behaves exactly as today for grids Dockview built itself.

Two points where the implementation is deliberately narrower than a naive recursion:

  • The root stays a branch. Collapsing branch[leaf] into a bare leaf would produce something fromJSON does not accept — Dockview's root is always a branch, and branch[leaf] is how it serializes a one-panel workspace. Root children are normalized without collapsing the root itself.
  • A hidden leaf keeps its own size. For visible: false, the leaf's size is Dockview's cachedVisibleSize; overwriting it with the branch's size would corrupt the restored width of a hidden panel.

Normalizing on restore prevents the gap rather than only repairing it

Because the redundant single-leaf branch is collapsed on first load — nothing moves, A keeps its 400 — closing that panel later leaves the root holding a single branch, which Dockview collapses itself. So B/C expand immediately in the live session, not just after a reload. There is a test pinning this.

The save path is left alone on purpose, and the live grid is never re-fed through fromJSON: that remounts groups, which would reload the panel iframes and tear down their FDC3 connections.

Tests

src/__tests__/components/normalize-layout.test.ts — 11 unit tests: a clean layout returns the identical object; [leaf, leaf, branch[]] drops the empty branch with the orientation unchanged; [branch[leaf(400)], branch[leaf, leaf]] yields [leaf(size 400), branch[leaf, leaf]]; [branch[], branch[leaf, leaf]] flips the root to VERTICAL with size === width; a hidden leaf keeps its own size; nested single-branch chains lift their grandchildren without changing the axis; a layout with no leaves returns null; null, undefined, {} and grid-less objects pass through; panels/activeGroup survive a rewrite.

src/__tests__/components/dockview-layout-restore.test.ts — the issue's reproduction against a real DockviewComponent, run headlessly in jsdom (which needs only a ResizeObserver stub, since these tests size the grid explicitly via layout()):

step B/C width
seeded layout restored A 400, B/C 600
after closing A saved grid becomes [branch[] size 400, …], B/C stay 600
raw reload 600 — the gap survives, as reported
normalized reload 1000 — full width

Plus: a layout Dockview built itself (via addPanel) comes back by reference; collapsing the redundant seeded branch leaves every width unchanged; and after a normalized restore, closing A leaves no empty branch at all.

Verification

  • vp test run for sail-finance: 44/44 passing (17 of them new).
  • tsc, vp lint . and vp fmt --check .: clean.
  • No dependency changes: the diff is four files under packages/sail-finance/src, and no manifest or lockfile is touched. The test imports DockviewComponent from dockview, which this package declares and which re-exports dockview-core.

The issue's note about seeded workspaces emitting a one-panel column as a plain leaf needs no change yet: nothing in the branch builds layouts programmatically (that arrives with #313).

🤖 Generated with Claude Code

patrickboneill and others added 2 commits October 15, 2026 10:13
Dockview collapses a branch left with one child, but not one left with none.
An empty branch keeps its share of the grid, so a workspace shows a blank
column (or row), and because the emptied layout is what gets saved, the gap
survives reloads.

A branch can only reach zero children if it held exactly one leaf, which
Dockview's drag/split UI never builds — so this only affects layouts written
outside Dockview: pre-seeded, imported or hand-written `dockviewLayout` state.

Add `normalizeDockviewLayout`, a pure pass over the serialized grid that drops
branches without leaves, replaces a redundant single-leaf branch with its leaf
(carrying the branch's size), lifts the grandchildren of a single-branch
branch, and flips `grid.orientation` when the root is left holding one branch.
Layouts needing no change come back by reference, so restore is unchanged for
grids Dockview built itself. Call it in the restore effect of Layout.tsx.

Normalizing on restore also keeps the gap from appearing in the live session:
a seeded single-leaf branch is collapsed before the user closes anything, so
closing that panel leaves the root with a single branch, which Dockview
collapses itself.

Fixes finos#388

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: patrickboneill <patrick.oneill46@gmail.com>
…ency

The restore test reached straight for `dockview-core`, which the package does
not declare — it only arrives transitively through `dockview-react`. `dockview`
is a declared dependency and re-exports all of dockview-core, so take it from
there instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: patrickboneill <patrick.oneill46@gmail.com>
@netlify

netlify Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

❌ Deploy Preview for fdc3-sail failed. Why did it fail? →

Name Link
🔨 Latest commit dc05129
🔍 Latest deploy log https://app.netlify.com/projects/fdc3-sail/deploys/6ad09d205f0e2271b8273a77

@SeeWhatsOn
SeeWhatsOn self-requested a review October 21, 2026 13:11
@SeeWhatsOn SeeWhatsOn added UI/UX sail-finance The Sail Finance UI (packages/sail-finance) labels Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sail-finance The Sail Finance UI (packages/sail-finance) UI/UX

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants