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:
parent
dbda70e83b
commit
6722438428
10 changed files with 93 additions and 35 deletions
|
|
@ -34,10 +34,17 @@ const PanelContainer = Paper.withProps({
|
|||
variant: "panel",
|
||||
});
|
||||
|
||||
// Centered in the full-height panel so the empty message sits mid-panel rather
|
||||
// than clinging to the top of an otherwise-empty box (matches LogStreamPanel).
|
||||
const EmptyCenter = Stack.withProps({
|
||||
flex: 1,
|
||||
align: "center",
|
||||
justify: "center",
|
||||
});
|
||||
|
||||
const EmptyState = Text.withProps({
|
||||
c: "dimmed",
|
||||
ta: "center",
|
||||
py: "xl",
|
||||
});
|
||||
|
||||
function formatTitle(count: number): string {
|
||||
|
|
@ -133,7 +140,9 @@ export function NetworkStreamPanel({
|
|||
</Group>
|
||||
|
||||
{!hasResults ? (
|
||||
<EmptyState>No network requests</EmptyState>
|
||||
<EmptyCenter>
|
||||
<EmptyState>No network requests</EmptyState>
|
||||
</EmptyCenter>
|
||||
) : (
|
||||
<EmbeddableScrollArea
|
||||
embedded={embedded}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,6 @@ export interface PromptControlsProps {
|
|||
onSelectPrompt: (name: string) => void;
|
||||
}
|
||||
|
||||
const LIST_MAX_HEIGHT =
|
||||
"calc(100vh - var(--app-shell-header-height, 0px) - var(--mantine-spacing-xl) * 2 - 160px)";
|
||||
|
||||
export function PromptControls({
|
||||
prompts,
|
||||
selectedName,
|
||||
|
|
@ -39,7 +36,10 @@ export function PromptControls({
|
|||
);
|
||||
|
||||
return (
|
||||
<Stack gap="sm">
|
||||
// Fill the full-height `sidebar` Card (a flex column) so the list runs to the
|
||||
// bottom of the card before it scrolls, instead of being capped short by a
|
||||
// fixed max-height. `mih: 0` lets the scroll child shrink and scroll.
|
||||
<Stack gap="sm" flex={1} mih={0}>
|
||||
<Group justify="space-between">
|
||||
<Title order={4}>Prompts</Title>
|
||||
<ListChangedIndicator visible={listChanged} onRefresh={onRefreshList} />
|
||||
|
|
@ -53,7 +53,7 @@ export function PromptControls({
|
|||
searchText ? <ClearButton onClick={() => onSearchChange("")} /> : null
|
||||
}
|
||||
/>
|
||||
<ScrollArea.Autosize viewportRef={viewportRef} mah={LIST_MAX_HEIGHT}>
|
||||
<ScrollArea viewportRef={viewportRef} flex={1} mih={0} type="auto">
|
||||
<Stack gap="xs">
|
||||
{filteredPrompts.map((prompt) => (
|
||||
<PromptListItem
|
||||
|
|
@ -66,7 +66,7 @@ export function PromptControls({
|
|||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</ScrollArea.Autosize>
|
||||
</ScrollArea>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,10 +54,17 @@ const PanelContainer = Paper.withProps({
|
|||
variant: "panel",
|
||||
});
|
||||
|
||||
// Centered in the full-height panel so the empty message sits mid-panel rather
|
||||
// than clinging to the top of an otherwise-empty box (matches LogStreamPanel).
|
||||
const EmptyCenter = Stack.withProps({
|
||||
flex: 1,
|
||||
align: "center",
|
||||
justify: "center",
|
||||
});
|
||||
|
||||
const EmptyState = Text.withProps({
|
||||
c: "dimmed",
|
||||
ta: "center",
|
||||
py: "xl",
|
||||
});
|
||||
|
||||
// The section header is a single "pleat" bar (rounded, with the filter-button
|
||||
|
|
@ -293,7 +300,9 @@ export function ProtocolListPanel({
|
|||
</Group>
|
||||
|
||||
{!hasResults ? (
|
||||
<EmptyState>No request history</EmptyState>
|
||||
<EmptyCenter>
|
||||
<EmptyState>No request history</EmptyState>
|
||||
</EmptyCenter>
|
||||
) : (
|
||||
<EmbeddableScrollArea embedded={embedded} viewportRef={viewportRef}>
|
||||
<Stack gap="md">
|
||||
|
|
|
|||
|
|
@ -17,9 +17,6 @@ export interface ToolControlsProps {
|
|||
onSelectTool: (name: string) => void;
|
||||
}
|
||||
|
||||
const LIST_MAX_HEIGHT =
|
||||
"calc(100vh - var(--app-shell-header-height, 0px) - var(--mantine-spacing-xl) * 2 - 160px)";
|
||||
|
||||
export function ToolControls({
|
||||
tools,
|
||||
selectedName,
|
||||
|
|
@ -40,7 +37,12 @@ export function ToolControls({
|
|||
: tools;
|
||||
|
||||
return (
|
||||
<Stack gap="sm">
|
||||
// Fill the full-height `sidebar` Card (a flex column) so the scroll region
|
||||
// below claims all the remaining space under the fixed title/search — the
|
||||
// list runs to the bottom of the card before it scrolls, instead of being
|
||||
// capped short by a fixed max-height. `mih: 0` lets the scroll child shrink
|
||||
// and scroll rather than overflow the card.
|
||||
<Stack gap="sm" flex={1} mih={0}>
|
||||
<Group justify="space-between">
|
||||
<Title order={4}>Tools</Title>
|
||||
<ListChangedIndicator visible={listChanged} onRefresh={onRefreshList} />
|
||||
|
|
@ -54,7 +56,7 @@ export function ToolControls({
|
|||
searchText ? <ClearButton onClick={() => onSearchChange("")} /> : null
|
||||
}
|
||||
/>
|
||||
<ScrollArea.Autosize viewportRef={viewportRef} mah={LIST_MAX_HEIGHT}>
|
||||
<ScrollArea viewportRef={viewportRef} flex={1} mih={0} type="auto">
|
||||
<Stack gap="xs">
|
||||
{filteredTools.map((tool) => (
|
||||
<ToolListItem
|
||||
|
|
@ -67,7 +69,7 @@ export function ToolControls({
|
|||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</ScrollArea.Autosize>
|
||||
</ScrollArea>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,10 @@ export function ConsoleScreen({
|
|||
}, [entries, filterText, sortDirection]);
|
||||
|
||||
return (
|
||||
<ScreenLayout h={embedded ? "100%" : undefined}>
|
||||
// See LoggingScreen: only override `h` when embedded, so the standalone
|
||||
// screen keeps ScreenLayout's default full-screen height (a `h={undefined}`
|
||||
// would clobber it and collapse an empty screen to its controls' height).
|
||||
<ScreenLayout {...(embedded ? { h: "100%" } : {})}>
|
||||
{embedded ? null : (
|
||||
<Sidebar>
|
||||
<SidebarCard>
|
||||
|
|
|
|||
|
|
@ -78,7 +78,11 @@ export function LoggingScreen({
|
|||
}
|
||||
|
||||
return (
|
||||
<ScreenLayout h={embedded ? "100%" : undefined}>
|
||||
// Embedded fills the monitoring sidebar column (100%); standalone keeps the
|
||||
// ScreenLayout's default full-screen height. Passing `h={undefined}` here
|
||||
// would clobber that default (withProps plain-spreads), collapsing an empty
|
||||
// screen to its controls' height — so only override `h` when embedded.
|
||||
<ScreenLayout {...(embedded ? { h: "100%" } : {})}>
|
||||
{embedded ? null : (
|
||||
<Sidebar>
|
||||
<SidebarCard>
|
||||
|
|
|
|||
|
|
@ -84,7 +84,10 @@ export function NetworkScreen({
|
|||
}
|
||||
|
||||
return (
|
||||
<ScreenLayout h={embedded ? "100%" : undefined}>
|
||||
// See LoggingScreen: only override `h` when embedded, so the standalone
|
||||
// screen keeps ScreenLayout's default full-screen height (a `h={undefined}`
|
||||
// would clobber it and collapse an empty screen to its controls' height).
|
||||
<ScreenLayout {...(embedded ? { h: "100%" } : {})}>
|
||||
{embedded ? null : (
|
||||
<Sidebar>
|
||||
<SidebarCard>
|
||||
|
|
|
|||
|
|
@ -69,9 +69,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 PromptControls' list fills the card and scrolls internally
|
||||
// once it overflows (matching the Resources sidebar).
|
||||
const SidebarCard = Card.withProps({
|
||||
withBorder: true,
|
||||
padding: "lg",
|
||||
variant: "sidebar",
|
||||
});
|
||||
|
||||
const DetailCard = Card.withProps({
|
||||
|
|
|
|||
|
|
@ -104,7 +104,10 @@ export function ProtocolScreen({
|
|||
}, [ui, visibleDirections, onUiChange]);
|
||||
|
||||
return (
|
||||
<ScreenLayout h={embedded ? "100%" : undefined}>
|
||||
// See LoggingScreen: only override `h` when embedded, so the standalone
|
||||
// screen keeps ScreenLayout's default full-screen height (a `h={undefined}`
|
||||
// would clobber it and collapse an empty screen to its controls' height).
|
||||
<ScreenLayout {...(embedded ? { h: "100%" } : {})}>
|
||||
{embedded ? null : (
|
||||
<Sidebar>
|
||||
<SidebarCard>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue