From 61765ca32c1b0286f07c6b10d02c55dc5882c87e Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Tue, 2 Jun 2026 17:04:23 +0300 Subject: [PATCH] feat: show subsessions under parent sessions in mobile sheet Nest child sessions (by parentID) under their parent in the mobile sessions list, with a chevron in the row's left gutter to expand/collapse them recursively. Top-level pagination counts only parent sessions; children whose parent isn't in the same bucket stay top-level so nothing is hidden. Expansion state lives in an in-memory store that survives closing and reopening the sheet but resets on a full page reload. --- packages/ui/src/apps/MobileSessionsSheet.tsx | 91 ++++++++++++++++--- .../stores/useMobileSessionExpansionStore.ts | 23 +++++ 2 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 packages/ui/src/stores/useMobileSessionExpansionStore.ts diff --git a/packages/ui/src/apps/MobileSessionsSheet.tsx b/packages/ui/src/apps/MobileSessionsSheet.tsx index 9849987f..bf4bd05b 100644 --- a/packages/ui/src/apps/MobileSessionsSheet.tsx +++ b/packages/ui/src/apps/MobileSessionsSheet.tsx @@ -46,6 +46,7 @@ import { cn } from '@/lib/utils'; import { listProjectWorktrees } from '@/lib/worktrees/worktreeManager'; import { useDirectoryStore } from '@/stores/useDirectoryStore'; import { refreshGlobalSessions, useGlobalSessionsStore } from '@/stores/useGlobalSessionsStore'; +import { useMobileSessionExpansionStore } from '@/stores/useMobileSessionExpansionStore'; import { useMobileSessionTreeStore } from '@/stores/useMobileSessionTreeStore'; import { useProjectsStore } from '@/stores/useProjectsStore'; import { orderWorktrees, useWorktreeOrderStore } from '@/stores/useWorktreeOrderStore'; @@ -100,6 +101,11 @@ const SESSIONS_PER_BUCKET = 7; // worktree sessions sit one level deeper. SessionRow adds 16px (dot + gap) on top. const PROJECT_SESSION_INDENT = 36; const WORKTREE_SESSION_INDENT = 52; +// Extra left padding applied to each nested subsession level. +const CHILD_INDENT_STEP = 18; + +const getParentId = (session: Session): string | null => + (session as Session & { parentID?: string | null }).parentID ?? null; const normalizePath = (value?: string | null): string => (value || '').replace(/\\/g, '/').replace(/\/+$/g, ''); @@ -248,6 +254,10 @@ const SessionRow: React.FC<{ contextLabel?: string; /** When true, the row shows the two-step archive confirmation. */ confirmingArchive?: boolean; + /** When true, a chevron is shown in the left gutter to toggle nested subsessions. */ + hasChildren?: boolean; + expanded?: boolean; + onToggleChildren?: () => void; onSelect: () => void; /** When provided, an archive affordance is shown; first tap arms confirm, X cancels. */ onRequestArchive?: () => void; @@ -258,6 +268,9 @@ const SessionRow: React.FC<{ indent, contextLabel, confirmingArchive = false, + hasChildren = false, + expanded = false, + onToggleChildren, onSelect, onRequestArchive, onConfirmArchive, @@ -268,11 +281,27 @@ const SessionRow: React.FC<{ return (
+ {hasChildren && onToggleChildren ? ( + + ) : null}