# Quality Checklist: Sidebar Separator Cursor Fixes

**Change**: 260419-9ufu-sidebar-separator-cursor-fixes
**Generated**: 2026-04-19
**Spec**: `spec.md`

<!--
  AUTO-GENERATED by /fab-fff at the tasks stage.

  This checklist validates that the IMPLEMENTATION matches the SPEC.
  Items derive from: spec.md requirements, key scenarios, edge cases, and
  fab/project/code-quality.md principles/anti-patterns relevant to frontend UI polish.
-->

## Functional Completeness
<!-- Every requirement in spec.md has working implementation -->
- [ ] CHK-001 Body Cursor Override During Drag: Horizontal handler writes `document.body.style.cursor = "row-resize"` at pointerdown start in `onHandlePointerDown` (~`collapsible-panel.tsx:200`) and clears to `""` in `onPointerUp` (~line 183).
- [ ] CHK-002 Body Cursor Override During Drag: Vertical handler writes `document.body.style.cursor = "col-resize"` after `isDraggingRef.current = true` in `handleDragStart` (~`app.tsx:198`) and clears to `""` in `handleEnd` (~line 212).
- [ ] CHK-003 Preserve `preventDefault`: Existing `e.preventDefault()` call on horizontal pointerdown is retained unchanged — text selection is still blocked during drag.
- [ ] CHK-004 Unmount Cleanup of Body Cursor: `CollapsiblePanel` unmount cleanup `useEffect` (~`collapsible-panel.tsx:218–223`) assigns `document.body.style.cursor = ""` alongside existing listener cleanup.
- [ ] CHK-005 Horizontal Cursor Class: `collapsible-panel.tsx` line 313 uses `cursor-row-resize` (replacing `cursor-ns-resize`); no other cursor class on this handle.
- [ ] CHK-006 Vertical Cursor Class Retained: `app.tsx` vertical separator keeps `cursor-col-resize` — unchanged from today.
- [ ] CHK-007 Vertical Hover Opacity: `app.tsx` line 898 uses `hover:bg-text-secondary` (the `/40` opacity suffix has been removed).
- [ ] CHK-008 Corner Element Rendering: `CollapsiblePanel` accepts optional `onCornerPointerDown?: (e: React.PointerEvent<HTMLDivElement>) => void`. When supplied AND `showDragHandle` is true, the handle block renders as a flex row with a `flex-1` handle plus a `w-[7px] h-[3px]` corner (`bg-border hover:bg-text-secondary transition-colors select-none cursor-nwse-resize`).
- [ ] CHK-009 Corner Invocation Order: Corner `onPointerDown` invokes exactly in order: (1) `onHandlePointerDown(e)`, (2) `onCornerPointerDown(e)`, (3) `document.body.style.cursor = "nwse-resize"` — verified in code.
- [ ] CHK-010 Default Render Preserved: When `onCornerPointerDown` is not supplied, `CollapsiblePanel` renders the handle exactly as before (single `<div>`, no flex row, no corner) — byte-equivalent to pre-change for existing call sites.
- [ ] CHK-011 Prop Threading (Sidebar): `SidebarProps` in `components/sidebar/index.tsx` gains optional `onSidebarResizeStart?: (e: React.PointerEvent<HTMLDivElement>) => void`; destructured and forwarded to `<ServerPanel>` (~line 494).
- [ ] CHK-012 Prop Threading (ServerPanel): `ServerPanelProps` in `components/sidebar/server-panel.tsx` gains optional `onSidebarResizeStart`; destructured and passed to `<CollapsiblePanel>` as `onCornerPointerDown={onSidebarResizeStart}` (~line 101).
- [ ] CHK-013 Desktop Sidebar Wiring: `app.tsx` desktop sidebar JSX (~line 880) passes `onSidebarResizeStart={(e) => handleDragStart(e.clientX)}` to `<Sidebar>`.
- [ ] CHK-014 Mobile Drawer NOT Wired: `app.tsx` mobile drawer `<Sidebar>` (~line 978) does NOT receive `onSidebarResizeStart` — mobile drawer is unaffected.

