fix(web): make sidebar lists, monitor panels, and Tools placeholder fill height (#1663) (#1664)

Three related "fill the available height" layout fixes to the connected screens:

- Tools & Prompts sidebars: replace the fixed magic max-height on ToolControls/
  PromptControls with the Resources pattern — sidebar Card `variant="sidebar"`
  (full-height flex column) + `Stack flex={1} mih={0}` + a flex-filling
  ScrollArea — so the list runs to the bottom of the card before scrolling.
- Logs/Console/Protocol/Network standalone screens: only override ScreenLayout
  `h` when embedded. Passing `h={undefined}` clobbered the default
  `h: calc(100dvh - header)` (withProps plain-spreads), collapsing an empty
  screen to its controls' height. Also center the Network/Protocol empty states.
- Tools placeholder: render the "Select a tool" empty state in a full-height
  `DetailCard flex={1}` (like Prompts/Resources) instead of the content-sized
  preview card, so it fills the pane.


Claude-Session: https://claude.ai/code/session_015sMW1YZXkM7fDkdPtHpE95

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cliff Hall 2026-07-11 18:45:50 -04:00 • committed by GitHub
commit 6722438428
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -79,9 +79,13 @@ const Sidebar = Stack.withProps({
flex: "0 0 auto",
});
// `sidebar` variant makes the card a full-height flex column capped at the
// screen height, so ToolControls' list fills the card and scrolls internally
// once it overflows (matching the Resources sidebar). (#1417)
const SidebarCard = Card.withProps({
withBorder: true,
padding: "lg",
variant: "sidebar",
});
// Column wrapper: stretches to the screen's available height (capped by the
@ -102,6 +106,15 @@ const ContentCard = Card.withProps({
variant: "preview",
});
// Full-height card for the empty placeholder (used with `flex={1}`) so it fills
// the screen height like the Prompts/Resources placeholders, rather than
// shrinking to its text. The result/detail states keep the content-sized
// `ContentCard` (their inner ScrollArea handles overflow).
const DetailCard = Card.withProps({
withBorder: true,
padding: "lg",
});
const EmptyState = Text.withProps({
c: "dimmed",
ta: "center",
@ -160,21 +173,25 @@ export function ToolsScreen({
</SidebarCard>
</Sidebar>
<ContentPane mah={SCROLL_MAX_HEIGHT}>
<ContentCard>
{callState?.result ? (
// Results replace the input form while present, and the panel's
// top-left X dismisses them back to the form (#1661) — the Prompts
// screen pattern. `formValues` live in the lifted UI state, so the
// form is restored intact for a re-run. A call in flight sets a
// `pending` state with no `result` (App.tsx), so the executing form
// (progress + cancel) shows until the result lands.
{callState?.result ? (
// Results replace the input form while present, and the panel's top-left
// X dismisses them back to the form (#1661) — the Prompts screen pattern.
// `formValues` live in the lifted UI state, so the form is restored
// intact for a re-run. A call in flight sets a `pending` state with no
// `result` (App.tsx), so the executing form (progress + cancel) shows
// until the result lands.
<ContentPane mah={SCROLL_MAX_HEIGHT}>
<ContentCard>
<ToolResultPanel
result={callState.result}
onClear={() => onClearResult?.()}
onReadResource={onReadResource}
/>
) : selectedTool ? (
</ContentCard>
</ContentPane>
) : selectedTool ? (
<ContentPane mah={SCROLL_MAX_HEIGHT}>
<ContentCard>
<ToolDetailPanel
tool={selectedTool}
formValues={formValues}
@ -193,11 +210,15 @@ export function ToolsScreen({
}
onCancel={() => onCancelCall?.()}
/>
) : (
<EmptyState>Select a tool to view details</EmptyState>
)}
</ContentCard>
</ContentPane>
</ContentCard>
</ContentPane>
) : (
// Empty placeholder fills the full screen height (like Prompts/Resources)
// rather than shrinking to its text.
<DetailCard flex={1}>
<EmptyState>Select a tool to view details</EmptyState>
</DetailCard>
)}
</ScreenLayout>
);
}