From 370b67d7ec5f8f5ee1d8df81bdd4a892c0c0c9d5 Mon Sep 17 00:00:00 2001 From: Anso Date: Sun, 19 Apr 2026 21:45:01 -0400 Subject: [PATCH] feat(sidebar): cockpit redesign with grouped stacks and activity footer (#702) * chore: ignore .superpowers/ brainstorm scratch dir * feat(sidebar): add useStackMenuItems hook with grouped menu model Pure transform hook that converts StackMenuCtx into four ordered MenuGroup arrays (inspect, organize, lifecycle, destructive). Shared type contract in sidebar-types.ts gives both the ContextMenu and DropdownMenu a single source of truth so they cannot drift. Covered by 8 unit tests. * refactor(sidebar): stabilize useStackMenuItems memoization deps Destructure menuVisibility flags into primitive deps so inline object literals from callers do not defeat memoization. Add a test confirming isBusy disables all lifecycle items. * feat(sidebar): add usePinnedStacks hook with per-node localStorage * refactor(sidebar): stabilize usePinnedStacks eviction signal and isPinned dep Change evictedOldest shape to { file, seq } so consumer effects re-fire on repeated evictions. Narrow isPinned's useCallback dep to the current node's pinned list so it only rebinds on local changes. Add test for eviction side-effect and a second test for the seq counter. * feat(sidebar): add useSidebarGroupCollapse hook with per-node keys * refactor(sidebar): tighten useSidebarGroupCollapse effect ordering Collapse the two write/read effects into a single skip-next-write ref pattern so switching nodes no longer writes the previous node's map under the new key before hydration. Also skip the no-op mount write. Add test for setCollapsed. * feat(sidebar): add row + group-header style helpers * feat(sidebar): add SidebarBrand with mono kicker + serif hero * feat(sidebar): add SidebarActions wrapper for create + scan * feat(sidebar): extract SidebarSearch with kbd pill * feat(sidebar): add StackRow with cyan-rail active state * refactor(sidebar): dedupe tooltip markup in StackRow, widen test coverage Extract a local RowTooltip helper so the update and git-pending branches share the CursorProvider scaffolding. Add four behavioral tests covering click, keyboard activation, kebab stop-propagation, and the busy loader branch. * feat(sidebar): unify context + kebab menus via useStackMenuItems * feat(sidebar): add StackGroup with collapse and pinned variant * feat(sidebar): add StackList with pinned + label groups * feat(sidebar): add SidebarActivityTicker with idle fallback * feat(sidebar): add StackSidebar container composing the regions * feat(sidebar): replace sidebar block with StackSidebar composition * docs(sidebar): add stack sidebar feature page with screenshots * fix(sidebar): satisfy react-hooks purity and memoization rules * fix(sidebar): restore "Sencho Logo" alt text for E2E selector --- .gitignore | 1 + docs/docs.json | 1 + docs/features/sidebar.mdx | 54 + docs/images/sidebar/sidebar-activity.png | Bin 0 -> 2542 bytes docs/images/sidebar/sidebar-context-menu.png | Bin 0 -> 15925 bytes docs/images/sidebar/sidebar-overview.png | Bin 0 -> 21750 bytes docs/images/sidebar/sidebar-pinned.png | Bin 0 -> 23962 bytes frontend/src/components/EditorLayout.tsx | 972 ++++++------------ .../src/components/sidebar/SidebarActions.tsx | 38 + .../sidebar/SidebarActivityTicker.tsx | 58 ++ .../src/components/sidebar/SidebarBrand.tsx | 21 + .../src/components/sidebar/SidebarSearch.tsx | 27 + .../components/sidebar/StackContextMenu.tsx | 99 ++ .../src/components/sidebar/StackGroup.tsx | 43 + .../src/components/sidebar/StackKebabMenu.tsx | 102 ++ frontend/src/components/sidebar/StackList.tsx | 197 ++++ frontend/src/components/sidebar/StackRow.tsx | 93 ++ .../src/components/sidebar/StackSidebar.tsx | 54 + .../__tests__/SidebarActivityTicker.test.tsx | 44 + .../sidebar/__tests__/StackGroup.test.tsx | 46 + .../sidebar/__tests__/StackRow.test.tsx | 76 ++ .../src/components/sidebar/sidebar-styles.ts | 21 + .../src/components/sidebar/sidebar-types.ts | 49 + .../hooks/__tests__/usePinnedStacks.test.ts | 75 ++ .../__tests__/useSidebarGroupCollapse.test.ts | 48 + .../__tests__/useStackMenuItems.test.tsx | 98 ++ frontend/src/hooks/usePinnedStacks.ts | 69 ++ frontend/src/hooks/useSidebarGroupCollapse.ts | 67 ++ frontend/src/hooks/useStackMenuItems.tsx | 86 ++ 29 files changed, 1793 insertions(+), 646 deletions(-) create mode 100644 docs/features/sidebar.mdx create mode 100644 docs/images/sidebar/sidebar-activity.png create mode 100644 docs/images/sidebar/sidebar-context-menu.png create mode 100644 docs/images/sidebar/sidebar-overview.png create mode 100644 docs/images/sidebar/sidebar-pinned.png create mode 100644 frontend/src/components/sidebar/SidebarActions.tsx create mode 100644 frontend/src/components/sidebar/SidebarActivityTicker.tsx create mode 100644 frontend/src/components/sidebar/SidebarBrand.tsx create mode 100644 frontend/src/components/sidebar/SidebarSearch.tsx create mode 100644 frontend/src/components/sidebar/StackContextMenu.tsx create mode 100644 frontend/src/components/sidebar/StackGroup.tsx create mode 100644 frontend/src/components/sidebar/StackKebabMenu.tsx create mode 100644 frontend/src/components/sidebar/StackList.tsx create mode 100644 frontend/src/components/sidebar/StackRow.tsx create mode 100644 frontend/src/components/sidebar/StackSidebar.tsx create mode 100644 frontend/src/components/sidebar/__tests__/SidebarActivityTicker.test.tsx create mode 100644 frontend/src/components/sidebar/__tests__/StackGroup.test.tsx create mode 100644 frontend/src/components/sidebar/__tests__/StackRow.test.tsx create mode 100644 frontend/src/components/sidebar/sidebar-styles.ts create mode 100644 frontend/src/components/sidebar/sidebar-types.ts create mode 100644 frontend/src/hooks/__tests__/usePinnedStacks.test.ts create mode 100644 frontend/src/hooks/__tests__/useSidebarGroupCollapse.test.ts create mode 100644 frontend/src/hooks/__tests__/useStackMenuItems.test.tsx create mode 100644 frontend/src/hooks/usePinnedStacks.ts create mode 100644 frontend/src/hooks/useSidebarGroupCollapse.ts create mode 100644 frontend/src/hooks/useStackMenuItems.tsx diff --git a/.gitignore b/.gitignore index efbf61ae..df753c8e 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ CLAUDE.md MANUAL_STEPS.md docs/superpowers/ .worktrees/ +.superpowers/ # Design system (local-only; authoritative reference for agents + user) frontend/DESIGN.md diff --git a/docs/docs.json b/docs/docs.json index e8bbe188..d9b33c9d 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -95,6 +95,7 @@ "pages": [ "features/overview", "features/dashboard", + "features/sidebar", "features/stack-management", "features/editor", "features/resources", diff --git a/docs/features/sidebar.mdx b/docs/features/sidebar.mdx new file mode 100644 index 00000000..217d5587 --- /dev/null +++ b/docs/features/sidebar.mdx @@ -0,0 +1,54 @@ +--- +title: Stack Sidebar +description: Manage, group, and pin your stacks from the primary sidebar. +--- + +The stack sidebar is your cockpit for every stack on the active node. Stacks are grouped by label, your most-used stacks can be pinned to the top, and a live activity footer keeps you aware of what just happened. + + + Stack sidebar overview + + +## Layout + +From top to bottom: + +1. **Branding header** shows your Sencho build version. +2. **Node switcher** selects which Sencho instance you are managing. +3. **Create Stack** opens the new-stack dialog. The folder icon scans your compose directory. +4. **Search** filters stacks by name. Press ⌘K (or Ctrl+K) to focus it. +5. **Stack list** shows your stacks grouped by label. +6. **Activity footer** shows the most recent stack event. + +## Groups + +Stacks are grouped by label. Stacks with multiple labels appear in each matching group so you always see the full fleet membership per label. Stacks without labels appear in an **Unlabeled** group at the bottom. Click a group header to collapse or expand it; the collapsed state is remembered per node. + +## Pinning + +Right-click a stack and choose **Pin to top**. Pinned stacks sit in a dedicated **PINNED** group at the top of the list. Up to 10 stacks can be pinned per node; pinning an 11th evicts the oldest. + + + Pinned stacks appear in a PINNED group at top + + +## Context menu + +Right-click any stack (or open the kebab that appears on hover) for its context menu, grouped by purpose: + +- **Inspect**: view alerts, open the auto-heal sheet, check for image updates, open the running app. +- **Organize**: assign labels, pin or unpin the stack. +- **Lifecycle**: deploy, stop, restart, or update the stack. +- **Destructive**: delete the stack. + + + Grouped stack context menu + + +## Activity footer + +The footer shows the most recent stack lifecycle event within the last hour. Click it to jump to the global activity log. When nothing has happened recently, the footer reads **IDLE**. + + + Activity footer ticker + diff --git a/docs/images/sidebar/sidebar-activity.png b/docs/images/sidebar/sidebar-activity.png new file mode 100644 index 0000000000000000000000000000000000000000..9b5753792e982c2d8912adbaa4ab9a7e5853c71c GIT binary patch literal 2542 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 a8Y5kSz7cb@i38i^h7PWWfYs~RWBvt&kL3*j literal 0 HcmV?d00001 diff --git a/docs/images/sidebar/sidebar-context-menu.png b/docs/images/sidebar/sidebar-context-menu.png new file mode 100644 index 0000000000000000000000000000000000000000..97e2dd20e120457744f8ecf6a5b12af155fd56bd GIT binary patch literal 15925 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 CxLard literal 0 HcmV?d00001 diff --git a/docs/images/sidebar/sidebar-overview.png b/docs/images/sidebar/sidebar-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..c995ef73691b6540492a55ac13b25968510a11ad GIT binary patch literal 21750 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 literal 0 HcmV?d00001 diff --git a/docs/images/sidebar/sidebar-pinned.png b/docs/images/sidebar/sidebar-pinned.png new file mode 100644 index 0000000000000000000000000000000000000000..ed72f305def95ce0fac7432f2460d7049e779788 GIT binary patch literal 23962 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 literal 0 HcmV?d00001 diff --git a/frontend/src/components/EditorLayout.tsx b/frontend/src/components/EditorLayout.tsx index ad05d93f..69974c0c 100644 --- a/frontend/src/components/EditorLayout.tsx +++ b/frontend/src/components/EditorLayout.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef, useMemo } from 'react'; +import { useState, useEffect, useRef, useMemo, useCallback } from 'react'; type Theme = 'light' | 'dark' | 'auto'; import Editor from '@monaco-editor/react'; @@ -17,26 +17,19 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Di import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from './ui/alert-dialog'; import { Tabs, TabsList, TabsTrigger, TabsHighlight, TabsHighlightItem } from './ui/tabs'; import { springs } from '@/lib/motion'; -import { CursorProvider, Cursor, CursorContainer, CursorFollow } from '@/components/animate-ui/primitives/animate/cursor'; import { Card, CardContent, CardHeader, CardTitle } from './ui/card'; -import { Plus, Trash2, Play, Square, Save, Terminal, RotateCw, CloudDownload, Pencil, X, Home, ExternalLink, MoreVertical, BellRing, Rocket, HardDrive, ScrollText, Activity, Radar, Undo2, RefreshCw, Download, Clock, FolderSearch, Loader2, Tag, Check, ChevronDown, GitBranch, FileCode2, ShieldCheck, ArrowUpRight, Copy } from 'lucide-react'; +import { Plus, Trash2, Play, Square, Save, Terminal, RotateCw, CloudDownload, Pencil, X, Home, MoreVertical, Rocket, HardDrive, ScrollText, Activity, Radar, Undo2, RefreshCw, Clock, Loader2, Check, ChevronDown, GitBranch, FileCode2, ShieldCheck, ArrowUpRight, Copy } from 'lucide-react'; import type { LucideIcon } from 'lucide-react'; -import { LabelPill, LabelDot } from './LabelPill'; import { type Label as StackLabel } from './label-types'; -import { LabelAssignPopover } from './LabelAssignPopover'; import { UserProfileDropdown } from './UserProfileDropdown'; import { NotificationPanel } from './NotificationPanel'; import { apiFetch, fetchForNode } from '@/lib/api'; import { toast } from '@/components/ui/toast-store'; import { Label } from './ui/label'; -import { Command, CommandInput, CommandList, CommandItem } from './ui/command'; import { ScrollArea } from './ui/scroll-area'; import { Checkbox } from './ui/checkbox'; import { GitSourceFields, type ApplyMode } from './stack/GitSourceFields'; -import { Skeleton } from './ui/skeleton'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from './ui/dropdown-menu'; -import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger } from './ui/context-menu'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { TopBar } from './TopBar'; import { cn } from '@/lib/utils'; @@ -66,6 +59,11 @@ import { useAuth } from '@/context/AuthContext'; import { useLicense } from '@/context/LicenseContext'; import { useTrivyStatus } from '@/hooks/useTrivyStatus'; import { VulnerabilityScanSheet } from './VulnerabilityScanSheet'; +import { StackSidebar } from '@/components/sidebar/StackSidebar'; +import { usePinnedStacks } from '@/hooks/usePinnedStacks'; +import { useSidebarGroupCollapse } from '@/hooks/useSidebarGroupCollapse'; +import type { StackRowStatus } from '@/components/sidebar/StackRow'; +import type { StackMenuCtx } from '@/components/sidebar/sidebar-types'; interface ContainerInfo { Id: string; @@ -322,9 +320,10 @@ export default function EditorLayout() { const [stackPorts, setStackPorts] = useState>({}); const [labels, setLabels] = useState([]); const [stackLabelMap, setStackLabelMap] = useState>({}); - const [activeLabelFilters, setActiveLabelFilters] = useState>(new Set()); - const [bulkActionLabel, setBulkActionLabel] = useState(null); - const [bulkAction, setBulkAction] = useState(''); + // Bulk-action dialog is retained as a safety fallback; the label pill entry + // point that drove the setters was removed alongside the sidebar rewrite. + const [bulkActionLabel] = useState(null); + const [bulkAction] = useState(''); const [bulkActionOpen, setBulkActionOpen] = useState(false); const [bulkActionRunning, setBulkActionRunning] = useState(false); @@ -342,6 +341,7 @@ export default function EditorLayout() { // Notifications & Settings state const [notifications, setNotifications] = useState([]); + const [tickerConnected, setTickerConnected] = useState(false); const [settingsModalOpen, setSettingsModalOpen] = useState(false); const [settingsInitialSection, setSettingsInitialSection] = useState<'account' | 'labels' | 'nodes'>('account'); const [alertSheetOpen, setAlertSheetOpen] = useState(false); @@ -679,6 +679,7 @@ export default function EditorLayout() { ws?.close(); return; } + setTickerConnected(true); retryCount = 0; // Reset backoff on successful connect }; @@ -700,6 +701,7 @@ export default function EditorLayout() { }; ws.onclose = (event) => { + setTickerConnected(false); if (!isMounted) return; // Exponential backoff: 1s, 2s, 4s, 8s, 16s, 30s max const delay = Math.min(1000 * Math.pow(2, retryCount), MAX_RETRY_DELAY_MS); @@ -1830,651 +1832,329 @@ export default function EditorLayout() { const stackName = selectedFile || ''; // Filter files based on search query - const filteredFiles = files.filter(file => { - if (!file.toLowerCase().includes(searchQuery.toLowerCase())) return false; - if (activeLabelFilters.size > 0) { - const fileLabels = stackLabelMap[file] || []; - if (!fileLabels.some(l => activeLabelFilters.has(l.id))) return false; - } - return true; - }); + const filteredFiles = files.filter(file => + file.toLowerCase().includes(searchQuery.toLowerCase()) + ); // Get display name for stack (now just returns the name as-is since no extension) const getDisplayName = (stackName: string) => { return stackName; }; + const { pinned, pin, unpin, isPinned, evictedOldest } = usePinnedStacks(activeNode?.id); + + useEffect(() => { + if (evictedOldest) toast.info('Pinned. Unpinned oldest (max 10).'); + }, [evictedOldest]); + + const { isCollapsed, toggle: toggleCollapse } = useSidebarGroupCollapse(activeNode?.id); + + const remoteResults = useMemo(() => { + return Object.entries(remoteStackResults).flatMap(([nodeIdStr, remoteFiles]) => { + const node = nodes.find(n => n.id === Number(nodeIdStr)); + if (!node || remoteFiles.length === 0) return []; + return [{ + nodeId: node.id, + nodeName: node.name, + files: remoteFiles.map(({ file, status }) => ({ file, status: status as StackRowStatus })), + }]; + }); + }, [remoteStackResults, nodes]); + + const buildMenuCtx = useCallback((file: string): StackMenuCtx => { + const stackName = file.replace(/\.(yml|yaml)$/, ''); + return { + stackStatus: (stackStatuses[file] ?? 'unknown') as 'running' | 'exited' | 'unknown', + hasPort: Boolean(stackPorts[file]), + isBusy: isStackBusy(file), + isPaid, + canDelete: can('stack:delete', 'stack', stackName), + isPinned: isPinned(file), + labels, + assignedLabelIds: (stackLabelMap[file] ?? []).map(l => l.id), + menuVisibility: getStackMenuVisibility(file), + openAlertSheet: () => openAlertSheet(file), + openAutoHeal: () => setAutoHealStackName(file), + checkUpdates: () => checkUpdatesForStack(), + openStackApp: () => openStackApp(file), + deploy: () => executeStackActionByFile(file, 'deploy', 'deploy'), + stop: () => executeStackActionByFile(file, 'stop', 'stop'), + restart: () => executeStackActionByFile(file, 'restart', 'restart'), + update: () => executeStackActionByFile(file, 'update', 'update'), + remove: () => { setStackToDelete(stackName); setDeleteDialogOpen(true); }, + pin: () => pin(file), + unpin: () => unpin(file), + toggleLabel: async (labelId: number) => { + const currentIds = (stackLabelMap[file] ?? []).map(l => l.id); + const assigned = currentIds.includes(labelId); + const newIds = assigned ? currentIds.filter(id => id !== labelId) : [...currentIds, labelId]; + const loadingId = toast.loading('Updating labels...'); + try { + const res = await apiFetch(`/stacks/${encodeURIComponent(file)}/labels`, { + method: 'PUT', + body: JSON.stringify({ labelIds: newIds }), + }); + if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error((data as { error?: string })?.error || 'Failed to update labels.'); } + refreshLabels(); + } catch (err: unknown) { + toast.error((err as Error)?.message || 'Failed to update labels.'); + } finally { + toast.dismiss(loadingId); + } + }, + openLabelManager: () => { setSettingsInitialSection('labels'); setSettingsModalOpen(true); }, + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + stackStatuses, stackPorts, isPaid, isPinned, labels, stackLabelMap, + pin, unpin, + ]); + + const createStackSlot = can('stack:create') ? ( + { + setCreateDialogOpen(o); + if (!o) { + setCreateMode('empty'); + resetCreateFromGitForm(); + resetCreateFromDockerRunForm(); + } + }}> + + + + + + Create New Stack + + Create a new stack: empty, cloned from a Git repository, or converted from a docker run command. + + + +
+ setCreateMode(v as 'empty' | 'git' | 'docker-run')}> + + + + + + Empty + + + + + + From Git + + + + + + From Docker Run + + + + + +
+ + {createMode === 'empty' && ( + <> +
+ + setNewStackName(e.target.value)} + /> +
+ + + + + )} + {createMode === 'git' && ( + <> + +
+
+ + setNewStackName(e.target.value)} + disabled={creatingFromGit} + /> +
+ + + +
+ setGitDeployNow(c === true)} + disabled={creatingFromGit} + /> + +
+
+
+ + + + + )} + {createMode === 'docker-run' && ( + <> + +
+
+ + setNewStackName(e.target.value)} + disabled={creatingFromDockerRun} + /> +
+
+ +