## Behavioral Correctness
<!-- Changed requirements behave as specified, not as before -->
- [ ] CHK-015 Horizontal cursor vocabulary change: On hover of the horizontal handle, the OS cursor displays `row-resize` (double-arrow with middle bar), NOT `ns-resize` (plain double-arrow) as before.
- [ ] CHK-016 Vertical hover brightness change: Hovering the vertical handle now shows full-opacity `text-secondary` fill — perceptibly brighter than the previous 40% opacity.
- [ ] CHK-017 Post-drag hover regression fixed: After completing a drag on either separator and releasing, hovering the handle again still changes the cursor to `row-resize` / `col-resize` (no loss of cursor feedback after first drag).

## Removal Verification
<!-- Every deprecated requirement is actually gone -->
- [ ] CHK-018 `cursor-ns-resize` removed: Grep `collapsible-panel.tsx` — no remaining occurrences of `cursor-ns-resize` on the horizontal drag handle.
- [ ] CHK-019 `/40` opacity suffix removed: Grep `app.tsx` vertical separator line — no remaining `hover:bg-text-secondary/40`.

## Scenario Coverage
<!-- Key scenarios from spec.md have been exercised -->
- [ ] CHK-020 Scenario: Horizontal cursor survives a drag — after one full pointerdown/move/up cycle, re-hovering the horizontal handle still shows `row-resize` (Playwright or manual).
- [ ] CHK-021 Scenario: Vertical cursor survives a drag — after one full mousedown-or-touchstart/move/up cycle, re-hovering the vertical handle still shows `col-resize`.
- [ ] CHK-022 Scenario: Cursor persists while dragging off the handle — mid-drag, moving the pointer anywhere over `document.body` keeps the drag cursor visible (no revert to default).
- [ ] CHK-023 Scenario: Cursor cleared on pointerup — after any drag ends, hovering non-handle UI shows the default cursor (no stuck `row-resize` / `col-resize` / `nwse-resize`).
- [ ] CHK-024 Scenario: Text selection suppressed during horizontal drag — no page-level text selection occurs during the drag gesture.
- [ ] CHK-025 Scenario: Visual parity between separators on hover — both separators show separator-style cursors (double-arrow + middle bar); no plain `ns-resize` / `ew-resize` visible on either.
- [ ] CHK-026 Scenario: Brightness parity on hover — both separators' hover highlights are visually equivalent in brightness.
- [ ] CHK-027 Scenario: Corner visible on desktop with server panel open — corner element renders at the bottom-right edge of the server panel, flush against the horizontal handle row.
- [ ] CHK-028 Scenario: Corner hides when server panel collapses — collapsing the server panel removes the horizontal handle AND the corner (coupled via `showDragHandle`).
- [ ] CHK-029 Scenario: Corner hidden on mobile — on a mobile viewport (< 768px), neither drag handles nor the corner render; mobile drawer layout unchanged.
- [ ] CHK-030 Scenario: Corner cursor on hover — hovering the corner (without pressing) shows `nwse-resize` and the corner background transitions to `bg-text-secondary`.
- [ ] CHK-031 Scenario: Diagonal drag resizes both axes — pointerdown on corner + diagonal drag adjusts server-panel height (via `clientY`) AND sidebar width (via `clientX`) simultaneously; cursor shows `nwse-resize` throughout.
- [ ] CHK-032 Scenario: Corner cursor overrides axis-specific writes — mid corner-drag, `document.body.style.cursor` reads `nwse-resize` (not `row-resize` or `col-resize`).
- [ ] CHK-033 Scenario: Corner drag ends cleanly on pointerup — both horizontal and vertical cleanup paths fire independently; `document.body.style.cursor` clears to `""`.
- [ ] CHK-034 Scenario: Default render path preserved for other call sites — `WindowPanel`, `HostPanel`, and mobile drawer `<Sidebar>` all render `CollapsiblePanel` without a flex wrapper or corner (no visible diff).
- [ ] CHK-035 Scenario: `handleDragStart` receives `clientX` — the `(e) => handleDragStart(e.clientX)` adapter correctly passes the `PointerEvent`'s `clientX` into the existing vertical-drag entry point.

