A persisted float rect saved at the viewport edge, or on a larger
monitor, passes clampFloatRect (which only keeps 48px reachable so a
live drag may deliberately hang off an edge) and renders the panel as
a 48px sliver on every open. Add containFloatRect, which snaps the
whole window inside the viewport, and an AgentSheet effect that
validates the persisted rect on sheet mount and on viewport resize
and persists the corrected position. The rect is read through a ref
so drag commits do not re-trigger containment: parking the window
half off-screen still works within a session. The live drag path and
clampFloatRect are unchanged, and containFloatRect's output is always
a fixpoint of clampFloatRect, so the render clamp stays a no-op.
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(agent): resizable, undockable assistant panel
User report: the assistant chat sheet sometimes covers the page content
the user is asking about, with no way to resize or move it.
- Docked mode is now drag-resizable from its left edge (380-800px,
clamped so the page keeps a 480px readable column) and the page
reflows beside it via the existing --agent-dock-w reservation.
- Expanded (focus) mode reserves page margin like the compact dock
instead of overlaying up to 1100px of the page.
- New undock toggle turns the sheet into a floating window that can be
dragged by its header and resized from edges/corners, clamped so the
header always stays reachable. Desktop only; mobile keeps the
full-screen sheet.
- Geometry (mode, dock width, float rect) persists per user in
user_preferences.ui_state.agent_panel, server-seeded to avoid a
first-paint jump; the ui-state API schema gains a strict agent_panel
key with nested merge.
- Pure clamp/resize math lives in lib/agent-panel/geometry with unit
tests; drag frames write styles imperatively and commit one
preference update on release.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(agent): address review findings on panel drag, a11y, and persistence
CodeRabbit round 1, all six findings fixed:
- Bind drag listeners to window (plus lostpointercapture) so a failed
pointer capture or mid-drag unmount can never leave the transition
suppression and data-agent-resizing stuck for the session.
- Keyboard resize now steps from the visible width (expandedW in focus
mode) instead of jumping to the persisted dock width.
- The width handle exposes window-splitter semantics: aria-valuenow,
aria-valuemin, aria-valuemax.
- --nav-w is read reactively via a MutationObserver on #dash-shell
instead of computed-style reads in the render body and per drag frame.
- The ui-state POST in updatePanelPrefs gets a 300ms trailing debounce
(state stays immediate) so key auto-repeat cannot produce one
read-merge-write per repeat; pending write flushes on unmount.
- globals.css keeps one :root token block; the agent-resizing rule moved
below it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(agent): filter drag events by pointer id, clear fired debounce timer
CodeRabbit round 2, both findings fixed:
- Window-level drag listeners now ignore events from pointers other than
the initiating one, so a second touch or pen cannot move the panel or
end the first pointer's drag.
- The persist debounce timer ref is nulled when the timer fires, so the
unmount flush only writes genuinely pending values instead of
replaying an already-persisted (possibly stale) geometry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>