mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-24 17:36:42 +00:00
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
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { Command } from '@/components/ui/command';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import type { NotificationItem } from '@/components/dashboard/types';
|
||||
import { SidebarActions } from './SidebarActions';
|
||||
import { SidebarActivityTicker } from './SidebarActivityTicker';
|
||||
import { SidebarBrand } from './SidebarBrand';
|
||||
import { SidebarSearch } from './SidebarSearch';
|
||||
import { StackList, type StackListProps } from './StackList';
|
||||
|
||||
export interface StackSidebarProps {
|
||||
isDarkMode: boolean;
|
||||
nodeSwitcherSlot: ReactNode;
|
||||
createStackSlot: ReactNode | null;
|
||||
onScan: () => void;
|
||||
isScanning: boolean;
|
||||
canCreate: boolean;
|
||||
searchQuery: string;
|
||||
onSearchChange: (v: string) => void;
|
||||
list: StackListProps;
|
||||
notifications: NotificationItem[];
|
||||
tickerConnected: boolean;
|
||||
onOpenActivity: () => void;
|
||||
}
|
||||
|
||||
export function StackSidebar(props: StackSidebarProps) {
|
||||
const {
|
||||
isDarkMode, nodeSwitcherSlot, createStackSlot, onScan, isScanning, canCreate,
|
||||
searchQuery, onSearchChange, list, notifications, tickerConnected, onOpenActivity,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className="w-64 border-r border-glass-border bg-sidebar backdrop-blur-md flex flex-col">
|
||||
<SidebarBrand isDarkMode={isDarkMode} />
|
||||
<div className="px-4 pt-2 pb-0">{nodeSwitcherSlot}</div>
|
||||
{canCreate && createStackSlot !== null && (
|
||||
<SidebarActions createStackSlot={createStackSlot} onScan={onScan} isScanning={isScanning} />
|
||||
)}
|
||||
<Command shouldFilter={false} className="bg-transparent flex-1 flex flex-col overflow-hidden">
|
||||
<SidebarSearch value={searchQuery} onValueChange={onSearchChange} />
|
||||
<ScrollArea className="flex-1 px-2 pb-2">
|
||||
<div data-stacks-loaded={list.isLoading ? 'false' : 'true'}>
|
||||
<StackList {...list} />
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</Command>
|
||||
<SidebarActivityTicker
|
||||
notifications={notifications}
|
||||
connected={tickerConnected}
|
||||
onNavigate={onOpenActivity}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user