feat(modules): ResponsiveLayout — container-size-driven reflow (7 phases)#1391
Closed
mathuo wants to merge 9 commits into
Closed
feat(modules): ResponsiveLayout — container-size-driven reflow (7 phases)#1391mathuo wants to merge 9 commits into
mathuo wants to merge 9 commits into
Conversation
Add `LayoutPriority.Fill` so a view (standalone gridview) or group (dockview) absorbs all surplus/deficit space while its siblings keep their fixed sizes — the common "central content fills, side panels stay put" layout that proportional priorities cannot express. Closes #1378. - splitview: fill views absorb space in layout/relayout/distributeEmptySpace; multiple fills split the surplus equally; hidden fills fall back to normal - gridview: Fill propagates up the branch tree (highest precedence); the runtime priority setter forces a full relayout so propagation takes effect - dockview: group-level priority via addGroup({ priority }), group.api.priority, and toJSON/fromJSON round-trip Backwards compatible: every behaviour change is gated on a fill view being present, so existing layouts and serialized state are unchanged; the serialized `priority` key only appears when a group opts in. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring LayoutPriority.Fill into the responsive-layout branch — the ResponsiveLayoutModule folds LayoutPriority (incl. Fill) into its collapse-priority scoring. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the seam + skeleton for container-size-driven responsive layouts. The module resolves the active breakpoint from the observed container width and fires `onDidBreakpointChange` as the container crosses thresholds. No reflow transforms yet — those land in later phases. - core: `responsive` option (breakpoints + hysteresis), the `IResponsiveLayoutHost` / `IResponsiveLayoutService` contracts + service slot, and `activeBreakpoint` / `onDidBreakpointChange` / `reflow()` on the component + public api. - module: a pure `BreakpointResolver` (direction-aware hysteresis dead bands, anti-thrash), a debounced `SizeObserver` (injectable clock), and `ResponsiveLayoutService` wiring them to `onDidLayoutChange`. Backwards compatible: inert unless `responsive.breakpoints` is configured; the option is ignored when the module is absent. Tests: 21 resolver + 8 observer + 4 integration. core 1135, modules 268 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…erialization
Introduce the canonical/derived model beneath the breakpoint resolver. The
service now holds the canonical ("wide") layout, projects the derived layout
from it via a pure `deriveLayout` (identity for now), and serializes the
*canonical* through `toJSON` so a narrow-width save can never bake a collapsed
layout in. No collapse transforms yet — the derived layout still equals the
live one.
- module: `CanonicalStore` (holds the wide SerializedDockview), a pure
`deriveLayout(canonical, rules)` (identity clone) + `diffLayouts` (zero ops
when identical — the idempotence guard), wired into `ResponsiveLayoutService`.
- core: `IResponsiveLayoutHost` gains `toJSON()` + `onDidLayoutFromJSON`;
`IResponsiveLayoutService` gains `serializeCanonical()`; `DockviewComponent.
toJSON` serializes canonical grid/panels when derived, else the live tree.
Backwards compatible: `serializeCanonical()` returns undefined until the layout
is actually collapsed, so serialization is byte-identical to today (verified by
the full core suite + round-trip/idempotence tests).
Tests: +5 canonical-store, +7 reflow-engine, +3 integration (round-trip,
idempotence, inert-serialization). core 1135, modules 283 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The first real reflow transform. `deriveLayout` now implements `collapseToTabs`: side-by-side groups fold into a single tabbed group, ordered by group priority. - Priority scoring (`computeGroupPriority`): folds `LayoutPriority` — `Fill` outranks `High` > `Normal` > `Low` — plus an active-group tie-break bonus, so the group the user is working in survives collapse longest. - The highest-priority group is the host (its id + settings survive); remaining groups' panels become tabs after it in descending priority order (document order breaks ties); the globally-active panel stays active. - Pure + non-destructive: canonical is cloned, never mutated — so widening (re-deriving with no collapse rule) reproduces it byte-for-byte. This is the pure engine only (parallel to the Phase 4 Applier): the service does not yet apply a collapse to the live tree — it keeps deriving identity until the minimal-diff apply + rebase safety land, so behaviour is unchanged. Tests: +2 priority scoring, +8 collapse (ordering, host, active preservation, single-group no-op, non-mutation, reversibility, panels pass-through). modules 293 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ive reflow)
The collapse now reaches the screen. On crossing into a collapsing band the
service freezes the current layout as canonical, then reconciles the live tree
to the derived target via `fromJSON({ reuseExistingPanels: true })` — panels are
re-parented, not re-created. Widening back to the canonical band restores the
frozen wide layout exactly.
- reentrancy guard (`_applying`): the apply's own layout events never
re-trigger a reflow.
- per-breakpoint rules (`effectiveRules`): a breakpoint's own `rules` win; the
widest band is identity; narrower bands fall back to the default `rules`.
- applied-tracking (`_appliedBreakpoint`): a layout that *starts* inside a band
(never crossing a boundary) still applies on the first settle.
- defer-on-maximize: reflow waits for restore.
- collapse output keeps the grid root a branch (dockview's `fromJSON` requires
it) — one leaf group filling the primary axis.
Known limitation (later phase): edits made *while collapsed* are not yet rebased
onto canonical, so saving in a collapsed state persists the pre-edit wide layout.
Tests: +5 integration — collapse/expand round-trip, panel-instance reuse,
canonical (wide) serialization while collapsed, one-event-per-transition (no
re-entrant reflow), maximize deferral. core 1135, modules 298 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two more reflow transforms, and `deriveLayout` now applies its rule chain in array order so passes compose. - restack: flip the primary axis (columns <-> rows) by flipping the grid orientation; nested branches alternate from the root, so the whole tree restacks. Sizes carry through and rescale to fit. - hide: park the Low-priority groups by marking them `visible: false` — the group and its panels survive (re-shown on widen), they are just not laid out. The highest-priority group is always kept visible so the layout is never left empty. (An overflow affordance to reach parked groups is a later refinement.) Both compose with collapseToTabs and, like it, are pure + non-destructive: canonical is cloned, never mutated, so widening reproduces it exactly. They reach the live layout through the existing Phase 4 apply. Tests: +6 pure (restack flip/reversible, hide park/keep-top/no-op, rule-chain ordering) + 2 integration (restack keeps panels through narrow/widen; hide parks a Low group while keeping its panel alive). modules 306 green (core unchanged, 1135). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…le collapsed) Close the last correctness gap: edits a user makes *while collapsed* are now folded back onto the canonical (wide) layout, so widening keeps them instead of resurrecting the pre-edit arrangement. - rebase (`responsiveRebase.ts`, pure): reconcile canonical against the live derived layout by panel id — closes remove the panel (and prune emptied groups / collapse single-child branches), adds insert into the canonical active group, and active-tab changes update canonical's active. Moves/resizes within the collapsed layout don't change the panel set and are ignored. - service: on `onDidMutateLayout` while collapsed (and not mid-apply), rebase per `rebase` mode — `'auto'` (default) reconciles, `'discard'` freezes, `'manual'` fires `onDidRebaseConflict`. Conflicts (e.g. an add with no target group) surface the event instead of silently losing the edit. - core: `rebase` option; host `serializeLiveLayout()` (raw live tree, bypassing the canonical `toJSON` hook — refactored `toJSON` to build on it) + `onDidMutateLayout`; `onDidRebaseConflict` on the service, component + api. Tests: +7 pure (close, prune, add, activate, no-op, non-mutation, conflict) +4 integration (auto keeps a close/add through widen; discard drops it; manual fires the conflict). core 1135, modules 317 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t exclusion Completes the module's public surface and hardens the out-of-scope guarantee. - public API: `getCanonicalLayout()` / `setCanonicalLayout()` on the service, component + api. `getCanonicalLayout` reports the wide layout even while collapsed; `setCanonicalLayout` swaps the wide baseline and re-derives for the current width. Both fall back to `toJSON`/`fromJSON` when the module is absent. - float/popout exclusion: the apply now overlays the *live* floating groups, popouts and edge groups onto the derived target (new `applyGrid` helper), so a collapse/restore never tears them down or reverts a float moved while collapsed. The pure transforms already pass them through untouched. Tests: +4 — getCanonicalLayout reports wide-while-collapsed, setCanonicalLayout replaces + re-derives, a floated group survives collapse/widen intact, and the pure transform passes floatingGroups through. core 1135, modules 321 green. Docs page + live demo are the remaining discoverability piece (they need the docs-package template infrastructure) — tracked separately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Owner
Author
|
Closing — opened prematurely; review happening on the branch directly before a PR is raised. |
|
|
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.



What this is
A new
ResponsiveLayoutModulethat makes a dockview layout reflow as its container resizes — collapsing side-by-side groups into tabs when narrow, restacking columns to rows, hiding low-priority groups — and restoring the exact wide arrangement losslessly when there's room again. It reacts to container width (not viewport), so a dock embedded in a 400px sidebar reflows even on a 4K monitor.Spec:
enterprise-modules/responsive-layout.md. Built opt-in via a newresponsiveDockviewOptionsfield; inert (andtoJSONbyte-identical) unless configured.Built in 7 reviewable phases (one commit each)
onDidBreakpointChange.toJSONserializes it, never the collapsed view.LayoutPriority, incl.Fill).fromJSON({ reuseExistingPanels }); reentrancy guard, per-breakpoint rules, defer-on-maximize.visible:false).rebase: auto|discard|manual+onDidRebaseConflict.get/setCanonicalLayout(); floats/popouts/edge groups untouched across reflow.Design highlights for reviewers
(canonical, rules) -> SerializedDockview; the only mutation is thefromJSONapply behind an_applyingreentrancy guard.Tests
86 responsive tests — pure engine (resolver, observer, reflow engine, rebase, canonical store) + jsdom integration (collapse/expand, panel reuse, canonical serialization, rebase round-trips, float survival).
dockview-core1135,dockview-modules321, both green; core + modules typecheck clean.Notes
LayoutPriority.Fill(PR feat(core): LayoutPriority.Fill — one view/group fills, siblings stay fixed #1389, targeting master) — that commit is merged into this branch, so the diff includes it. For a clean merge, land Fill on v8-branch first (via master → v8 merge-forward), then this rebases to responsive-only.CodeRunnerdemo are not included — they need the docs-package template infrastructure and are tracked as a separate follow-up.🤖 Generated with Claude Code