[eric] chat: stats density follows the container, the capped note sits on the widget, the reconnect pill yields to a tile, the docked browser grows in

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
ciregenz
2026-09-06 14:03:29 -07:00
co-authored by Claude Fable 5.1
parent ae297de242
commit c1fcfa6797
7 changed files with 75 additions and 17 deletions
@@ -10,6 +10,12 @@ import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { useAppDispatch, useAppSelector } from '@/shared/hooks';
import { hideProviderHealthToast } from '@/shared/state/subscriptionsSlice';
import { openSettingsCard } from '@/shared/state/dashboardLayoutSlice';
import { healthToastAnchor, type HealthToastAnchor } from '@/app/pages/Dashboard/canvas/spawnPillCover';
// Where the toast would sit at its bottom-left home (MUI's 24 px inset), so a toast already moved up is judged on the spot it came from.
function HOME_RECT(r: DOMRect): { x: number; y: number; w: number; h: number } {
return { x: 24, y: window.innerHeight - 24 - r.height, w: r.width, h: r.height };
}
export default function ProviderHealthToast() {
const c = useClaudeTokens();
@@ -17,6 +23,18 @@ export default function ProviderHealthToast() {
const open = useAppSelector((s) => s.subscriptions.healthToastOpen);
const dead = useAppSelector((s) => s.subscriptions.healthDead);
const cliMissing = useAppSelector((s) => s.subscriptions.healthCliMissing);
const tiledZonesKey = useAppSelector((s) => Object.values(s.dashboardLayout.tiledCards).sort().join(','));
const rootRef = React.useRef<HTMLDivElement>(null);
const [anchor, setAnchor] = React.useState<HealthToastAnchor>({ vertical: 'bottom', horizontal: 'left' });
React.useLayoutEffect(() => {
// Measured at its home spot, never at the moved one, or a toast that moved up would never come back down.
const el = rootRef.current;
if (!el || !open) return;
const r = el.getBoundingClientRect();
const zones = tiledZonesKey ? tiledZonesKey.split(',') : [];
setAnchor(healthToastAnchor(zones, anchor.vertical === 'bottom' ? { x: r.x, y: r.y, w: r.width, h: r.height } : HOME_RECT(r)));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tiledZonesKey, open]);
const onReconnect = React.useCallback(() => {
dispatch(openSettingsCard({ tab: 'models' }));
@@ -32,7 +50,8 @@ export default function ProviderHealthToast() {
autoHideDuration={null}
// Clickaway would kill the pill on the user's first canvas click, before they read it; only the X or Reconnect dismisses.
onClose={(event, reason) => { if (reason !== 'clickaway') dispatch(hideProviderHealthToast()); }}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
anchorOrigin={anchor}
ref={rootRef}
>
<Alert
icon={false}
@@ -89,6 +89,9 @@ import { parseMcpToolName, getMcpInputSummary } from '@/shared/mcpToolMeta';
import { isNarration } from './parsing/isNarration';
import { shouldForwardGutterWheel } from './gutterWheel';
import { openMarketplace } from '@/app/pages/Directory/openMarketplace';
import Collapse from '@mui/material/Collapse';
// The docked browser slot used to land at its full height in one frame and re-pin the view 287 px (ENG-468); it grows in over one reveal beat instead.
const BROWSER_SLOT_REVEAL_MS = 200;
const CONTEXT_WINDOWS: Record<string, number> = {
'opus-4-8': 1_000_000,
@@ -2119,7 +2122,7 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
return (
<React.Fragment key={`${item.id}-with-browser`}>
{rendered}
<Box data-browser-slot={id} ref={announceBrowserSlot} sx={browserSlotSx}>{browserSlotBody}</Box>
<Collapse in appear timeout={BROWSER_SLOT_REVEAL_MS} easing="cubic-bezier(0.32, 0.72, 0, 1)"><Box data-browser-slot={id} ref={announceBrowserSlot} sx={browserSlotSx}>{browserSlotBody}</Box></Collapse>
</React.Fragment>
);
}
@@ -2194,7 +2197,7 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
)}
{/* Fallback dock slot for a browser that docked before any browser tool row exists (or whose row was compacted away); once a row appears the slot anchors at it instead (see browserAnchorItemId). The real card overlays this rect geometrically, so the webview never remounts; the mini hides itself when its slot scrolls mostly out of view, since a live webview can't be clipped by the scroller. */}
{hasDockedBrowser && !browserAnchorItemId && (
<Box data-browser-slot={id} ref={announceBrowserSlot} sx={browserSlotSx}>{browserSlotBody}</Box>
<Collapse in appear timeout={BROWSER_SLOT_REVEAL_MS} easing="cubic-bezier(0.32, 0.72, 0, 1)"><Box data-browser-slot={id} ref={announceBrowserSlot} sx={browserSlotSx}>{browserSlotBody}</Box></Collapse>
)}
</Box>
</Box>
@@ -6,7 +6,7 @@ import LinksWidget from './LinksWidget';
import VendoredToolUi from '@toolui/VendoredToolUi';
import type { ShowUiPayload } from './showUiPayload';
import { useOpenUrlInBrowserCard } from './useOpenUrlInBrowserCard';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { useClaudeTokens, useThemeMode } from '@/shared/styles/ThemeContext';
import { ambientShape } from './showUiAmbient';
import { perfBaselineFor } from '@/shared/perfBaseline';
@@ -14,6 +14,7 @@ import { perfBaselineFor } from '@/shared/perfBaseline';
function ShowUiWidgetView({ payload, ambient }: { payload: ShowUiPayload; ambient?: boolean }): React.ReactElement | null {
const openUrl = useOpenUrlInBrowserCard();
const c = useClaudeTokens();
const { mode } = useThemeMode();
if (payload.component === 'weather') return <WeatherWidget props={payload.props} ambient={ambient} />;
if (payload.component === 'plan') return <PlanWidget props={payload.props} />;
if (payload.component === 'stats') return <StatsWidget props={payload.props} />;
@@ -37,10 +38,11 @@ function ShowUiWidgetView({ payload, ambient }: { payload: ShowUiPayload; ambien
const shaped = ambient && !perfBaselineFor('ambient') ? ambientShape(payload.name, raw) : { props: payload.props, note: null, extraProps: {} };
let widget = <VendoredToolUi name={payload.name} props={shaped.props} quietFail={ambient} extraProps={{ ...nav, ...shaped.extraProps }} />;
if (shaped.note) {
// The note sits on the widget's own surface (the tool-ui theme's card colour, tucked under the table's rounded bottom) so it reads as the artifact's footer, not stray canvas text (ENG-474).
widget = (
<div>
<div className={`tool-ui-scope${mode === 'dark' ? ' dark' : ''}`}>
{widget}
<div style={{ fontSize: '0.75rem', color: c.text.secondary, padding: '6px 8px 2px' }}>{shaped.note}</div>
<div className="bg-card text-muted-foreground border border-border border-t-0 rounded-b-xl text-xs" style={{ marginTop: -12, padding: '18px 12px 6px' }}>{shaped.note}</div>
</div>
);
}
@@ -1,6 +1,8 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { coveredByTiledZones, rectsIntersect } from './spawnPillCover';
import fs from 'node:fs';
import path from 'node:path';
import { coveredByTiledZones, healthToastAnchor, rectsIntersect } from './spawnPillCover';
// With a chat tiled to the bottom-left quarter, the floating "Ask me anything" pill sat on top of the
// card's own composer, two inputs on one spot (ENG-469). The pill yields to any tile that covers it.
@@ -28,3 +30,22 @@ test('rectsIntersect is a strict overlap, not a touch', () => {
assert.equal(rectsIntersect({ x: 0, y: 0, w: 10, h: 10 }, { x: 10, y: 0, w: 10, h: 10 }), false);
assert.equal(rectsIntersect({ x: 0, y: 0, w: 10, h: 10 }, { x: 9, y: 9, w: 10, h: 10 }), true);
});
test('the reconnect pill moves to the top centre only while a tile covers its bottom-left home, and never hides', () => {
const home = { x: 24, y: 840, w: 420, h: 48 };
const rects: Record<string, { x: number; y: number; w: number; h: number }> = { bl: { x: 0, y: 450, w: 700, h: 450 }, tr: { x: 700, y: 0, w: 700, h: 450 } };
const rectFor = (z: string) => rects[z] ?? null;
assert.deepEqual(healthToastAnchor([], home, rectFor), { vertical: 'bottom', horizontal: 'left' });
assert.deepEqual(healthToastAnchor(['tr'], home, rectFor), { vertical: 'bottom', horizontal: 'left' });
assert.deepEqual(healthToastAnchor(['bl'], home, rectFor), { vertical: 'top', horizontal: 'center' });
assert.deepEqual(healthToastAnchor(['bl'], null, rectFor), { vertical: 'bottom', horizontal: 'left' }, 'unmeasured stays home');
});
test('the toast wires the anchor to the tiled zones, and the capped-table note lives on the widget surface', () => {
const toast = fs.readFileSync(path.join(process.cwd(), 'src/app/components/overlays/ProviderHealthToast.tsx'), 'utf8');
assert.ok(toast.includes('anchorOrigin={anchor}') && toast.includes('healthToastAnchor('));
const view = fs.readFileSync(path.join(process.cwd(), 'src/app/pages/AgentChat/tool-ui/ShowUiWidgetView.tsx'), 'utf8');
assert.ok(view.includes('className="bg-card text-muted-foreground border border-border border-t-0 rounded-b-xl text-xs"'), 'ENG-474: the note is painted on the tool-ui card surface');
const chat = fs.readFileSync(path.join(process.cwd(), 'src/app/pages/AgentChat/AgentChat.tsx'), 'utf8');
assert.equal((chat.match(/<Collapse in appear timeout=\{BROWSER_SLOT_REVEAL_MS\}/g) || []).length, 2, 'ENG-468: both browser-slot mounts grow in');
});
@@ -12,3 +12,11 @@ export function coveredByTiledZones(zones: string[], pill: ZoneRect, rectFor: (z
}
return false;
}
export type HealthToastAnchor = { vertical: 'bottom' | 'top'; horizontal: 'left' | 'center' };
/** The reconnect pill must never hide (a covered composer is row 7, a hidden reconnect is row 5), so when a tile covers its bottom-left spot it moves to the top centre, which no quarter or half tile owns together with the composer. */
export function healthToastAnchor(zones: string[], toastRect: ZoneRect | null, rectFor: (zone: string) => ZoneRect | null = zoneRect): HealthToastAnchor {
if (toastRect && coveredByTiledZones(zones, toastRect, rectFor)) return { vertical: 'top', horizontal: 'center' };
return { vertical: 'bottom', horizontal: 'left' };
}
@@ -176,7 +176,7 @@ function StatCard({
<div
className={cn(
"relative flex flex-col gap-1",
compact ? "min-h-16 px-3" : "min-h-28 px-6",
compact ? "min-h-16 px-3" : "min-h-16 px-3 @[440px]:min-h-28 @[440px]:px-6",
isSingle ? "justify-center" : "justify-end",
)}
>
@@ -203,7 +203,7 @@ function StatCard({
<span
className={cn(
"font-light tracking-normal",
isSingle ? "text-5xl" : compact ? "text-xl" : "text-3xl",
isSingle ? "text-5xl" : compact ? "text-xl" : "text-xl @[440px]:text-3xl",
)}
>
<FormattedValue
@@ -257,11 +257,13 @@ export function StatsDisplay({
)}
<CardContent className="@container overflow-hidden p-0">
<div
className="grid @[440px]:-ml-px @[440px]:-mt-px"
style={{
// Under a collapsed pill (compact) three cells must sit side by side at ~380 px; the page-sized minimum stacked them into a 336 px column.
gridTemplateColumns: compact ? "repeat(auto-fit, minmax(110px, 1fr))" : "repeat(auto-fit, minmax(220px, 1fr))",
}}
// A chat column or a pill is narrower than the page these were drawn for: cells sit side by side from ~330 px (110 px each) and take the page's 220 px minimum only past 660 px, so a quarter-tiled chat no longer stacks three numbers into a 336 px column (ENG-469).
className={cn(
"grid @[440px]:-ml-px @[440px]:-mt-px",
compact
? "grid-cols-[repeat(auto-fit,minmax(110px,1fr))]"
: "grid-cols-[repeat(auto-fit,minmax(110px,1fr))] @[660px]:grid-cols-[repeat(auto-fit,minmax(220px,1fr))]",
)}
>
{stats.map((stat, index) => (
<div
@@ -5,10 +5,13 @@ import path from 'node:path';
// Under a collapsed pill the vendored stats card stacked its three cells into a 336 px column (its grid
// minimum is 220 px per cell and the pill is 380 px wide). Compact density puts them side by side.
test('compact density narrows the grid minimum and the cell so three stats fit a pill', () => {
// A quarter-tiled chat column is under 440 px too, so the same density follows the CONTAINER width in the chat: dense
// cells and a 110 px minimum by default, the page's 220 px minimum only past 660 px (ENG-469 item 3).
test('density follows the container: dense by default, page density only in a wide container, compact forces dense', () => {
const src = fs.readFileSync(path.join(process.cwd(), 'src/toolui/components/stats-display/stats-display.tsx'), 'utf8');
assert.ok(src.includes('compact ? "repeat(auto-fit, minmax(110px, 1fr))" : "repeat(auto-fit, minmax(220px, 1fr))"'));
assert.ok(src.includes('compact ? "min-h-16 px-3" : "min-h-28 px-6"'));
assert.ok(src.includes('? "grid-cols-[repeat(auto-fit,minmax(110px,1fr))]"'));
assert.ok(src.includes(': "grid-cols-[repeat(auto-fit,minmax(110px,1fr))] @[660px]:grid-cols-[repeat(auto-fit,minmax(220px,1fr))]"'));
assert.ok(src.includes('compact ? "min-h-16 px-3" : "min-h-16 px-3 @[440px]:min-h-28 @[440px]:px-6"'));
assert.ok(src.includes('compact={compact}'), 'the density reaches every cell');
assert.ok(src.includes('compact ? "min-w-0" : "min-w-80"'), 'the 320 px floor would overflow the pill');
});