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 <lilyzhaun@users.noreply.github.com> Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
lilyzhaun
Bohdan Triapitsyn
parent
12835c7646
commit
307808bec2
@@ -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 => {
|
const sessionMatchesQuery = (session: Session, projectLabel: string, query: string): boolean => {
|
||||||
if (!query) return true;
|
if (!query) return true;
|
||||||
const haystack = `${session.title ?? ''} ${session.id} ${getSessionDirectory(session)} ${projectLabel}`.toLowerCase();
|
const haystack = `${session.title ?? ''} ${session.id} ${getSessionDirectory(session)} ${projectLabel}`.toLowerCase();
|
||||||
@@ -662,12 +676,10 @@ export const MobileSessionsSheet: React.FC<MobileSessionsSheetProps> = ({ open,
|
|||||||
for (const session of sessions) {
|
for (const session of sessions) {
|
||||||
const directory = getSessionDirectory(session);
|
const directory = getSessionDirectory(session);
|
||||||
if (!directory) continue;
|
if (!directory) continue;
|
||||||
const node = nodes.find((entry) => {
|
const normalizedDirectory = normalizePath(directory);
|
||||||
if (pathBelongsToRoot(directory, entry.project.path)) return true;
|
const node = nodes.find((entry) => projectMatchesExactDirectory(entry.project, normalizedDirectory));
|
||||||
return entry.project.worktrees.some((worktree) => pathBelongsToRoot(directory, worktree.path));
|
|
||||||
});
|
|
||||||
if (!node) continue;
|
if (!node) continue;
|
||||||
const matchedWorktree = node.project.worktrees.find((entry) => pathBelongsToRoot(directory, entry.path));
|
const matchedWorktree = findExactWorktreeMatch(node.project, normalizedDirectory);
|
||||||
const bucket = matchedWorktree
|
const bucket = matchedWorktree
|
||||||
? ensureBucket(node, matchedWorktree.path, matchedWorktree)
|
? ensureBucket(node, matchedWorktree.path, matchedWorktree)
|
||||||
: ensureBucket(node, node.project.path, null);
|
: ensureBucket(node, node.project.path, null);
|
||||||
@@ -677,7 +689,9 @@ export const MobileSessionsSheet: React.FC<MobileSessionsSheetProps> = ({ open,
|
|||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
for (const bucket of node.buckets) {
|
for (const bucket of node.buckets) {
|
||||||
bucket.sessions.sort((a, b) => getSessionTimestamp(b) - getSessionTimestamp(a));
|
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<MobileSessionsSheetProps> = ({ open,
|
|||||||
// Switching session switches the working directory (handled by
|
// Switching session switches the working directory (handled by
|
||||||
// setCurrentSession) — also move the active project so the rest of the app
|
// setCurrentSession) — also move the active project so the rest of the app
|
||||||
// and the active highlight follow the selected session, not just the draft.
|
// and the active highlight follow the selected session, not just the draft.
|
||||||
const project = projectsMeta.find((entry) => {
|
const project = findExactProjectMatch(projectsMeta, directory ?? '');
|
||||||
if (pathBelongsToRoot(directory ?? '', entry.path)) return true;
|
|
||||||
return entry.worktrees.some((worktree) => pathBelongsToRoot(directory ?? '', worktree.path));
|
|
||||||
});
|
|
||||||
if (project) setActiveProjectIdOnly(project.id);
|
if (project) setActiveProjectIdOnly(project.id);
|
||||||
void setCurrentSession(session.id, directory);
|
void setCurrentSession(session.id, directory);
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
@@ -878,12 +889,9 @@ export const MobileSessionsSheet: React.FC<MobileSessionsSheetProps> = ({ open,
|
|||||||
const buildSessionContextLabel = React.useCallback(
|
const buildSessionContextLabel = React.useCallback(
|
||||||
(session: Session): string => {
|
(session: Session): string => {
|
||||||
const directory = getSessionDirectory(session);
|
const directory = getSessionDirectory(session);
|
||||||
const project = projectsMeta.find((entry) => {
|
const project = findExactProjectMatch(projectsMeta, directory);
|
||||||
if (pathBelongsToRoot(directory, entry.path)) return true;
|
|
||||||
return entry.worktrees.some((worktree) => pathBelongsToRoot(directory, worktree.path));
|
|
||||||
});
|
|
||||||
if (!project) return getProjectLabel(directory) || 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}`;
|
if (matchedWorktree?.branch) return `${project.label} · ${matchedWorktree.branch}`;
|
||||||
return project.label;
|
return project.label;
|
||||||
},
|
},
|
||||||
@@ -915,10 +923,7 @@ export const MobileSessionsSheet: React.FC<MobileSessionsSheetProps> = ({ open,
|
|||||||
return sessions
|
return sessions
|
||||||
.filter((session) => {
|
.filter((session) => {
|
||||||
const directory = getSessionDirectory(session);
|
const directory = getSessionDirectory(session);
|
||||||
const project = projectsMeta.find((entry) => {
|
const project = findExactProjectMatch(projectsMeta, directory);
|
||||||
if (pathBelongsToRoot(directory, entry.path)) return true;
|
|
||||||
return entry.worktrees.some((worktree) => pathBelongsToRoot(directory, worktree.path));
|
|
||||||
});
|
|
||||||
return sessionMatchesQuery(session, project?.label ?? '', normalizedQuery);
|
return sessionMatchesQuery(session, project?.label ?? '', normalizedQuery);
|
||||||
})
|
})
|
||||||
.sort((a, b) => getSessionTimestamp(b) - getSessionTimestamp(a));
|
.sort((a, b) => getSessionTimestamp(b) - getSessionTimestamp(a));
|
||||||
@@ -931,9 +936,9 @@ export const MobileSessionsSheet: React.FC<MobileSessionsSheetProps> = ({ open,
|
|||||||
.map((project) => ({
|
.map((project) => ({
|
||||||
...project,
|
...project,
|
||||||
sessionCount: sessions.filter((session) => {
|
sessionCount: sessions.filter((session) => {
|
||||||
const directory = getSessionDirectory(session);
|
if (getParentId(session)) return false;
|
||||||
if (pathBelongsToRoot(directory, project.path)) return true;
|
const directory = normalizePath(getSessionDirectory(session));
|
||||||
return project.worktrees.some((worktree) => pathBelongsToRoot(directory, worktree.path));
|
return projectMatchesExactDirectory(project, directory);
|
||||||
}).length,
|
}).length,
|
||||||
}));
|
}));
|
||||||
}, [normalizedQuery, projectsMeta, sessions]);
|
}, [normalizedQuery, projectsMeta, sessions]);
|
||||||
|
|||||||
Reference in New Issue
Block a user