Skip to content

app: workspaces carry an old panes.zon over into the layout - #464

Merged
foxnne merged 10 commits into
mainfrom
app/workspaces-carry-panes
Oct 16, 2026
Merged

foxnne merged 10 commits into
mainfrom
app/workspaces-carry-panes

Conversation

@foxnne

@foxnne foxnne commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

Part of fizzyedit/work#21 (step 4 of plans/WORKSPACES_PLAN.md). Stacked on #462, which is stacked on #461.

What changes

Behind FIZZY_WORKSPACES=1, a saved layout comes back whole.

  • An old panes.zon becomes the app's splits. The first time fizzy runs with the flag, the workbench's saved tree is laid out split for split from the place the first workspace is in (Main). Each pane's workspace goes in its half, at the share it had. Then panes.zon is deleted: the arrangement is layout.zon's from then on. A subtree whose panes didn't come back is skipped, so its sibling takes the room.
  • A place whose workspace didn't come back closes. A workspace that held no document at quit, or none that could open, isn't restored. Its place used to come back empty; now it closes, as it would have had it emptied.
  • Region.splitOnSized: splitOn for a place that hasn't been drawn yet, with a share for the new half. A nested tree splits a half made in the same frame. splitOn now calls it with the drawn size and half.

What's already kept and needed no change: which place holds which workspace and each workspace's documents in order (layout.zon, since #461), and each workspace's selected tab (the session, KeptDocuments.applySelections).

Older fizzy reads the same layout.zon it always did; nothing new is written. With the flag off, nothing changes. (The workbench still reads panes.zon as it did; only the carry-over deletes it.)

SDK impact

  • None (test-sdk-version passes).

Verified

  • macOS: in a sandbox profile:
    • ran without the flag and split Main (Pane 0 | Pane 4);
    • hand-edited the saved panes.zon into a nested tree (horizontal 0.6: Pane 0 | vertical 0.5 of Pane 4, Pane 5) and gave Pane 5 a document in layout.zon;
    • relaunched with FIZZY_WORKSPACES=1: Main came back as Main | (Main/r1 over Main/r1/b1), each half showing its workspace's document, the horizontal share 60/40, and panes.zon gone.
    • zig build, zig build test, zig build test-integration (53/53 steps), zig build check-web, zig build test-sdk-version, scripts/check-fmt.sh.
  • Windows:
  • Linux:
  • Web: builds; no panes.zon there, and the flag is never on.

Follow-ups

  • A split of a split's new half comes out uneven. The vertical 0.5 above settled at about 1:3, and splitting a nested half by hand came out uneven the same way. A branch sizes itself in its parent by its origin's extent (Region, .branch), the same name its kept half reads its own size from along the other axis. That's the app's split code with or without this flag, so it's a separate fix.
  • Step 5: tear a tab off into a float holding a new workspace (State.documents with a float), and back.

🤖 Generated with Claude Code

foxnne and others added 5 commits October 16, 2026 13:16
With FIZZY_WORKSPACES=1, each workbench pane is a view the app places:
fizzy.workspace.<grouping>, titled by the document it shows. Main shows the
first by its keywords; a pane opened beside another (a tab dropped on a
pane's edge, Open to the Side) is an app split of the place holding it, and
an emptied pane closes its place as any emptied view's does, the half beside
it taking its room. The workbench's own split tree and panes.zon are not used
while the flag is on; layout.zon keeps the places and what each holds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With FIZZY_WORKSPACES=1, a document carried in the view drag is taken by
the edges of any place, not only by the document panes: the place splits on
that side and the app gives the new half a workspace holding the document
(State.documents, which fizzy's Workspaces fills in). A plain place's middle
is no target for a document. The app, not the workbench, now seats a
document no pane holds, into the workspace it was opened toward, grouping 0
meaning the active one once the first has gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With FIZZY_WORKSPACES=1, the first frame the layout has drawn squares last
session's layout with the panes that came back. An arrangement the workbench
saved before the app placed its panes (panes.zon) becomes the app's splits of
Main, split for split with each pane's share, and the file is deleted. A
place whose workspace did not come back (it held no document at quit) closes
instead of standing empty. Region.splitOnSized splits a place that has not
been drawn yet, which a nested tree needs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@foxnne
foxnne marked this pull request as ready for review October 16, 2026 19:20
@foxnne

foxnne commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator Author

The 1:3 in the follow-up comes from this PR's carry-over, not from the .branch arm.

I reproduced it on this head (f927a7f) with the recipe above: a panes.zon of Pane 0 | (Pane 4 over Pane 5, vertical 0.5), relaunched with FIZZY_WORKSPACES=1. Main/r1 came out at 248 px over Main/r1/b1 at 708 px (2×). The extent layout.zon stored for Main/r1/b1 is 354 pt:

  • (718 − handle_size 10) × 0.5 = 354, and
  • 718 pt is Main's content height without the Panel: (976 + 20 + 472) px / 2 − 2 × 8 pt card padding.

With the Panel there, Main is 472 pt tall inside, and its half would be 231 pt. So settle reads placeSize(Main) on a frame before the Panel has taken its 236 pt, and carryOver passes that height to splitOnSized. The width is right (Main/r1's 341.6 = (864 − 10) × 0.4) because nothing beside Main changes width on that frame.

Why it isn't the shared name. The kept origin is always the leftover half of its own branch (resize = false), so it never writes an extent under that name. Within a tree, extent("Main/r1") only ever means Main/r1's reach along its parent's axis, before and after it is split again. Splitting a nested half by hand on main comes out even apart from the card padding, which a restart keeps.

The uneven split by hand was a separate, smaller bug: every split of a carded place came out short by the card padding on both sides (494 / 462 px). #465 fixes it: a place's recorded size is now its border box, and splitOnSized here picks that up as it is.

Suggested fix here: carry over once the layout under Main has settled, for example on a later frame, or once the Panel has registered its size. Another option is to store each new half's share of the place and turn it into an extent when the half is first drawn. Measuring on the first frame settle runs will keep picking up whatever hasn't been laid out yet.

🤖 Generated with Claude Code

foxnne and others added 4 commits October 16, 2026 15:05
With FIZZY_WORKSPACES=1, a document carried onto a plain place lands there
inside a new workspace, the way a view would: its middle adds the workspace
to a place of tabs and takes a place of one (the view it replaces going
where its keywords show it), its chooser takes it where it was let go, and
an edge splits as before. State.documents now hands back the new
workspace's view for the layout to place. A pane's only tab dropped on its
own edge moves there with room opening on the other side, as a view alone
in its place does (Workbench.Places.open). A minted place closed in the
frame it was made, never drawn, is dropped outright instead of standing
empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A document carried alone out of a workspace it is the only tab of takes the
workspace along: dropped on a plain place, the workspace itself lands there
(State.Documents.alone), as a drag of it would, rather than a new workspace
being made round the document and the old one closing behind it. That round
trip drew the document anew twice, and it blinked. A view joined with an
empty half stays the one view, not a place of tabs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from layout/documents-beside-views to main October 16, 2026 20:26
foxnne added a commit that referenced this pull request Oct 16, 2026
Found while chasing the uneven nested split reported in
#464's follow-ups.

## What changes

A split of a carded place now opens two even halves. Before, the new
half was short by the card's padding on both sides. That covers fizzy's
Main and Panel, and every split of their halves. At 2× on macOS:

| | before | after |
|---|---|---|
| Main → Split → Vertical | Main 886 px, Main/r1 854 px | 870, 870 |
| then Main/r1 → Split → Horizontal | Main/r1 494 px, Main/r1/b1 462 px
| 478, 478 |

**Why.** A split's extent is the new half's whole reach, border to
border, padding included (`Region.init`). `splitOn` halved the place's
recorded `size`, which was its *content* rect. `State.setPlaceMetrics`
now records the border box, and it takes the box's `WidgetData`, so all
three call sites measure it the same way. A split is the only thing that
reads `size` (`placeSize`, and a drag's frozen `Target.size`).

**The suspected cause was not it.** #464 suspected the `.branch` arm: it
sizes the branch by `extent(b.origin)`, the same name the kept leaf has.
But the kept origin is always the leftover half of its own branch
(`resize = false`), so it never writes an extent under that name. Inside
one tree, `extent(name)` always means the minted leaf's reach along its
parent's axis, before and after that leaf is split again. Persistence
round-trips too: the nested split came back at the same sizes after a
restart.

**The 1:3 in #464 comes from its own carry-over.** I reproduced it on
#464's head (f927a7f) with its recipe: a `panes.zon` of `Pane 0` |
(`Pane 4` over `Pane 5`), relaunched with `FIZZY_WORKSPACES=1`. Main/r1
came out 248 px over Main/r1/b1 708 px. The stored extent for Main/r1/b1
was 354 pt, which is exactly (718 − 10) × 0.5. 718 pt is Main's content
height *without* the 236 pt Panel under it. So `settle` measures Main on
a frame before the Panel has its room, and hands that height to
`splitOnSized`. That fix belongs in #464.

## SDK impact

- [x] None (`test-sdk-version` passes; `app/` only)

## Verified

- [x] macOS:
- new integration test `a split of a carded place, and of its new half,
opens even halves`. It uses fizzy's shape: a carded Main with
`pane_cards`, in a column, and compares the halves' drawn rects, not the
`size` this PR redefines. Before the fix it failed at 187 vs 171.
- sandbox fizzy (`--profile`, driven through the agent plugin): Main →
Split → Vertical, then Main/r1 → Split → Horizontal, measured as in the
table.
- `scripts/check-fmt.sh`, `zig build`, `zig build test` (91/91 steps),
`zig build test-integration` (53/53 steps), `zig build
check-tests-reached`, `zig build check-web`, `zig build
test-sdk-version`.
- [ ] Windows:
- [ ] Linux:
- [ ] Web: builds

## Follow-ups

- #464: measure Main once the layout under it has settled (or from the
window), not on the first frame `settle` runs. Its `splitOnSized` gets
the reach fix from this PR for free.
- Removing a half that was split again: fixed in #467,
stacked on this one.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@foxnne
foxnne merged commit f6a8f2d into main Oct 16, 2026
11 checks passed
@foxnne
foxnne deleted the app/workspaces-carry-panes branch October 16, 2026 21:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant