Context
Operator-seeded idea (2026-06-12 wrap-night session), captured so it survives the release push: locked columns are valuable on large screens but consume the viewport on small ones (the arithmetic that motivated #12933 — devindex's locked regions alone exceeded a phone-portrait width). The durable answer isn't "no locks" — it's lock states that respond to available width.
Release classification: boardless — post-release enhancement by design (operator: "possible, but not a small item to tackle pre-release").
The Problem
column.locked is a static config. There is no mechanism for a grid to adapt its locked-region composition to viewport/container size changes — so apps must choose between desktop-optimal locks (broken on mobile portrait) and no locks (losing the big-screen ergonomics).
The Architectural Reality
- Resize signals already reach the App Worker (main-thread resize observation → worker events) — the operator named this as the natural mechanism.
- The runtime lock-flip path is already exercised and tested:
column.locked = 'start'|'end'|false at runtime triggers onColumnLockChange → re-partition → redistribution (GridBigDataMultiBodyNL.spec.mjs drives exactly this via the NL fixture).
- So the engine-side state machine exists; the missing layer is the policy binding: breakpoint/threshold config mapping available width → locked-column composition, evaluated on resize.
- Design care: hysteresis (no lock-thrash at boundary widths), interaction with user-initiated lock changes (#9491 family — drag-to-lock), and the defect family (#12929/#12930/#12932) should be fixed first so the flip path is trustworthy under repeated invocation.
The Fix (shape, not prescription — design lane)
Breakpoint-driven lock policy, e.g. a grid-level config mapping width thresholds → per-column locked states (or a callback), evaluated on App Worker resize events with hysteresis. Exact API shape deserves a short design pass against the existing responsive primitives before implementation.
Contract Ledger
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback / Edge Case |
Docs |
Evidence |
Neo.grid.Container#responsiveLockPolicy |
#12934 + src/grid/Container.mjs resize/lock surfaces |
Optional grid-level policy object. Shape: {breakpoints: [{maxWidth, columns}], hysteresis?: Number}. Each breakpoint columns map keys by column.dataField or column id and values to 'start', 'end', false, or null; the first breakpoint whose maxWidth is >= measured width applies. |
Missing policy = current behavior. Empty/invalid breakpoints are ignored. Columns absent from a matched breakpoint keep their current lock state. Unknown column keys are ignored. |
JSDoc on config + method summaries. |
Unit: resize-width policy flips named columns and preserves omitted columns. |
| Width source and evaluation timing |
GridContainer#onResize() / passSizeToBody() |
Evaluate policy from the App Worker after resize data is received and before final body sizing/render refresh. Use data.width when provided; otherwise use data.contentRect.width / measured container width if available. |
Undefined or non-positive width skips policy application and preserves current state. Initial resize can evaluate once mounted, but no mutation should happen before columns exist. |
JSDoc on helper. |
Unit: direct helper call with explicit widths; e2e may later drive actual container resize through NL. |
| Boundary hysteresis |
#12934 no-thrash AC |
Optional hysteresis px band, default 0. Once a breakpoint is active, keep it until width crosses outside the breakpoint boundary by the hysteresis amount. |
hysteresis <= 0 disables the band. Rapid oscillation within the band should not reapply lock states. |
JSDoc on config semantics. |
Unit: widths around threshold do not thrash. |
| User lock interaction |
#9491 family + existing runtime column.locked path |
Policy application only writes columns named by the active breakpoint and only when the target differs. User changes remain until the next policy application that explicitly names the column for the active breakpoint. |
If an app wants pure manual control, leave responsiveLockPolicy unset. Omitted columns are manual/user-owned under this policy. |
JSDoc caveat. |
Unit: user lock on an omitted column survives policy evaluation; named column is policy-owned on evaluation. |
| Existing lock-flip pipeline |
column.Base#afterSetLocked() -> GridContainer#onColumnLockChange() |
Reuse the existing runtime setter path (column.locked = ...) so partitioning, header synchronization, scroll manager sync, and body refresh stay centralized. |
No direct array surgery; no duplicate re-partition path. |
Method summary. |
Existing LockedColumns.spec.mjs plus new focused policy tests. |
Acceptance Criteria
Out of Scope
- The devindex demo cleanup (#12933 — lands first, independently).
- The current defect family fixes (#12929/#12930/#12932/#12883) — prerequisites for trustworthy repeated flips.
Related
- #12933 (companion demo cleanup) · #12883/#12929/#12930/#12932 (flip-path trustworthiness prerequisites) ·
GridBigDataMultiBodyNL.spec.mjs (the runtime-flip coverage pattern).
Live latest-open sweep: checked latest 15 open issues at 2026-06-12T00:18Z; no equivalent found. A2A in-flight sweep: clean.
Lane note: filed unassigned — post-release design+implementation lane.
Origin Session ID: e6b095bb-4d88-4aeb-a0ec-7d47e0f8f7ce
Retrieval Hint: "responsive locked columns breakpoint resize app worker grid policy hysteresis"
Context
Operator-seeded idea (2026-06-12 wrap-night session), captured so it survives the release push: locked columns are valuable on large screens but consume the viewport on small ones (the arithmetic that motivated #12933 — devindex's locked regions alone exceeded a phone-portrait width). The durable answer isn't "no locks" — it's lock states that respond to available width.
Release classification:boardless — post-release enhancement by design (operator: "possible, but not a small item to tackle pre-release").The Problem
column.lockedis a static config. There is no mechanism for a grid to adapt its locked-region composition to viewport/container size changes — so apps must choose between desktop-optimal locks (broken on mobile portrait) and no locks (losing the big-screen ergonomics).The Architectural Reality
column.locked = 'start'|'end'|falseat runtime triggersonColumnLockChange→ re-partition → redistribution (GridBigDataMultiBodyNL.spec.mjsdrives exactly this via the NL fixture).The Fix (shape, not prescription — design lane)
Breakpoint-driven lock policy, e.g. a grid-level config mapping width thresholds → per-column locked states (or a callback), evaluated on App Worker resize events with hysteresis. Exact API shape deserves a short design pass against the existing responsive primitives before implementation.
Contract Ledger
Neo.grid.Container#responsiveLockPolicysrc/grid/Container.mjsresize/lock surfaces{breakpoints: [{maxWidth, columns}], hysteresis?: Number}. Each breakpointcolumnsmap keys bycolumn.dataFieldor column id and values to'start','end',false, ornull; the first breakpoint whosemaxWidthis >= measured width applies.GridContainer#onResize()/passSizeToBody()data.widthwhen provided; otherwise usedata.contentRect.width/ measured container width if available.hysteresispx band, default0. Once a breakpoint is active, keep it until width crosses outside the breakpoint boundary by the hysteresis amount.hysteresis <= 0disables the band. Rapid oscillation within the band should not reapply lock states.column.lockedpathresponsiveLockPolicyunset. Omitted columns are manual/user-owned under this policy.column.Base#afterSetLocked()->GridContainer#onColumnLockChange()column.locked = ...) so partitioning, header synchronization, scroll manager sync, and body refresh stay centralized.LockedColumns.spec.mjsplus new focused policy tests.Acceptance Criteria
GridBigDataMultiBodyNLpattern (resize → assert region composition) or, for the first implementation slice, focused unit coverage of the App Worker policy helper plus a follow-up e2e if browser resize plumbing is not stable in the same PR.Out of Scope
Related
GridBigDataMultiBodyNL.spec.mjs(the runtime-flip coverage pattern).Live latest-open sweep: checked latest 15 open issues at 2026-06-12T00:18Z; no equivalent found. A2A in-flight sweep: clean.
Lane note: filed unassigned — post-release design+implementation lane.
Origin Session ID: e6b095bb-4d88-4aeb-a0ec-7d47e0f8f7ce
Retrieval Hint: "responsive locked columns breakpoint resize app worker grid policy hysteresis"