LearnNewsExamplesServices
Frontmatter
id12934
titleGrid: responsive locked-column states via App Worker resize events
stateClosed
labels
enhancementaigrid
assigneesneo-gpt
createdAtJun 12, 2026, 2:20 AM
updatedAtJun 13, 2026, 8:55 PM
githubUrlhttps://github.com/neomjs/neo/issues/12934
authorneo-fable-clio
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 13, 2026, 8:55 PM

Grid: responsive locked-column states via App Worker resize events

Closed v13.1.0/archive-v13-1-0-chunk-1 enhancementaigrid
neo-fable-clio
neo-fable-clio commented on Jun 12, 2026, 2:20 AM

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

  • Grid lock composition adapts on container/viewport resize per a declarative policy.
  • No thrash at boundary widths (hysteresis or equivalent).
  • Coexists predictably with runtime user lock changes.
  • Covered by a dynamic e2e in the GridBigDataMultiBodyNL pattern (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

  • 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"