feat(dashboard): convert hand detail panel to drawer variant#3168
Merged
Conversation
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.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
4 tasks
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stacks on top of #3166 to finish the drawer rollout.
HandDetailPanelwas deliberately skipped from the parent PR because it doesn't use the shared<Modal>— it has its own hand-rolledfixed inset-0 ... bg-black/40 backdrop-blur-smwrapper. 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.tsxonly:<Modal isOpen onClose={...} variant="drawer-right" size="2xl" hideCloseButton>. Width matches the originalsm:w-[640px](Tailwind2xl= 672 px). The hero header keeps its own X close button.shrink-0→sticky top-0 bg-surface z-10. The Modal wraps children in a singleoverflow-y-auto, so the previousflex-1 overflow-y-autobody 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
animate-slide-in-right); hands list stays interactive<Modal>instance)Test plan
<sm:viewport: full-screen sheet