# Feature request: sections with their own width in floating panels - **Status:** RESOLVED (2026-11-02), with a simpler design than proposed below. No new `SectionDef` fields: a section is designed for its home panel, so its width is that panel's `defaultLeftWidth`/`defaultRightWidth`, or the library default. A new floating panel opens at that width. The widest designed width is a hard minimum for the panel, and nothing shrinks a panel by itself, so a resize drag can only narrow one once its widest section has left. The viewer's info sections get their width from its existing `defaultRightWidth` with no code change. See `docs/dnd.md`. - **Component:** `layout/sectionLayout.ts`, `layout/StageFloatHost.tsx`; docs in `docs/dnd.md` - **Pulled by:** dsts-viewer (its info sections are designed for 360px, its controls for the default panel width) - **Severity:** medium. Sections end up at widths they weren't designed for, and stay there - **Date:** 2026-11-01 ## What happens today Found by hand in the viewer, with sections of different widths: 1. **Merging:** dragging one floating panel onto another makes the arrivals take the target's width, however narrow, which can squeeze them into a layout they were never designed for. 2. **Pulling apart:** a section pulled off a floating panel stays at the width of the panel it left, even though it's now free to return to its own size. ## Cause A section has no width of its own. `SectionDef` has no width field, so width exists only on the container: a side panel's track, or `FloatingPanel.w`. - `onDocDragEnd` seeds every new floating panel with the width the section measured at the moment of the drag: `layout.floatOut(id, x, y, sectionEl.getBoundingClientRect().width)`. That avoids a jump when leaving a side panel, but can't tell a section's natural width from one it only had because of the panel it was in. - `dockPanel` moves the sections and prunes the source panel, with no width logic. The target keeps its width and the arrivals fill it. - `move` into a floating dock behaves the same way. ## Proposed API Additive on `SectionDef`. Omitting both keeps today's behavior, so existing consumers don't change. ```ts interface SectionDef { // ...existing /** Width (px) this section is designed for when floating. */ readonly preferredWidth?: number; /** Narrowest width (px) its content works at. Floors any floating panel holding it. */ readonly minWidth?: number; } ``` The names are suggestions. ## Proposed behavior Derive a floating panel's width rather than copying it around, so that merging and pulling apart need no special handling: - **`FloatingPanel.w` means the user resized this panel.** Only a resize drag sets it for sections with a `preferredWidth`. - **With no `w`, a panel is as wide as the widest `preferredWidth` of its sections,** falling back to the default CSS width when none has one. - **With a `w`, that width stands,** even when a section with a larger `preferredWidth` joins. - **Either way, the panel never goes below the largest `minWidth` of its sections.** This also floors the resize drag, in place of `MIN_FLOAT_WIDTH` where it is larger. | Action | Result | |---|---| | Pull a section off a side panel | Opens at its `preferredWidth` | | Pull a section off a floating panel | Opens at its `preferredWidth`; the old panel's resize doesn't follow it | | Merge two unresized panels | Takes the widest `preferredWidth` of the combined sections | | Merge into a resized panel | Keeps its width, raised if needed to fit each `minWidth` | | A wide section leaves an unresized panel | The panel narrows to fit the sections left | A section with no `preferredWidth` keeps today's seeding when it leaves a side panel, so it doesn't jump in width. See question 1 for one leaving a floating panel. ## Design questions 1. **Where does a section with no `preferredWidth` go when pulled off a floating panel?** Today it keeps the panel's width, which is the second bug above. **Recommended: treat its home dock's width as its natural width** when that dock can be measured, otherwise the default CSS width. The home dock is already the place `redock` sends it, so this fits the model. If that's too costly, the default CSS width alone is still better than keeping a leftover width. 2. **Does `w` stay set for resized panels after every section but one has left?** **Recommended: yes.** The user sized that panel on purpose, and a new floating panel is what starts fresh. ## Details to get right - **Persistence:** `w` is already persisted, so a stored width from before this change reads as a user resize. That's harmless, and a consumer can bump `version` to discard it. A derived width shouldn't be stored at all, so a changed `preferredWidth` in code takes effect on next load. - **Region limits:** a `minWidth` or `preferredWidth` wider than the float region must not push the panel off-screen. `max-width: 100%` already caps it, and that cap should win. - **No width jump on drag:** the drag image is built at the section's docked width, and the panel lands at `preferredWidth`, so a section whose two widths differ will change width on drop. That's expected, but the grab offset should still keep the panel's top-left where the ghost's was. - **Reactivity:** a derived width should follow membership changes without a remount, as sections arrive or leave. ## Acceptance - Merging panels with sections of different `preferredWidth`s gives the widest, and pulling one back off returns each to its own width. - A panel resized by hand keeps that width through a merge, but is raised to fit a `minWidth`. - A resize drag can't go below the largest `minWidth` of the panel's sections. - Sections with neither field behave as they do today when leaving a side panel. - `InfoIcon`, field validation and everything else outside floating panels is unaffected. - `docs/dnd.md` documents both fields, and tests cover the table above. ## Out of scope - **Side panel widths.** These stay the consumer's call, and a section docked in a panel narrower than its `minWidth` isn't widened by this request. In the viewer that's a real case: the digivolution tree needs about 240px and the controls panel is 230px, so docking it there clips it. That's worth a separate look if it matters in practice.