Skip to content

fix: decouple the panel divider's unmount-commit from the onCommit prop identity - #16

Merged
resure merged 2 commits into
mainfrom
panel-resizer-hardening
Aug 10, 2026
Merged

fix: decouple the panel divider's unmount-commit from the onCommit prop identity#16
resure merged 2 commits into
mainfrom
panel-resizer-hardening

Conversation

@resure

@resure resure commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Follow-up to #15, from the re-review's one remaining finding plus two comment corrections and a docs catch-up.

The fix

PanelResizer's mid-drag-unmount cleanup (added in #15 to stop a rail-close under a held divider from orphaning the inline width var) tears down the live gesture — it nulls drag.current. Having onCommit in the effect's dep list therefore made the component's internal correctness depend on the caller passing a referentially stable callback.

Workspace happens to pass raw useState setters, so nothing is broken today. But an inline lambda anywhere — the obvious thing to write — breaks dragging the moment the parent re-renders for any unrelated reason. Measured on the pre-fix code:

commits after re-render: [320]     ← premature commit
commits after release:   [320]     ← every later pointermove ignored
body class still set:    true      ← after pointerup

The third is the bad one: endDrag returns early on a null gesture, so setDragging(false) never runs and panel-resizing stays on <body> — app-wide cursor: col-resize and user-select: none until the divider unmounts.

Reading onCommit through a ref lets the effect key on [dragging] alone, which is what it actually means.

Also

  • The cleanup comment's ⌃R workspace switch example was inert. App keys Workspace by workspace id (App.tsx:111), so a switch unmounts the whole thing: the .workspace element and its inline var are destroyed, and the commit lands on an unmounting component so usePanelWidth's persistence effect never runs. Nothing was committed and nothing needed to be. ⌘⇧\ closing the rail (Workspace stays mounted) is the real case.
  • Noted the pointer/keyboard cap asymmetry. The pointer gesture freezes its editor-room floor at drag start while the keyboard re-tightens per keypress, so within one drag you can shrink past the cap and come back above it — bounded by startWidth, so never worse than where the drag began. Deliberate, but the two paths were presented as sharing "the same floor".
  • CLAUDE.md: the two panel-width keys now sit with the other per-workspace layout keys, and PanelResizer has an entry in the components map.

Tests

One regression test: a parent re-render mid-drag with a deliberately unstable onCommit must leave the gesture intact. Verified it fails on the pre-fix code at the premature-commit assertion, and passes after.

970 passing, typecheck / format:check / ESLint clean.

Unrelated: the red rust CI job

Folded in as its own commit. cargo fmt --check has been failing on main since the iOS work landed — the two cfg-gated MetadataExt imports in is_dataless are out of alphabetical order. Because that step comes first, the job's cargo test and cargo clippy steps have been skipped ever since, so the Rust suite hasn't actually run in CI since #14.

The commit is pure cargo fmt output (the two imports are mutually exclusive by cfg, so the order carries no meaning). CI is now green on both jobs — first full rust pass since f2a4c56, which means clippy and the 25 Rust tests are exercised on Linux again.

🤖 Generated with Claude Code

resure and others added 2 commits August 10, 2026 18:11
…ntity

The mid-drag-unmount cleanup added in #15 tears down the live gesture
(it nulls `drag`), so having `onCommit` in its dep list made correctness
depend on the caller passing a referentially stable callback. Workspace
happens to pass useState setters, so this is latent — but an inline
lambda anywhere would break dragging on the next parent render: a
premature commit, every later pointermove ignored, and `panel-resizing`
stuck on <body> (endDrag returns early on a null gesture, so `dragging`
never clears) leaving the whole app in col-resize/no-select.

Read onCommit through a ref and key the effect on [dragging] alone.
Regression test asserts a parent re-render mid-drag leaves the gesture
intact; it fails on the old code at the premature-commit assertion.

Also two comment corrections and a docs catch-up:

- The cleanup's "⌃R workspace switch" example was inert: App keys
  Workspace by workspace id, so a switch unmounts the whole thing —
  the .workspace element (and the inline var) goes with it, and the
  commit lands on an unmounting component, so nothing persists and
  nothing needed to. ⌘⇧\ closing the rail is the real case.
- Note that the pointer path freezes its editor-room floor for the
  gesture while the keyboard path re-tightens per keypress, so the two
  disagree after shrinking past the cap and coming back.
- CLAUDE.md: list the two panel-width keys with the other per-workspace
  layout keys, and give PanelResizer an entry in the components map.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`cargo fmt --check` has been red on main since the iOS work landed: the
two cfg-gated `MetadataExt` imports in `is_dataless` are out of
alphabetical order, which put the rust job in a failing state and so
skipped its `cargo test` and `cargo clippy` steps entirely.

Pure `cargo fmt` output; the two imports are mutually exclusive by cfg,
so the order carries no meaning. Locally: fmt clean, 25 Rust tests pass,
clippy --all-targets -D warnings clean (macOS host — CI builds Linux,
where the macOS-only paths are cfg'd out).
@resure
resure merged commit 5a6b276 into main Aug 10, 2026
2 checks passed
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