feat(sidebar): §14 sidebar orchestration, filter chips, pinned rail, trailing column (#850)

* feat(sidebar): §14 sidebar orchestration (filter chips, pinned rail, trailing column)

- Add All / Up / Down / Updates filter chips with live counts; active chip
  filters the list; chip-filtered files computed in EditorLayout with useMemo
- Surface the PINNED group with a 3px cyan left rail and glow via the
  sidebarPinnedGroupRail token; reuses the brand token already on the active row
- Compact brand row from three stacked elements to a single 44px horizontal bar
- Restructure StackRow trailing column as fixed slots: label dots (max 3 + +N
  overflow), update-dot | git-pending icon (priority order), kebab; add reserved
  invisible checkbox slot for PR2 bulk mode
- Export statusText / statusColor from StackRow and reuse them in StackList
  remote-results section to remove the duplicate inline logic
- Lift filterChip state and chip-filtered files to EditorLayout; remove
  filterChip from StackListProps to eliminate the dual-path redundancy
- Remove unused labels param from buildGroups and the void labels workaround
- Wrap filteredFiles in useMemo so filterCounts memo is not defeated on every
  render

* fix(sidebar): move statusText and statusColor to stack-status-utils

react-refresh/only-export-components requires component files to export
only components. Move the two utility functions and StackRowStatus type
to a dedicated stack-status-utils.ts so StackRow.tsx is a pure component
module. Update StackList.tsx and EditorLayout.tsx to import from the new
source directly.
This commit is contained in:
Anso
2026-04-30 19:37:49 -04:00
committed by GitHub
parent eead195529
commit 4c0efcb9a8
10 changed files with 187 additions and 62 deletions
+26 -7
View File
@@ -73,8 +73,8 @@ 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';
import type { StackRowStatus } from '@/components/sidebar/stack-status-utils';
import type { FilterChip, StackMenuCtx } from '@/components/sidebar/sidebar-types';
import { StackFileExplorer } from '@/components/files/StackFileExplorer';
interface ContainerInfo {
@@ -1931,9 +1931,9 @@ export default function EditorLayout() {
// Stack name is now the same as selectedFile (no extension to strip)
const stackName = selectedFile || '';
// Filter files based on search query
const filteredFiles = files.filter(file =>
file.toLowerCase().includes(searchQuery.toLowerCase())
const filteredFiles = useMemo(
() => files.filter(file => file.toLowerCase().includes(searchQuery.toLowerCase())),
[files, searchQuery],
);
// Get display name for stack (now just returns the name as-is since no extension)
@@ -1947,6 +1947,23 @@ export default function EditorLayout() {
if (evictedOldest) toast.info('Pinned. Unpinned oldest (max 10).');
}, [evictedOldest]);
const [filterChip, setFilterChip] = useState<FilterChip>('all');
const filterCounts = useMemo(() => ({
all: filteredFiles.length,
up: filteredFiles.filter(f => stackStatuses[f] === 'running').length,
down: filteredFiles.filter(f => stackStatuses[f] === 'exited').length,
updates: filteredFiles.filter(f => !!stackUpdates[f]).length,
}), [filteredFiles, stackStatuses, stackUpdates]);
const chipFilteredFiles = useMemo(() => {
if (filterChip === 'all') return filteredFiles;
if (filterChip === 'up') return filteredFiles.filter(f => stackStatuses[f] === 'running');
if (filterChip === 'down') return filteredFiles.filter(f => stackStatuses[f] === 'exited');
if (filterChip === 'updates') return filteredFiles.filter(f => !!stackUpdates[f]);
return filteredFiles;
}, [filteredFiles, filterChip, stackStatuses, stackUpdates]);
const { isCollapsed, toggle: toggleCollapse } = useSidebarGroupCollapse(activeNode?.id);
const remoteResults = useMemo(() => {
@@ -2286,8 +2303,11 @@ export default function EditorLayout() {
canCreate={can('stack:create')}
searchQuery={searchQuery}
onSearchChange={setSearchQuery}
filterChip={filterChip}
filterCounts={filterCounts}
onFilterChipChange={setFilterChip}
list={{
files: filteredFiles ?? [],
files: chipFilteredFiles,
isLoading,
isPaid,
selectedFile,
@@ -2296,7 +2316,6 @@ export default function EditorLayout() {
stackStatuses: stackStatuses as Record<string, StackRowStatus | undefined>,
stackUpdates,
gitSourcePendingMap,
labels,
pinnedFiles: pinned,
isCollapsed,
toggleCollapse,