mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-06 00:47:52 +00:00
3ec0a45ff0
* feat(frontend): add SystemSheet primitive and migrate mesh sheets to §9.11 chrome
DESIGN.md §9.11 codifies one canonical right-side detail-sheet shell (cyan
rail, mono crumb, italic serif name, mono meta, ESC chip + close glyph,
fixed three-slot toolbar, cyan-underline tabs, ScrollArea body, footer
freshness band). Today the 16 sheet consumers each render their own
header chrome with stock shadcn SheetHeader/SheetTitle.
Introduce <SystemSheet> + <SheetSection> in
frontend/src/components/ui/system-sheet.tsx, composing the existing
<Sheet>/<SheetContent> primitive. Add a backward-compatible showClose
prop to SheetContent so SystemSheet can render its own ESC chip + close
glyph instead of the stock cyan square close.
Migrate the four mesh sheets as the first batch:
* MeshActivitySheet: crumb Fleet › Mesh › Activity, footer freshness from
most-recent event timestamp.
* MeshOptInSheet: crumb Fleet › Mesh › {nodeName}, meta of opted-in
count, drops the redundant bottom Close button (ESC chip dismisses).
* MeshDiagnosticsSheet: removes the icon-prefixed title (forbidden by
§9.11), lifts Refresh/Restart buttons from the body into the toolbar
band, three SheetSection blocks for sidecar status, streams, cache.
* MeshRouteDetailSheet: adds Overview/Events/Raw tabs, lifts Test probe
into the toolbar primary slot, footer surfaces last probe latency.
* feat(frontend): migrate security and scheduled sheets to §9.11 chrome
PR 2 of the System Sheet (§9.11) rollout, stacked on the SystemSheet
primitive PR. Migrates six more sheet consumers and merges the Stack
alert + auto-heal sheets into a single tabbed sheet per audit §17.
Sheets migrated:
* NodeUpdatesSheet: crumb Fleet > Updates, Recheck primary, Update-all
secondary when applicable. Stat tiles and node rows lose their
card-in-sheet wrapping (forbidden by §9.11) for flat dividers.
* StackAlertSheet (now the merged stack monitor): tabs Alerts /
Auto-heal, crumb Stack > {name} > Monitor. New initialTab prop lets
callers open directly to either tab. Auto-heal tab is hidden entirely
for Community-tier users (matches the existing tier-gating on the
context menu trigger and keyboard shortcut).
* StackAutoHealSheet.tsx: deleted. Its body became the Auto-heal tab
inside the merged sheet.
* VulnerabilityScanSheet: removed the icon-prefixed title (forbidden by
§9.11). Re-scan, Compare, CSV, SARIF lifted from body cards into the
toolbar band. Tabs Vulnerabilities | Secrets | Misconfigs (counts on
the tab labels). SBOM dropdown stays in the body summary section
pending a primitive enhancement for dropdown-attached toolbar actions.
* ScanComparisonSheet: crumb Security > Scans > Compare, name Diff,
meta with the +added/-removed delta.
* SecurityHistoryView: the inner sheet only. Crumb Security > Scan
history. Compare (paid + 2 selected) and Refresh in toolbar.
* ScheduledOperationsView run-history sheet (lines ~847-935 only):
crumb Schedules > {taskName} > Runs, Download CSV in toolbar, footer
surfaces next-run timestamp.
Hook refactor:
* useOverlayState replaces three separate state vars (alertSheetOpen,
alertSheetStack, autoHealStackName) with one stackMonitor object
carrying { stackName, tab }. New helpers openAlertSheet(stackName),
openAutoHeal(stackName), closeStackMonitor(). Tests rewritten and
pass (11/11).
* useSidebarContextMenu and ShellOverlays updated for the new API. The
three other call sites (useStackMenuItems, useStackKeyboardShortcuts)
already use openAlertSheet/openAutoHeal and need no change.
96 lines
4.4 KiB
TypeScript
96 lines
4.4 KiB
TypeScript
import { renderHook, act } from '@testing-library/react';
|
|
import { describe, it, expect } from 'vitest';
|
|
import { useOverlayState } from './useOverlayState';
|
|
|
|
describe('useOverlayState', () => {
|
|
it('initialises with all overlays closed and null/empty data', () => {
|
|
const { result } = renderHook(() => useOverlayState());
|
|
expect(result.current.createDialogOpen).toBe(false);
|
|
expect(result.current.deleteDialogOpen).toBe(false);
|
|
expect(result.current.stackToDelete).toBeNull();
|
|
expect(result.current.pendingUnsavedLoad).toBeNull();
|
|
expect(result.current.pendingUnsavedNode).toBeNull();
|
|
expect(result.current.bashModalOpen).toBe(false);
|
|
expect(result.current.selectedContainer).toBeNull();
|
|
expect(result.current.logViewerOpen).toBe(false);
|
|
expect(result.current.logContainer).toBeNull();
|
|
expect(result.current.stackMonitor).toBeNull();
|
|
expect(result.current.policyBlock).toBeNull();
|
|
expect(result.current.policyBypassing).toBe(false);
|
|
expect(result.current.stackMisconfigScanId).toBeNull();
|
|
expect(result.current.diffPreview).toBeNull();
|
|
expect(result.current.diffPreviewConfirming).toBe(false);
|
|
});
|
|
|
|
it('openBashModal sets open flag and container object', () => {
|
|
const { result } = renderHook(() => useOverlayState());
|
|
act(() => result.current.openBashModal({ id: 'abc', name: 'my-container' }));
|
|
expect(result.current.bashModalOpen).toBe(true);
|
|
expect(result.current.selectedContainer).toEqual({ id: 'abc', name: 'my-container' });
|
|
});
|
|
|
|
it('closeBashModal resets bash state', () => {
|
|
const { result } = renderHook(() => useOverlayState());
|
|
act(() => result.current.openBashModal({ id: 'abc', name: 'my-container' }));
|
|
act(() => result.current.closeBashModal());
|
|
expect(result.current.bashModalOpen).toBe(false);
|
|
expect(result.current.selectedContainer).toBeNull();
|
|
});
|
|
|
|
it('openDeleteDialog sets open flag and stack name', () => {
|
|
const { result } = renderHook(() => useOverlayState());
|
|
act(() => result.current.openDeleteDialog('my-stack'));
|
|
expect(result.current.deleteDialogOpen).toBe(true);
|
|
expect(result.current.stackToDelete).toBe('my-stack');
|
|
});
|
|
|
|
it('closeDeleteDialog resets delete state', () => {
|
|
const { result } = renderHook(() => useOverlayState());
|
|
act(() => result.current.openDeleteDialog('my-stack'));
|
|
act(() => result.current.closeDeleteDialog());
|
|
expect(result.current.deleteDialogOpen).toBe(false);
|
|
expect(result.current.stackToDelete).toBeNull();
|
|
});
|
|
|
|
it('openLogViewer sets open flag and container object', () => {
|
|
const { result } = renderHook(() => useOverlayState());
|
|
act(() => result.current.openLogViewer({ id: 'xyz', name: 'log-container' }));
|
|
expect(result.current.logViewerOpen).toBe(true);
|
|
expect(result.current.logContainer).toEqual({ id: 'xyz', name: 'log-container' });
|
|
});
|
|
|
|
it('closeLogViewer resets log viewer state', () => {
|
|
const { result } = renderHook(() => useOverlayState());
|
|
act(() => result.current.openLogViewer({ id: 'xyz', name: 'log-container' }));
|
|
act(() => result.current.closeLogViewer());
|
|
expect(result.current.logViewerOpen).toBe(false);
|
|
expect(result.current.logContainer).toBeNull();
|
|
});
|
|
|
|
it('openAlertSheet opens stack monitor on the alerts tab', () => {
|
|
const { result } = renderHook(() => useOverlayState());
|
|
act(() => result.current.openAlertSheet('web-stack'));
|
|
expect(result.current.stackMonitor).toEqual({ stackName: 'web-stack', tab: 'alerts' });
|
|
});
|
|
|
|
it('openAutoHeal opens stack monitor on the auto-heal tab', () => {
|
|
const { result } = renderHook(() => useOverlayState());
|
|
act(() => result.current.openAutoHeal('web-stack'));
|
|
expect(result.current.stackMonitor).toEqual({ stackName: 'web-stack', tab: 'auto-heal' });
|
|
});
|
|
|
|
it('openAutoHeal after openAlertSheet switches to the auto-heal tab', () => {
|
|
const { result } = renderHook(() => useOverlayState());
|
|
act(() => result.current.openAlertSheet('web-stack'));
|
|
act(() => result.current.openAutoHeal('web-stack'));
|
|
expect(result.current.stackMonitor).toEqual({ stackName: 'web-stack', tab: 'auto-heal' });
|
|
});
|
|
|
|
it('closeStackMonitor clears the stack monitor state', () => {
|
|
const { result } = renderHook(() => useOverlayState());
|
|
act(() => result.current.openAlertSheet('web-stack'));
|
|
act(() => result.current.closeStackMonitor());
|
|
expect(result.current.stackMonitor).toBeNull();
|
|
});
|
|
});
|