Skip to content

feat(dashboard): convert hand detail panel to drawer variant#3168

Merged
houko merged 6 commits into
mainfrom
feat/hand-detail-drawer
Apr 25, 2026
Merged

feat(dashboard): convert hand detail panel to drawer variant#3168
houko merged 6 commits into
mainfrom
feat/hand-detail-drawer

Conversation

@houko

@houko houko commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Stacks on top of #3166 to finish the drawer rollout. HandDetailPanel was deliberately skipped from the parent PR because it doesn't use the shared <Modal> — it has its own hand-rolled fixed inset-0 ... bg-black/40 backdrop-blur-sm wrapper. Converting it cleanly is a small refactor rather than a one-line variant flip, so it lives in its own PR.

Changes

crates/librefang-api/dashboard/src/pages/HandsPage.tsx only:

  • Replace the bespoke fixed-inset wrapper with <Modal isOpen onClose={...} variant="drawer-right" size="2xl" hideCloseButton>. Width matches the original sm:w-[640px] (Tailwind 2xl = 672 px). The hero header keeps its own X close button.
  • Hero header changes shrink-0sticky top-0 bg-surface z-10. The Modal wraps children in a single overflow-y-auto, so the previous flex-1 overflow-y-auto body wrapper would create a nested scroll — dropped that wrapper, body content now uses just the padding-spacing <div className="px-6 py-5 space-y-5">.
  • TomlViewer (manifest sub-modal) was previously rendered inside the bespoke outer fixed-inset div. Lifts to a sibling of <Modal> inside a fragment so it renders as its own centred modal on top of the drawer when manifest is opened.

Behaviour after

  • Drawer slides in from the right (animate-slide-in-right); hands list stays interactive
  • Click another hand card while drawer is open → drawer content updates in place
  • Esc closes; X closes; clicks outside the drawer pass through to the page
  • Manifest TomlViewer stacks on top as a centred modal (its own <Modal> instance)

Test plan

  • Manual: open hand detail → drawer slides from right; click another hand card → drawer updates
  • Manual: Esc / X both close; rest of page interactive
  • Manual: open manifest viewer (FileText button) → TomlViewer modal stacks on top, closing it returns to drawer
  • Manual <sm: viewport: full-screen sheet
  • CI green

houko added 5 commits April 26, 2026 00:42
The system prompt card had max-h-72 overflow-y-auto when collapsed,
producing a scroll-inside-scroll inside the modal — the modal itself
already scrolls so the inner one was both redundant and annoying.

Switch to overflow-hidden + a fade-out gradient at the bottom edge so
the cut reads as 'click expand to see more' instead of 'we clipped you'.
Expanded state shows the full prompt and falls back to the modal's
single outer scroll.

Also corrects the Tailwind v4 utility name: bg-gradient-to-t →
bg-linear-to-t (v4 renamed the gradient direction utilities).
The 4xl modal forced a tall-narrow shape that made eight inspectable
sections scroll forever, and every "open another agent" needed a
close-then-reopen because the modal occluded the list with a dim
backdrop. Reshape the detail surface as a right-docked inspector
panel:

- Modal gains a `variant` prop. `drawer-right` docks to the right edge
  full-height, drops the dim backdrop, and uses pointer-events-none on
  the outer container so clicks pass through to the agents list. The
  modal contract (centred + dim + click-to-dismiss) is unchanged for
  every other call site.
- AgentsPage detail uses `variant="drawer-right" size="xl"` (576 px on
  sm+, full-screen sheet on mobile). Click a different agent card in
  the list while the drawer is open and the drawer's content updates
  in place — no close + reopen.
- aria-modal=false in drawer mode (the page IS interactive); Esc and
  the explicit close button stay as the dismissal paths because
  click-outside would race with the list-click-to-switch interaction.
- New slide-in-right keyframe + .animate-slide-in-right utility paired
  with the existing apple-ease timing curve so the drawer's entrance
  matches the rest of the dashboard's motion language.
…e panel

The drawer's whole point is that the underlying list stays interactive
— pointer-events-none on the container lets mouse clicks pass through.
But the focus trap was still active, so keyboard users hit Tab inside
the drawer and got bounced back to the first focusable element forever
with no way to reach the agents list short of Esc-then-restart.

Add a fourth `trap` parameter to useFocusTrap (default true so all
existing call sites — ConfirmDialog / ShortcutsHelp / CommandPalette /
Modal — keep their current behaviour) and pass false from Modal when
`variant === "drawer-right"`. Initial focus and focus-restoration on
close still happen; only the Tab-cycling interception is skipped.
Same drawer-right variant rolled out to the other two list+inspect
flows on the dashboard:

- ProvidersPage.ProviderDetailModal — read-only provider inspector
  (auth status, model count, latency, model list). Click another
  provider card while open → drawer's content updates in place.
- SkillsPage.SkillDetailModal — installed-skill inspector with evolve
  panes. Same in-place switching benefit.

Skipped on purpose:
- MarketplaceDetailModal stays modal — install flow is transactional,
  not browse-comparing.
- All Create / Edit / Add form modals (Plugins / Models / Memory /
  Scheduler / McpServers) stay modal — drawer's click-through would
  silently drop in-flight form state.
- HandsPage HandDetailPanel uses its own bespoke JSX rather than the
  shared <Modal>, so converting it to drawer needs a full panel
  rewrite — out of scope here.
Stacks on #3166. HandDetailPanel rolled its own backdrop + inner panel
JSX rather than using the shared <Modal>, so the drawer migration here
is a small refactor rather than a one-line variant flip:

- Replace the bespoke fixed-inset wrapper with <Modal isOpen onClose={...}
  variant="drawer-right" size="2xl" hideCloseButton>. The hero header
  keeps its own X close button (lines up with the existing visual).
- Hero header switches from shrink-0 to sticky top-0 bg-surface z-10 so
  identity + close stay reachable during scroll. Modal wraps children
  in a single overflow-y-auto, so any inner overflow-y-auto becomes a
  nested scroll inside a drawer — drop the body's flex-1 overflow-y-auto
  wrapper accordingly.
- TomlViewer (manifest viewer sub-modal) was previously rendered as a
  sibling of the inner panel inside the outer fixed-inset div. Lifts to
  a sibling of <Modal> inside a fragment so it renders as its own modal
  on top.

The 90% benefit applies here just like agents/providers/skills: with
the dim backdrop gone (drawer mode keeps the hands list interactive),
clicking another hand card while the drawer is open switches its
content in place. Focus trap is already disabled in drawer mode by
the Modal-level fix from the parent PR.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@houko
houko merged commit 1eb5ef5 into main Apr 25, 2026
13 checks passed
@houko
houko deleted the feat/hand-detail-drawer branch April 25, 2026 15:58
@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review no-rust-required This task does not require Rust knowledge size/S 10-49 lines changed and removed ready-for-review PR is ready for maintainer review labels Apr 25, 2026
@houko houko mentioned this pull request Apr 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-rust-required This task does not require Rust knowledge size/S 10-49 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant