Skip to content

Commit ac1cbb4

Browse files
bloveclaude
andauthored
feat: tool-panel pane resizing and column auto width (SP5) (#539)
* docs: tool-panel pane-resize + auto-width design (SP5) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: SP5 pane-resize + auto-width implementation plan Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(react): setColumnAutoWidth on the grid handle (SP5) B2: the public handle gains setColumnAutoWidth(columnId, auto) beside setColumnWidth — true puts the column in the live auto-width set, false makes it manual at the engine's current stored width. setColumnWidth still flips auto off and autosizeColumns keeps its all-columns meaning; both pinned by test. B4 audit verdict: Reset restored pin/visibility/order but left auto-mode drift — a half-reset. The surface now captures the mount-time auto set (read from the store's own state, so the no-declared-widthPx rule keeps one home) beside the layout baseline, and Reset replays it through the new setter. The facade also carries an internal ɵautoWidths read seam for chrome that must reflect membership (the SP5 kebab toggle's future subscription). jsdom observes the set at its real seam: mergeRenderColumns strips widthPx for auto columns, so the drawn header width follows the renderer's fallback (140) for auto and the engine store for manual — content-driven pixels stay with Task 4's e2e. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: SP5 spec/plan amended — auto width is a mode bit, not a content fit Verified in a real browser: nothing measures cell content in the column-width path. B3 (auto-size-all footer) dropped; the toggle and docs say grid-managed width; the 140→160 jump documented. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(react): the tool-panel pane resizes by pointer and keyboard (SP5) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(react): the resize separator always carries a coherent aria value range (SP5 review) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(react): auto-width toggle in the columns section's row menu (SP5) B1: the kebab menu gains a role=menuitemcheckbox "Auto width" item, checked from a live subscription over the facade's ɵautoWidths seam and writing through setColumnAutoWidth. The item is a mode bit ("let the grid manage this column's width"), never a content fit, and toggling stays open — the native menuitemcheckbox pattern — while the pin items keep their select-and-close command semantics. B5: ColumnPinMenu sheds its stale name for ColumnRowMenu (it now holds a non-pin item); ColumnPinMenuMessages follows. New message key toolPanelAutoWidthLabel in the three established places. The Task 1 test header's lingering "content-tracking mode" phrase is corrected to the amended spec's mode-bit wording. B3 (auto-size-all footer) is dropped per the amended spec. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: purge the last content-tracking phrases from the SP5 spec Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(e2e): pane resize and auto width through the real shell (SP5) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: pane resizing and the auto-width mode bit on the tool-panel page (SP5) The tool-panel page gains a Resizing-the-pane section (drag, keyboard, bounds, and the A5 stylesheet-vs-inline-width interplay), the width trio joins the Configuration table, the columns section documents the Auto width toggle in the mode-bit wording (including the 140→160 jump), and the two new message keys are named and bound in the docs guard's roster (mutation-verified: dropping the roster entry, retyping a table cell, and unnaming a key each fail the guard). setColumnAutoWidth's TSDoc sheds the pre-amendment content-tracking phrasing for the same mode-bit wording. Changesets: @pretable/react minor, @pretable/ui patch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: regenerate api reports for SP5 (config trio, setColumnAutoWidth, message keys) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 2ebc25d commit ac1cbb4

26 files changed

Lines changed: 2212 additions & 68 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pretable/ui": patch
3+
---
4+
5+
Styles for the tool panel's pane-resize handle: a slim strip on the pane/grid seam (`data-pretable-pane-resize`) with a hover/focus tint over existing tokens, a `col-resize` cursor, and a 24px hit area on coarse pointers.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@pretable/react": minor
3+
---
4+
5+
The tool panel's pane is resizable, and the columns section can hand a column's width back to the grid.
6+
7+
The pane's grid-side edge is now a drag handle with full keyboard parity: a focusable `role="separator"` with live `aria-value*`, arrow keys stepping 16px (direction-aware in RTL), `Home`/`End` to the bounds, and `Enter` or double-click resetting to the default. Width is React chrome state with the surface's controlled/uncontrolled trio — `defaultPaneWidthPx` / `paneWidthPx` / `onPaneWidthChange` on `PretableToolPanelConfig` — clamped everywhere and reported already-clamped. An untouched, uncontrolled pane writes no inline style, so an existing stylesheet override of the pane's width keeps working until someone actually resizes.
8+
9+
The columns section's row menu gains an "Auto width" toggle (`role="menuitemcheckbox"`, the new `toolPanelAutoWidthLabel` message), and the handle gains `setColumnAutoWidth(columnId, auto)`. Auto width is a mode bit, not a content fit: on, the grid manages the column's width (the renderer's default, or a flex share when the column declares `flex`); sizing the column yourself turns it off; the toggle hands the width back. The resize handle's accessible name is the new `toolPanelResizeLabel` message.

‎apps/website/content/docs/grid/tool-panel.mdx‎

Lines changed: 33 additions & 15 deletions
Large diffs are not rendered by default.

‎apps/website/e2e/tool-panel.spec.ts‎

Lines changed: 304 additions & 26 deletions
Large diffs are not rendered by default.

‎apps/website/lib/docs/__tests__/docs-api-surface.test.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1871,6 +1871,15 @@ const TOOL_PANEL_MESSAGE_KEYS: Record<string, MessageKeyBinding> = {
18711871
toolPanelFiltersLabel: { section: "Configuration" },
18721872
toolPanelGroupingLabel: { section: "Configuration" },
18731873

1874+
// The pane's resize handle is shell chrome, not a section's — its label is
1875+
// named where the handle itself is documented.
1876+
toolPanelResizeLabel: { section: "Resizing the pane" },
1877+
1878+
// The columns section still ships no strings listing (see the unlisted
1879+
// block below), but the auto-width toggle's paragraph names its key
1880+
// outright, so it binds plain rather than joining the excuse.
1881+
toolPanelAutoWidthLabel: { section: "The columns section" },
1882+
18741883
// The filters section's strings paragraph names these outright…
18751884
toolPanelAddFilterLabel: { section: "The filters section" },
18761885
toolPanelAddGroupLabel: { section: "The filters section" },
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Tool Panel SP5 — Pane Resizing + Auto Width Implementation Plan
2+
3+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4+
5+
**Goal:** Pane width resizing (drag + keyboard, controlled/uncontrolled) and the columns section's auto-width affordances (kebab toggle + auto-size-all footer action), per `docs/superpowers/specs/2026-08-30-tool-panel-resize-autosize.md`.
6+
7+
**Architecture:** Resizing is React chrome state (the `activeSection` pattern) applied as an inline `inline-size` only after first interaction; the handle is a `role="separator"` strip on the pane's inline-start edge. Auto width rides the existing `createAutoWidthStore` — one new public handle method (`setColumnAutoWidth`), no engine changes.
8+
9+
**Tech Stack:** React 19, TypeScript, vitest via the package `test` script (never bare `vitest run`; positional filters, never `--`), Playwright, api-extractor, changesets.
10+
11+
**Read first, every task:** the spec (decisions A1–A7, B1–B6); `packages/react/src/tool-panel/ToolPanel.tsx` + the `<ToolPanel>` render site and `PretableToolPanelConfig` in `pretable-surface.tsx`; `packages/react/src/tool-panel/useToolRowDrag.ts` (#527 — the drag conventions: capture at pointerdown, Escape-cancel, the comment register); `createAutoWidthStore` + `autosizeColumns` + `setColumnWidth` in `packages/react/src/pretable-model.ts` (~lines 420-440, 655-745); `packages/react/src/tool-panel/ColumnPinMenu.tsx` + `overlay/menu-keyboard.ts`; the keyboard-walk rosters in `apps/website/e2e/tool-panel.spec.ts` and `grid-tab-wrap-rows.spec.ts`; the coarse-pointer hit-area pattern and the pane block in `packages/ui/src/grid.css` (~1455-1480).
12+
13+
**House rules:** worktree only; no `git stash`; `pnpm format` before every commit (required gate); loaded box — isolate failures before believing; `pnpm build` before `pnpm api`; commit per task with trailer `Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>`.
14+
15+
---
16+
17+
### Task 1: `setColumnAutoWidth` on the handle + Reset audit (B2, B4)
18+
19+
**Files:**
20+
- Modify: `packages/react/src/pretable-model.ts` (facade + type)
21+
- Modify: `packages/react/src/tool-panel/ColumnsSection.tsx` ONLY IF the Reset audit finds drift (see Step 3)
22+
- Test: `packages/react/src/__tests__/column-auto-width.test.tsx` (new)
23+
24+
- [ ] **Step 1: Write the failing tests.**
25+
26+
```tsx
27+
// Harness: a 2-column surface — "fixed" declares widthPx: 120, "fluid"
28+
// declares NO width (starts auto). Capture the grid via onGridReady.
29+
// Content strings chosen so auto width visibly differs between them
30+
// (choose-data-that-can-disprove): render "x" vs a long string, assert the
31+
// drawn header cell's offsetWidth/style width CHANGES when content changes
32+
// while auto, and does NOT change after setColumnAutoWidth(id, false).
33+
34+
it("a column with no declared width starts auto; a declared one does not", ...);
35+
it("setColumnAutoWidth(id, true) makes content drive the drawn width", ...);
36+
it("setColumnAutoWidth(id, false) freezes the current width (no jump)", ...);
37+
it("setColumnWidth still flips auto OFF (old behavior survives)", ...);
38+
```
39+
40+
(Adapt: jsdom has no real text measurement — read how existing auto-width behavior is tested; if drawn-width assertions need the renderer's measure seam mocked, follow that precedent. If jsdom cannot observe content-driven width at all, assert the STORE membership through the surface (a data attribute or the kebab state added in Task 3) AND add the drawn-width proof to the Playwright task instead — note the substitution honestly in the test header.)
41+
42+
- [ ] **Step 2: Implement.** In `pretable-model.ts`: add `setColumnAutoWidth(columnId: TColumnId, auto: boolean)` to the `PretableReactGrid`/facade type beside `setColumnWidth`, TSDoc mirroring its conventions (declare-here-for-the-prototype-reach reason if applicable); implementation: `stores.autoWidths.setAuto(columnId as string, auto)`. Verify `autosizeColumns` and `setColumnWidth` interplay unchanged.
43+
- [ ] **Step 3: The Reset audit (B4).** Read ColumnsSection's Reset action: does it restore the initial auto set? Write the test either way: Reset after `setColumnAutoWidth("fixed", true)` must return "fixed" to manual and "fluid" to auto (the initial state). If Reset today doesn't touch the store, wire it (the initial set = ids without declared widthPx — the store's own constructor rule; expose what's needed via existing seams, not new public API).
44+
- [ ] **Step 4: Run the new file + the columns-section suites** (`pnpm --filter @pretable/react test column-auto-width tool-panel`). Mutation: make `setColumnAutoWidth` ignore its `auto` arg (always true) → the freeze test fails; revert.
45+
- [ ] **Step 5: Format, lint, typecheck, commit** — `feat(react): setColumnAutoWidth on the grid handle (SP5)`.
46+
47+
---
48+
49+
### Task 2: pane resize — state, handle, pointer + keyboard (A1–A7)
50+
51+
**Files:**
52+
- Modify: `packages/react/src/pretable-surface.tsx` (config trio + state + clamp), `packages/react/src/tool-panel/ToolPanel.tsx` (the handle element + inline width application)
53+
- Create: `packages/react/src/tool-panel/pane-resize.ts` (pure: clamp logic, step math, direction resolution — the testable-without-React split)
54+
- Modify: `packages/ui/src/grid.css` (handle styling + coarse hit area; zero new tokens)
55+
- Tests: `packages/react/src/__tests__/tool-panel-pane-resize.test.tsx` (new) + pure `tool-panel-pane-resize-math.test.ts`; extend `attribute-contract.test.tsx` (`data-pretable-pane-resize`, asserted non-vacuously)
56+
57+
- [ ] **Step 1: Pure module first, TDD.** `pane-resize.ts`: `clampPaneWidth(px, {min, max})`, `paneWidthAfterKey(key, current, {min, max, dir})` (arrows ±16, Home/End → min/max, direction-aware per A6), `paneWidthAfterDrag(startWidth, startX, currentX, dir, bounds)`. Write the table-driven tests red (both `ltr`/`rtl` rows — the direction flip is the mutation-prone branch), implement, green. MIN: measure the filters section's narrowest usable row in the browser once, record the number AND the measurement in a comment (A4's rule).
58+
- [ ] **Step 2: Config + state.** `PretableToolPanelConfig` gains `defaultPaneWidthPx?/paneWidthPx?/onPaneWidthChange?` (TSDoc per the `activeSection` fields' register, incl. the A5 inline-style note and the clamp-and-report rule). Surface: uncontrolled `useState<number | null>(defaultPaneWidthPx ?? null)` (`null` = untouched → no inline style); controlled presence wins; every write path clamps then reports. Pass width + change callback + bounds into `<ToolPanel>`.
59+
- [ ] **Step 3: The handle in ToolPanel.tsx.** Rendered at the pane's inline-start edge when a pane is open: `role="separator"`, `aria-orientation="vertical"`, `aria-valuenow/min/max`, `tabIndex={0}`, `data-pretable-pane-resize`, aria-label from a NEW message key `toolPanelResizeLabel` (add to messages layer per the established three-places pattern). Pointer: capture at pointerdown, live width updates via the pure drag math, Escape restores drag-start width (document-level listener while dragging, `preventDefault` interlock comment per the house pattern), release commits, double-click and Enter reset to default (clear to `null` when uncontrolled — restoring the stylesheet width per A5; controlled → report the default). Keyboard per the pure module.
60+
- [ ] **Step 4: jsdom tests red then green** (spec Verification A list): drag/clamp/Escape/dblclick-reset/controlled-trio/clamp-report/no-inline-before-interaction/aria values/arrow steps both directions. Follow `useToolRowDrag`'s test techniques for pointer sequences.
61+
- [ ] **Step 5: css** — the strip (reuse rule/hover tokens; a wider transparent hit area on coarse pointers via the established pattern); attribute-contract addition.
62+
- [ ] **Step 6: Mutations:** invert the direction resolution → the rtl rows fail; drop the clamp on the controlled path → the clamp-report test fails. Revert, green.
63+
- [ ] **Step 7: Full react suite + ui suite; format, lint, typecheck; commit** — `feat(react): the tool-panel pane resizes by pointer and keyboard (SP5)`.
64+
65+
---
66+
67+
### Task 3: the kebab toggle + rename (B1, B5; B3 DROPPED — see the amended spec)
68+
69+
**Files:**
70+
- Rename: `packages/react/src/tool-panel/ColumnPinMenu.tsx` → the honest name (B5; update imports/tests)
71+
- Modify: the renamed menu (menuitemcheckbox "Auto width"), `ColumnsSection.tsx` (plumb `setColumnAutoWidth` + the auto set via the ɵautoWidths seam Task 1 added), messages (+`toolPanelAutoWidthLabel` — three places), `grid.css` if the checkbox item needs styling; correct the Task 1 test header's "content-tracking mode" phrase to the mode-bit wording
72+
- Tests: extend the menu's + columns-section's suites; attribute contract if new attributes
73+
74+
- [ ] **Step 1: Failing tests:** the menu shows "Auto width" checked/unchecked per the live set (fixed → unchecked, fluid → checked — reuse Task 1's harness shape); toggling writes through `setColumnAutoWidth` with the RIGHT column id (mutation target); `role="menuitemcheckbox"` + `aria-checked` correct; keyboard operation via the shared `useMenuKeyboard` (no fourth hand-rolled copy); Reset restores (extends Task 1's B4 test through the UI). Assertions state the REAL semantic (auto ⇒ renderer width, manual ⇒ engine width) — never a content-fit claim.
75+
- [ ] **Step 2: Implement.** The section already subscribes to engine/layout state — the auto set needs its own subscription (`autoWidths.subscribe/getState` — check what the section can reach; plumb through the descriptor via a stable handle, respecting the DEPS RULE: the set is engine-ish state, so reach it by subscription, never bake it into the memo).
76+
- [ ] **Step 3: Mutations:** wire the toggle to a constant column id → the per-column test fails; invert checked-state derivation → the reflect test fails. Revert.
77+
- [ ] **Step 4: Run the tool-panel suites + full react suite; format, lint, typecheck; commit** — `feat(react): auto-width toggle and auto-size-all in the columns section (SP5)`.
78+
79+
---
80+
81+
### Task 4: e2e — the seam, the walks, the pixel proofs
82+
83+
**Files:**
84+
- Modify: `apps/website/e2e/tool-panel.spec.ts` (+ possibly the fixtures page if the walk needs the handle)
85+
- Verify: `grid-tab-wrap-rows.spec.ts` untouched-green
86+
87+
- [ ] **Step 1: New cases** (hydration-gated, helpers reused): (a) pointer-drag the seam → pane width changes AND the grid reflows (assert something grid-side: a header cell's x-position or the horizontal scrollbar state — prove the pixel, not the style); (b) keyboard: Tab reaches the handle inside the pane's roster (UPDATE every walk roster the handle joins — with the "update when…" comment style), arrows resize, Enter resets; tab-exit guard still green; (c) auto-width: toggle a column's "Auto width" in the kebab → the drawn header width moves between the ENGINE's stored width and the RENDERER's width (140/flex share) — the verified mode-bit semantic; content changes must NOT move an auto column's width (assert the negative — it pins the semantic against the content-fit myth).
88+
- [ ] **Step 2: Run per the recipe** (root build, website build, prod server on a free port, `--workers=1`, both browsers, twice; isolate any failure). Include `grid-tab-wrap-rows.spec.ts` in the run.
89+
- [ ] **Step 3: Format/lint/typecheck website; commit** — `test(e2e): pane resize and auto width through the real shell (SP5)`.
90+
91+
---
92+
93+
### Task 5: docs, api, changesets, final verification
94+
95+
**Files:**
96+
- Modify: `apps/website/content/docs/grid/tool-panel.mdx` (resizing subsection with the A5 css-override interplay note; the width trio joins the Configuration table; the columns-section prose gains the auto-width pair with B6's plain statement; new message keys join the strings listings — the #512 guard will demand them), handle docs for `setColumnAutoWidth` wherever the guard requires
97+
- Create: changesets — `@pretable/react` minor, `@pretable/ui` patch
98+
99+
- [ ] **Step 1:** Docs + live-example touch-ups if the existing tool-panel example benefits (don't force one); run the website guard suite; satisfy every fail-closed demand honestly (SEO description ≤155 if touched).
100+
- [ ] **Step 2:** `pnpm build` then `pnpm api`; commit reports; `api:check` green. Expected moves: the config trio, `setColumnAutoWidth`, new message keys.
101+
- [ ] **Step 3:** Full verification: root build; react/ui/website suites; root lint + typecheck; format. Commit. Push branch, open PR `feat: tool-panel pane resizing and column auto width (SP5)` (body: spec decisions + test plan + generated-with footer). Merge on green only; read the merge back from origin/main.
102+
103+
---
104+
105+
## Self-review notes
106+
107+
- Spec coverage: A1→T2 (handle/css), A2→T2 (pointer/Escape/dblclick), A3→T2 (trio), A4→T2 (pure clamp + measured MIN), A5→T2 (null-state inline rule) + T5 docs, A6→T2+T4 (keyboard + rosters), A7→spec-only (no work); B1→T3, B2→T1, B3→T3, B4→T1+T3, B5→T3, B6→T5 docs. Verification section maps to T1–T4 tests + T5 gates.
108+
- Names used consistently: `setColumnAutoWidth`, `defaultPaneWidthPx`/`paneWidthPx`/`onPaneWidthChange`, `pane-resize.ts` functions (T2 defines, T4 exercises), message keys `toolPanelResizeLabel`/`toolPanelAutoWidthLabel` (T2/T3 define, T5 documents).
109+
- No engine (grid-core/row-model) changes anywhere — if a task finds itself editing them, stop and escalate.
110+
- jsdom's text-measurement limitation is handled explicitly (T1 Step 1's substitution rule + T4's pixel proof), not discovered mid-task.

0 commit comments

Comments
 (0)