## Edge Cases & Error Handling
<!-- Error states, boundary conditions, failure modes -->
- [ ] CHK-036 Component unmounts mid-drag: If `CollapsiblePanel` unmounts while a drag is active (navigation, hot-reload, error boundary), the unmount cleanup clears `document.body.style.cursor`; no leaked cursor on remount.
- [ ] CHK-037 Partial corner-handler failure tolerance: If the horizontal handler's pointerdown throws, the vertical handler would not start — verified that both sub-calls are simple synchronous ref assignments (near-zero throw risk) and the order is documented.
- [ ] CHK-038 Corner pixel alignment at boundaries: At `1024px+` viewports, the `7px × 3px` corner does not overlap the vertical 5px handle unnaturally and does not create a visible gap (verify in Playwright).
- [ ] CHK-039 Hover scope on corner: Hovering the corner lights up only the corner (not both handles) — correct scope for the `hover:bg-text-secondary` treatment.

## Code Quality
<!-- One item per relevant code-quality principle + relevant anti-pattern, plus baseline items -->
- [ ] CHK-040 Pattern consistency: New code (prop naming, destructuring, optional-prop threading, JSX structure) follows the existing conventions in `sidebar/` components.
- [ ] CHK-041 No unnecessary duplication: Corner reuses existing handle styling tokens (`bg-border hover:bg-text-secondary transition-colors select-none`) rather than introducing parallel classes; corner invocation reuses `onHandlePointerDown` and the existing `handleDragStart` — no forked drag logic.
- [ ] CHK-042 Readability over cleverness: Flex-row conditional render (corner present vs absent) is implemented with a straightforward `if`/ternary branch, not a clever higher-order abstraction or context.
- [ ] CHK-043 Type narrowing over `as` casts: New callbacks typed as `React.PointerEvent<HTMLDivElement>` without `as` coercions; no new `any` introduced in the prop chain.
- [ ] CHK-044 No in-memory caches: The change derives state only from existing refs, props, and DOM — no new module-level or component-level caches introduced.
- [ ] CHK-045 Tests for changed behavior: Existing `collapsible-panel.test.tsx` still passes on the default render path; if the corner branch is non-trivial, a new unit test covers it (per T009) — or corner behavior is covered by Playwright.
- [ ] CHK-046 Playwright verification run on desktop: `just pw test <name>` or `just test-e2e` exercises the nine checkpoints from the spec's Testing section on ≥1024px viewport; never invoked via `npx playwright test` directly.
- [ ] CHK-047 No magic strings/numbers without context: Cursor strings (`"row-resize"`, `"col-resize"`, `"nwse-resize"`) and corner dimensions (`w-[7px] h-[3px]`) are self-descriptive in context; no unexplained numeric literals.
- [ ] CHK-048 No polling from client / no database imports: Change touches frontend UI only — no new `setInterval`/`fetch` polling, no ORM/DB imports (not expected for this scope, but verified).
- [ ] CHK-049 No new routes added without spec justification: Change does not add any routes (constitution IV — Minimal Surface Area).
- [ ] CHK-050 Go backend verification (`go test ./...` under `app/backend/`) passes — no backend changes, but verify the repo still builds clean.
- [ ] CHK-051 Frontend type check (`npx tsc --noEmit` under `app/frontend/`) passes — new optional props typed correctly across all three components.
- [ ] CHK-052 Production build (`just build`) succeeds — no type or build regressions introduced by the optional-prop additions.

## Notes

- Check items as you review: `- [x]`
- All items must pass before `/fab-continue` (hydrate)
- If an item is not applicable, mark checked and prefix with **N/A**: `- [x] CHK-### **N/A**: {reason}`
- Security category omitted — no authentication, authorization, exec, WebSocket, or SSE surface touched by this change (frontend presentation polish only).

<!-- Migrated to plan.md on 2026-05-29 — safe to delete. -->
