fix: make Reduced motion gate overlays, standardize the tab band, and polish fleet/snapshots (#1504)

* fix: address review feedback on motion, fleet band, and snapshots

- Reduced motion now also skips the animate-ui overlay open/close animations
  (dialog, sheet, popover, dropdown-menu) via a shared useReducedTransition that
  zeroes the transition when reduced motion is active; MotionConfig alone only
  neutralized transform/layout, leaving the opacity/blur fade.
- Fleet tab band: flatten the list's own pill band so the tabs sit in a single
  full-width band instead of a nested second band.
- Fleet Overview: label the node-update button "Node Update"; the add-node
  button becomes a ChartNetwork icon that opens Settings > Nodes (outline style,
  matching the node-update button) instead of the add-node modal.
- Blueprint deployments empty state: drive the serif headings from the theme
  heading-style token so Calm drops the italic.
- Snapshots: move the per-stack Restore onto the stack header row (right side),
  matching the ghost action style.

* feat: standardize the tab band on Security and add a tab hover state

- Apply the Fleet full-width tab band to the Security page (single band, the
  list's pill background flattened so the tabs sit directly in it).
- Add a hover highlight to tabs so the band reads as interactive, not flat;
  the active tab is unaffected.

* fix: drive reduced-motion transitions from the app setting, not the OS query

useReducedTransition called framer-motion's useReducedMotion(), which only
reflects the OS prefers-reduced-motion media query and ignores our MotionConfig
/ appearance toggle, so dialogs kept animating with the setting on. Read the
app's useReducedMotion selector instead. Verified: with the setting on, the
dialog opacity snaps 0->1 in one frame; with it off, it ramps over the spring.

* feat: standardize stack-detail body text to text-xs and add a log expand toggle

- Bump the anatomy panel body text (rows, field values, warnings) and the
  structured log lines from 10/11/12px to text-xs (12px), leaving the uppercase
  kicker labels and the log level/source badges as-is.
- Add an expand control next to the log download button that collapses the
  Command Center so the logs pane fills the left column; toggles back to restore.
This commit is contained in:
Anso
2026-06-28 08:17:34 -04:00
committed by GitHub
parent 04e69021e0
commit d6ce60d280
17 changed files with 167 additions and 91 deletions
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import {
Search, ArrowUpDown, AlertTriangle, Play, Square,
LayoutGrid, Network, SlidersHorizontal, Plus, RefreshCcwDot,
LayoutGrid, Network, SlidersHorizontal, ChartNetwork, RefreshCcwDot,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
@@ -259,25 +259,27 @@ export function OverviewToolbar({
<Button
variant="outline"
size="sm"
className="h-9 w-9 p-0 shrink-0"
className="h-9 gap-2 shrink-0"
onClick={onCheckUpdates}
disabled={checkingUpdates}
title="Check for updates"
aria-label="Check for updates"
title="Check for node updates"
aria-label="Check for node updates"
>
<RefreshCcwDot className={`w-4 h-4 ${checkingUpdates ? 'animate-spin' : ''}`} />
Node Update
</Button>
)}
{onAddNode && (
<Button
variant="outline"
size="sm"
className="h-9 w-9 p-0 shrink-0"
onClick={onAddNode}
title="Add node"
aria-label="Add node"
title="Manage nodes"
aria-label="Manage nodes"
>
<Plus className="w-4 h-4" strokeWidth={1.5} />
<ChartNetwork className="w-4 h-4" strokeWidth={1.5} />
</Button>
)}
</div>
@@ -82,32 +82,32 @@ describe('OverviewToolbar', () => {
expect(screen.queryByText('Tags')).not.toBeInTheDocument();
});
it('renders the Add node button and fires onAddNode when provided', () => {
it('renders the manage-nodes button and fires onAddNode when provided', () => {
const onAddNode = vi.fn();
render(<OverviewToolbar {...props({ onAddNode })} />);
fireEvent.click(screen.getByRole('button', { name: 'Add node' }));
fireEvent.click(screen.getByRole('button', { name: 'Manage nodes' }));
expect(onAddNode).toHaveBeenCalledTimes(1);
});
it('omits the Add node button when onAddNode is not provided', () => {
it('omits the manage-nodes button when onAddNode is not provided', () => {
render(<OverviewToolbar {...props()} />);
expect(screen.queryByRole('button', { name: 'Add node' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Manage nodes' })).not.toBeInTheDocument();
});
it('renders the Check Updates button and fires onCheckUpdates when provided', () => {
const onCheckUpdates = vi.fn();
render(<OverviewToolbar {...props({ onCheckUpdates })} />);
fireEvent.click(screen.getByRole('button', { name: /Check for updates/ }));
fireEvent.click(screen.getByRole('button', { name: /Check for node updates/ }));
expect(onCheckUpdates).toHaveBeenCalledTimes(1);
});
it('omits the Check Updates button when onCheckUpdates is not provided', () => {
render(<OverviewToolbar {...props()} />);
expect(screen.queryByRole('button', { name: /Check for updates/ })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: /Check for node updates/ })).not.toBeInTheDocument();
});
it('disables the Check Updates button while a check is in flight', () => {
render(<OverviewToolbar {...props({ onCheckUpdates: vi.fn(), checkingUpdates: true })} />);
expect(screen.getByRole('button', { name: /Check for updates/ })).toBeDisabled();
expect(screen.getByRole('button', { name: /Check for node updates/ })).toBeDisabled();
});
});