From 307808bec2753769aba1c98e31b7028570837389 Mon Sep 17 00:00:00 2001 From: lilyzhaun <90462695+lilyzhaun@users.noreply.github.com> Date: Wed, 24 Jun 2026 02:27:54 +0800 Subject: [PATCH] fix(mobile): use exact directory matching for session grouping (#1687) * fix(mobile): use exact directory matching for session grouping The new mobile sessions sheet used startsWith prefix matching to assign sessions to projects, which caused child-directory sessions (e.g. /root/repos/opencode) to be grouped into parent projects (e.g. /root/repos). Switch to exact directory matching (project root or registered worktree paths only) to match the desktop sidebar behavior. Also exclude sub-agent sessions (those with parentID) from the totalSessions badge count so the displayed number reflects only top-level sessions. * fix: align mobile session project matching --------- Co-authored-by: lilyzhaun Co-authored-by: Bohdan Triapitsyn --- packages/ui/src/apps/MobileSessionsSheet.tsx | 49 +++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/packages/ui/src/apps/MobileSessionsSheet.tsx b/packages/ui/src/apps/MobileSessionsSheet.tsx index 736f6500..39449100 100644 --- a/packages/ui/src/apps/MobileSessionsSheet.tsx +++ b/packages/ui/src/apps/MobileSessionsSheet.tsx @@ -154,6 +154,20 @@ const pathBelongsToRoot = (path: string, root: string): boolean => { ); }; +const findExactWorktreeMatch = (project: ProjectMeta, normalizedDirectory: string): WorktreeMetadata | null => ( + project.worktrees.find((worktree) => normalizePath(worktree.path) === normalizedDirectory) ?? null +); + +const projectMatchesExactDirectory = (project: ProjectMeta, normalizedDirectory: string): boolean => ( + normalizedDirectory === project.path || Boolean(findExactWorktreeMatch(project, normalizedDirectory)) +); + +const findExactProjectMatch = (projects: ProjectMeta[], directory: string): ProjectMeta | null => { + const normalizedDirectory = normalizePath(directory); + if (!normalizedDirectory) return null; + return projects.find((project) => projectMatchesExactDirectory(project, normalizedDirectory)) ?? null; +}; + const sessionMatchesQuery = (session: Session, projectLabel: string, query: string): boolean => { if (!query) return true; const haystack = `${session.title ?? ''} ${session.id} ${getSessionDirectory(session)} ${projectLabel}`.toLowerCase(); @@ -662,12 +676,10 @@ export const MobileSessionsSheet: React.FC = ({ open, for (const session of sessions) { const directory = getSessionDirectory(session); if (!directory) continue; - const node = nodes.find((entry) => { - if (pathBelongsToRoot(directory, entry.project.path)) return true; - return entry.project.worktrees.some((worktree) => pathBelongsToRoot(directory, worktree.path)); - }); + const normalizedDirectory = normalizePath(directory); + const node = nodes.find((entry) => projectMatchesExactDirectory(entry.project, normalizedDirectory)); if (!node) continue; - const matchedWorktree = node.project.worktrees.find((entry) => pathBelongsToRoot(directory, entry.path)); + const matchedWorktree = findExactWorktreeMatch(node.project, normalizedDirectory); const bucket = matchedWorktree ? ensureBucket(node, matchedWorktree.path, matchedWorktree) : ensureBucket(node, node.project.path, null); @@ -677,7 +689,9 @@ export const MobileSessionsSheet: React.FC = ({ open, for (const node of nodes) { for (const bucket of node.buckets) { bucket.sessions.sort((a, b) => getSessionTimestamp(b) - getSessionTimestamp(a)); - node.totalSessions += bucket.sessions.length; + for (const session of bucket.sessions) { + if (!getParentId(session)) node.totalSessions += 1; + } } } @@ -816,10 +830,7 @@ export const MobileSessionsSheet: React.FC = ({ open, // Switching session switches the working directory (handled by // setCurrentSession) — also move the active project so the rest of the app // and the active highlight follow the selected session, not just the draft. - const project = projectsMeta.find((entry) => { - if (pathBelongsToRoot(directory ?? '', entry.path)) return true; - return entry.worktrees.some((worktree) => pathBelongsToRoot(directory ?? '', worktree.path)); - }); + const project = findExactProjectMatch(projectsMeta, directory ?? ''); if (project) setActiveProjectIdOnly(project.id); void setCurrentSession(session.id, directory); onOpenChange(false); @@ -878,12 +889,9 @@ export const MobileSessionsSheet: React.FC = ({ open, const buildSessionContextLabel = React.useCallback( (session: Session): string => { const directory = getSessionDirectory(session); - const project = projectsMeta.find((entry) => { - if (pathBelongsToRoot(directory, entry.path)) return true; - return entry.worktrees.some((worktree) => pathBelongsToRoot(directory, worktree.path)); - }); + const project = findExactProjectMatch(projectsMeta, directory); if (!project) return getProjectLabel(directory) || directory; - const matchedWorktree = project.worktrees.find((entry) => pathBelongsToRoot(directory, entry.path)); + const matchedWorktree = findExactWorktreeMatch(project, normalizePath(directory)); if (matchedWorktree?.branch) return `${project.label} · ${matchedWorktree.branch}`; return project.label; }, @@ -915,10 +923,7 @@ export const MobileSessionsSheet: React.FC = ({ open, return sessions .filter((session) => { const directory = getSessionDirectory(session); - const project = projectsMeta.find((entry) => { - if (pathBelongsToRoot(directory, entry.path)) return true; - return entry.worktrees.some((worktree) => pathBelongsToRoot(directory, worktree.path)); - }); + const project = findExactProjectMatch(projectsMeta, directory); return sessionMatchesQuery(session, project?.label ?? '', normalizedQuery); }) .sort((a, b) => getSessionTimestamp(b) - getSessionTimestamp(a)); @@ -931,9 +936,9 @@ export const MobileSessionsSheet: React.FC = ({ open, .map((project) => ({ ...project, sessionCount: sessions.filter((session) => { - const directory = getSessionDirectory(session); - if (pathBelongsToRoot(directory, project.path)) return true; - return project.worktrees.some((worktree) => pathBelongsToRoot(directory, worktree.path)); + if (getParentId(session)) return false; + const directory = normalizePath(getSessionDirectory(session)); + return projectMatchesExactDirectory(project, directory); }).length, })); }, [normalizedQuery, projectsMeta, sessions]);