Skip to content

refactor(outline): extract resize-handle into a sibling module - #306

Merged
mtskf merged 6 commits into
mainfrom
chore/split-outline-panel-keyboard-resize
Jul 31, 2026
Merged

refactor(outline): extract resize-handle into a sibling module#306
mtskf merged 6 commits into
mainfrom
chore/split-outline-panel-keyboard-resize

Conversation

@mtskf

@mtskf mtskf commented Jul 31, 2026

Copy link
Copy Markdown
Owner

What

Extracts the outline sidebar's runtime resize concern out of OutlinePanel into a self-contained sibling module, src/webview/cm/outline/resize-handle.ts, mirroring the existing settings-popover.ts / build-outline.ts / icons.ts / update-config-sink.ts extractions.

Addresses the "Split outline-panel.ts" cleanup TODO (from PR #245 review). The TODO named two blocks — keyboard-tree nav and the resize handle; this PR ships the resize handle and defers the keyboard-tree block as a designed follow-up (see below).

Why the resize handle, not both

  • Resize handle is cleanly severable. Its state (resizing / resizePointerId / resizeMoved), the --quoll-outline-sidebar-width writes, and the persisted-width restore/seed are touched by resize methods only. It reaches back into the panel through a small typed deps set (hover-close scheduling, focus-out dismiss, Escape-close) and exposes el + isResizing() + destroy().
  • Keyboard-tree nav is entangled. It shares four mutable fields — rows, collapsedFroms, tabbableFrom, pendingFocusedFrom — with renderList / updateActive / toggleCollapse / rebuild / update(). A clean extraction needs a designed ownership boundary for that shared state, not a naive method-move; that belongs in its own PR (follow-up TODO added).

Residual size

outline-panel.ts was 1470 lines (the TODO's cited 1049 predated later a11y work), so neither block — nor both — gets it "meaningfully under 500". The Done-when's "documented rationale for the residual size" is the operative branch. This PR takes it from 1470 → 1266 (−204); new module is 291 lines.

Notable

While wiring, found a fifth panel↔resize coupling point the initial analysis missed: scheduleClose() reads the drag state to no-op mid-drag (live in happy-dom, where setPointerCapture is a no-op so pointer boundary events still fire during a drag). Preserved it behaviour-identically via a read-only isResizing() on the handle rather than dropping the guard.

Verification

  • Public import path preserved: DEFAULT_WIDTH_PX is re-exported from outline-panel.ts (a styles.css parity contract test imports it there).
  • No behaviour change — the full outline test suite is the contract net:
    • pnpm compile (4 tsconfigs) ✅
    • pnpm build
    • pnpm lint (changed files) ✅
    • pnpm test:unit ✅ 4154 passed
    • pnpm test:browser ✅ 35 passed (incl. real-Chromium resize-drag geometry)

🤖 Generated with Claude Code

mtskf added 6 commits July 31, 2026 23:16
Carve the runtime window-splitter resize concern out of OutlinePanel into a
self-contained createResizeHandle() factory (resize-handle.ts), mirroring the
settings-popover.ts / build-outline.ts extractions. The handle owns its drag
state, width-clamp math, persisted-width restore/seed, and every
--quoll-outline-sidebar-width write; it reaches back into the panel only through
a small typed deps set (hover-close scheduling, focus-out dismiss, Escape-close)
and exposes el + isResizing() + destroy(). No behaviour change — the full
outline test suite stays green. outline-panel.ts drops ~200 lines.
Add a behaviour test asserting a boundary pointerleave during an active
(unpinned) drag does not close the overlay — pinning the new isResizing()
cross-module seam (scheduleClose's mid-drag guard). Verified non-vacuous:
stripping the isResizing() term from scheduleClose turns this test red.
The extracted module restated the same facts in its file header and two
in-body blocks. Trim the in-body restatements to their location-specific
content; the file header is the single authoritative description. No code change.
…ess arrow wrappers)

The module's own pointer/keydown handlers are free functions that never touch
`this`, so the `(e) => fn(e)` forwarding wrappers (a carryover from the class
form) are pure indirection. Bind the named functions directly. No behaviour
change; the deps-callback listeners stay as explicit arrows by intent.
…Index

The comment de-dup over-trimmed and dropped the one non-duplicated fact
explaining why el.tabIndex=0 is a safe permanent tab stop: CSS display:none
evicts the closed handle from the tab order, mirroring the inert sidebar.
@mtskf
mtskf merged commit d2b3305 into main Jul 31, 2026
1 check passed
@mtskf
mtskf deleted the chore/split-outline-panel-keyboard-resize branch July 31, 2026 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant