Port to react-resizable-panels 4.12.2 - #269
Merged
Merged
Conversation
Supersedes #241, which could not be merged: v4 renames the public API, so both panelled pages failed to compile. PanelGroup direction= → Group orientation= PanelResizeHandle → Separator autoSaveId → useDefaultLayout({ id, storage }) feeding defaultLayout + onLayoutChanged onLayout(number[]) → onLayoutChange(Layout), keyed by panel id Panel order= → gone; ids alone identify conditional panels Sizes changed meaning: in v4 a number is pixels and a string is a percentage, so every defaultSize/minSize/maxSize becomes a string to keep what it meant. The neighbour-only divider rule survives, but not for free. v4 still spills past a neighbour that has reached its minimum — measured again here: the board|intel divider took 73px from the Analysis rail before the pin was working. And the pin was not working, because Separator spreads the props it is given and *then* sets its own onFocus/onBlur, silently overwriting the handlers the hook passes. It looked wired up and never fired. The hook now uses the capture-phase props, which the component does not overwrite. Measured after porting, on both multi-panel groups, each divider in both directions: the pair trades, the third panel reports 0, and a drag that would overrun a neighbour's minimum stops there. Analysis rail|board → rail +128 board −128 side 0 board|side ← board −421 side +421 rail 0 Players rail|filters → rail +366 filters −366 mosaic 0 filters|mosaic → filters +366 mosaic −366 rail 0 One upgrade note: v4 persists layouts in its own format, so saved panel sizes reset once. The old react-resizable-panels:* keys are left behind as dead data rather than migrated — the sizes are cheap to redo and the migration is not worth carrying. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reported from the real app: two dividers on Games and Analysis highlighted on
hover and did nothing on drag, while every divider in a two-panel group worked
— and "click it first, then click and drag" got them moving. Both symptoms
point at the same thing: the pin.
The neighbour-only rule works by constraining the panels a divider does not
touch. Two mistakes in how that was applied, neither visible in a browser
harness driven by keyboard:
· It was applied on pointer-down. Changing a panel's constraints *during* a
drag aborts that drag, which is why the first press did nothing and the
second — with constraints already settled — worked. It is now applied on
hover, before the pointer goes down.
· It locked min == max. A spill can only ever *shrink* a panel the divider
does not touch (the panel being grown is always the divider's own
neighbour), so a floor is enough; a lock over-constrains the group, and
locking the last panel made v4 refuse to resize at all. `pin` is now
`floor`, setting minSize only, with maxSize left static.
Verified by toggling the floors at runtime on the Games group: without them,
dragging filters|board spilled 254px out of the far right panel; with them the
pair trades and the far panel reports 0, in both directions and on Analysis
too.
One harness lesson worth recording: firing several resize keys with no frame
between them makes v4 report no movement at all, which reads exactly like a
frozen divider. Every measurement here paces the presses.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dragging the divider between the move text and the reference panel resized the board and left the move text untouched — because the "board" panel was really two things: the board area, which absorbed every width change, and a move-list sidebar pinned to a fixed pixel width with its own little drag handle inside. A divider should resize the two panels it separates, which means those two have to *be* panels. The Analysis group is now rail | board | move text | intel: four siblings, three dividers, each trading between its own pair. GameBoard keeps all of its state; only the DOM moves. It takes an optional `moveListHost` element and portals the move list into it, so the Analysis view can own that column as a panel while the PGNs viewer keeps the inline sidebar and its handle exactly as before. Measured on a fresh layout, each divider in turn: rail|board → rail +121 board -121 moves 0 side 0 board|moves → rail 0 board +179 moves -179 side 0 moves|intel ← rail 0 board 0 moves -179 side +179 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both pages were fixed-width rails: PGNs at 14rem and 18rem, Prep at 18rem,
with nothing to drag. They are panels now — PGNs is files | games | board,
Prep is tournaments | prep — under the same rule as the rest.
PGNs also had its own collapse idiom: a floating ‹ chevron hanging off the
panel edge, and a 24px strip with sideways text. It now matches Games and
Players — « in the panel header, » on the strip.
Two things this page needed that the others did not:
· Its panels stay mounted while collapsed. The file browser would otherwise
forget its directory, and the game list is what reports a file's game
count. Collapsing therefore constrains the panel to a strip's width rather
than removing it. Verified by tagging a node inside the browser and
finding the same node after a collapse and expand.
· This whole view is mounted the entire time and merely hidden, so its group
measures zero on mount and falls back to an equal split, ignoring every
defaultSize — the files rail came up at 50%. It takes an explicit starting
layout instead, which needs no measurement.
Dropping `panelIds` from useDefaultLayout on the Games group for the same
reason: with it, v4 synthesises an equal split and ignores defaultSize.
Known rough edge: expanding a collapsed rail restores it to its minimum width
rather than the width it had before collapsing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #241, which could not be merged as-is: v4 renames the public API, so
AnalysisPageandGamesPageboth failed to compile.The API move
PanelGroup direction=Group orientation=PanelResizeHandleSeparatorautoSaveIduseDefaultLayout({ id, storage })→defaultLayout+onLayoutChangedonLayout(number[])onLayoutChange(Layout), keyed by panel idPanel order=Sizes changed meaning: a number is now pixels and a string is a percentage, so every size prop becomes a string.
What the port cost, beyond renames
Three defects, each found only by testing in the real app rather than a browser harness:
Dividers that highlighted but would not drag. The neighbour-only rule constrains panels a divider does not touch, and it did so on pointer-down — changing a constraint mid-drag aborts that drag. Hence "click it first, then click and drag works". Constraints are now applied on hover, before the pointer goes down. It also locked
min == max; a spill can only ever shrink a far panel, so a floor is enough, and locking the last panel made v4 refuse to resize at all.The Analysis move text could not be resized. Its "board" panel was really two things — a board area that absorbed every width change and a move list pinned to a fixed pixel width. The move text is now a panel in its own right: rail | board | move text | tabs.
GameBoardkeeps all state and portals the move list into a host the view owns, so the PGNs viewer keeps its inline sidebar unchanged.PGNs and Prep had no panels at all, and PGNs used a different collapse idiom. Both are panelled now, with the
«header button and»strip used elsewhere. PGNs needed two special cases: its panels stay mounted while collapsed (the browser would forget its directory; the game list reports the file's game count), and because that view is always mounted and merely hidden, its group measures zero at mount and needs an explicit starting layout — otherwise everydefaultSizeis ignored and it comes up at an equal split. The same trap applied to the Games group viapanelIds, now dropped.Verification
Divider rule re-measured under v4 on every multi-panel group, each divider in both directions — the pair trades, the third panel reports 0, and a drag that would overrun a neighbour's minimum stops there:
With the floors disabled at runtime, the same Games drag spills 254px out of the far right panel — so the rule is doing real work.
PGNs: starts 16/84, collapses to a 32px strip with its separator gone, expands back; the file browser is not remounted across a collapse (verified by tagging a node and finding it again).
Also exercised end-to-end in the packaged app across drafts 0.15.9–0.15.12.
Known rough edges
react-resizable-panels:*keys are left as dead data.🤖 Generated with Claude Code
https://claude.ai/code/session_01Hx8hR7hu2hrMGhZr3HxRko