feat: Board Autofit Toggle#330
Conversation
There was a problem hiding this comment.
Pull request overview
This PR expands the Board UX by adding a per-board desktop autofit toggle (persisted in localStorage and controllable via TopBar + command palette), and also includes substantial board pane reorder functionality (DnD + palette parity) plus a small backend fix to support cross-server neighbour resolution for reorder.
Changes:
- Add
useBoardAutofitpersistence + wire a board-only TopBar toggle andBoard: Toggle Autofitpalette action; implement the DesktopRow/BoardPane autofit layout branch and hide resize handles while on. - Add board pane reorder: new reorder helpers + DnD hook + BoardPage/BoardPane wiring + palette Move Focused Pane Left/Right actions; adjust focus tracking to follow panes by key and avoid SSE-driven focus stealing.
- Add/extend automated coverage: Vitest unit tests, Playwright e2e specs + companion
.spec.md, and backend Go tests; update architecture/UI memory docs and fab change artifacts.
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| fab/changes/260708-rmiq-board-pane-reorder/plan.md | Spec/plan for board pane reorder (DnD + palette parity + backend neighbour lookup fix). |
| fab/changes/260708-rmiq-board-pane-reorder/intake.md | Intake/spec capture for board pane reorder. |
| fab/changes/260708-rmiq-board-pane-reorder/.status.yaml | Change status/metrics for reorder change. |
| fab/changes/260708-rmiq-board-pane-reorder/.history.jsonl | Stage history for reorder change. |
| fab/changes/260708-738w-board-autofit/plan.md | Spec/plan for board autofit toggle (persistence, layout, UI parity, tests). |
| fab/changes/260708-738w-board-autofit/intake.md | Intake/spec capture for board autofit toggle. |
| fab/changes/260708-738w-board-autofit/.status.yaml | Change status/metrics for autofit change. |
| fab/changes/260708-738w-board-autofit/.history.jsonl | Stage history for autofit change. |
| docs/memory/run-kit/ui-patterns.md | Documents new board reorder + autofit UI patterns and invariants. |
| docs/memory/run-kit/log.md | Memory-domain log entry for the new additions. |
| docs/memory/run-kit/index.md | Updates memory index summary to include new board features. |
| docs/memory/run-kit/architecture.md | Documents backend reorder neighbour resolution cross-server aggregation. |
| app/frontend/tests/e2e/board-reorder.spec.ts | E2E coverage for reorder endpoint + SSE echo contract. |
| app/frontend/tests/e2e/board-reorder.spec.md | Companion doc describing what the reorder e2e proves and why. |
| app/frontend/tests/e2e/board-autofit.spec.ts | E2E coverage for autofit layout behavior + persistence + controls parity. |
| app/frontend/tests/e2e/board-autofit.spec.md | Companion doc describing what the autofit e2e proves and why. |
| app/frontend/src/lib/board-reorder.ts | Pure helpers for palette move neighbours + focus-by-key reconciliation + focus gating. |
| app/frontend/src/lib/board-reorder.test.ts | Unit tests for reorder neighbour computation and focus gating helpers. |
| app/frontend/src/hooks/use-pin-actions.ts | Makes reorder rethrow after toast so callers can roll back optimistic UI. |
| app/frontend/src/hooks/use-board-pane-reorder.ts | New DnD reorder hook (custom MIME, override reconcile, cancel-revert, rollback-on-reject). |
| app/frontend/src/hooks/use-board-pane-reorder.test.ts | Unit tests for the board pane reorder hook’s behaviours and edge cases. |
| app/frontend/src/hooks/use-board-autofit.ts | New persisted per-board autofit preference hook (localStorage, per-board key). |
| app/frontend/src/hooks/use-board-autofit.test.ts | Unit tests for autofit persistence/isolation/malformed tolerance. |
| app/frontend/src/contexts/top-bar-slot-context.tsx | Extends TopBar slot type with optional autofit toggle fields. |
| app/frontend/src/components/top-bar.tsx | Adds BoardAutofitToggle rendering in board mode and plumbs new props. |
| app/frontend/src/components/board/board-pane.tsx | Adds autofit sizing behaviour + DnD handle/target props + drag-source dim support. |
| app/frontend/src/components/board/board-page.tsx | Wires autofit state, reorder hook, palette actions, and focus-by-key reconciliation. |
| app/frontend/src/components/board/board-header.tsx | Makes header the drag handle and prevents ✕ from initiating drags. |
| app/frontend/src/app.tsx | Maps new slot fields into TopBar props (autofit toggle wiring). |
| app/backend/api/router.go | Cross-server neighbour key lookup for reorder (lookupNeighbourKeys) + warning logs on per-server failures. |
| app/backend/api/router_test.go | Go tests covering cross-server neighbour lookup and negative cases. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import { useBoardAutofit } from "@/hooks/use-board-autofit"; | ||
| import { usePinActions } from "@/hooks/use-pin-actions"; | ||
| import { useOptimisticAction } from "@/hooks/use-optimistic-action"; | ||
| import { useOptimisticContext } from "@/contexts/optimistic-context"; | ||
| import { useSessionContext } from "@/contexts/session-context"; | ||
| import { useChromeState, useChromeDispatch } from "@/contexts/chrome-context"; | ||
| import { useFocusedTerminal } from "@/contexts/focused-terminal-context"; | ||
| import { useToast } from "@/components/toast"; | ||
| import { BottomBar } from "@/components/bottom-bar"; | ||
| import { Shell } from "@/components/shell/shell"; | ||
| import { Sidebar } from "@/components/sidebar"; | ||
| import { HELP_URL } from "@/components/top-bar"; | ||
| import { useRegisterTopBarSlot } from "@/contexts/top-bar-slot-context"; | ||
| import { createSession, createWindow as createWindowApi, killServer as killServerApi, createServer } from "@/api/client"; | ||
| import { setBoardOrder } from "@/api/boards"; | ||
| import { computeMoveOrder } from "@/lib/palette-move"; | ||
| import { Dialog } from "@/components/dialog"; | ||
| import type { PaletteAction } from "@/components/command-palette"; | ||
| import { ValidBoardName } from "./board-name"; | ||
| import { BoardPane, type BoardPaneHandle } from "./board-pane"; | ||
| import { selectLivePanes } from "./select-live-panes"; | ||
| import { useBoardPaneReorder } from "@/hooks/use-board-pane-reorder"; | ||
| import { computeMoveNeighbors, focusedIndexForKey, shouldFocusPane } from "@/lib/board-reorder"; |
There was a problem hiding this comment.
Deferred — the board-pane-reorder plumbing (useBoardPaneReorder, board-reorder) is a pinned dependency for the autofit change and was intentionally cherry-picked into this branch by the operator, not an accidental co-mingling. Its own tests (use-board-pane-reorder.test.ts, board-reorder.test.ts, board-reorder.spec.ts) ship alongside. Splitting the branch at this stage is out of scope for review-pr; the observation is noted.
| if (effectiveKeys) { | ||
| const byKey = new Map(entries.map((e) => [paneKey(e.server, e.windowId), e])); | ||
| const ordered: BoardEntry[] = []; | ||
| for (const key of effectiveKeys) { | ||
| const e = byKey.get(key); | ||
| if (e) ordered.push(e); | ||
| } | ||
| for (const e of entries) { | ||
| if (!effectiveKeys.includes(paneKey(e.server, e.windowId))) ordered.push(e); | ||
| } |
There was a problem hiding this comment.
Fixed — build a Set from effectiveKeys for the membership check in ordered-entry derivation, replacing the O(n) .includes() per entry with O(1) lookups (overall O(n) instead of O(n²)). Ordering semantics unchanged. (864d878)
Per-board autofit toggle for desktop board panes: panes flex to share row width equally (up to 4 visible, 25% floor beyond), toggled via a top-bar button and Board: Toggle Autofit palette action, persisted per board in localStorage. Preserves hand-tuned per-pane widths when off.
Use Set-based membership checks in useBoardPaneReorder to avoid O(n^2) scans during ordered-entry derivation (dragover) and onDrop reconcile. Ordering semantics unchanged.
9face17 to
e6abbbc
Compare
Meta
738wexcludes
fab/,docs/· generated by fab-kit v2.13.5Pipeline: intake ✓ → apply ✓ → review ✓ → hydrate ✓ → ship → review-pr
Summary
On a desktop board, panes render at explicit pixel widths, leaving unused row width on wide monitors and requiring manual per-pane drag-resizing that doesn't adapt to window resizes or pane pin/unpin. This adds a per-board autofit toggle: a pure-CSS flex layout that shares row width equally across up to 4 visible panes (floored at 25%/280px beyond that), reflowing live on resize, while leaving hand-tuned per-pane widths untouched so toggling off restores them exactly.
Changes
useBoardAutofithook (use-board-autofit.ts), mirroringuse-pane-widths.tsconventions — per-board localStorage keyrunkit:board-autofit:{board}, defaults to off.DesktopRowautofit branch inboard-page.tsx—flex: 1 1 0+ gap-adjustedmin-width: max(280px, calc(25% - 3px))onBoardPanewhen autofit is on; resize handles hidden while on; stored per-pane widths never modified.FixedWidthToggle) plus aBoard: Toggle Autofitcommand-palette action, both wired to the same state via the top-bar slot context.use-board-autofit.test.ts) and a Playwright e2e spec with companion.spec.mdcovering equal-width fill, the 25%/280px floor with scroll, per-board persistence, and handle visibility.