From 505f9b9e8c65ba62f087500b3e6d5aa50a090b21 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Fri, 28 Aug 2026 20:06:02 +0300 Subject: [PATCH] feat(mobile): list managed Chats in the sessions sheet Chat sessions without a project were dropped from the mobile session tree because no registered project owned their directory. The sheet now partitions sessions like the desktop sidebar and shows Chats as a collapsible section above the project tree, with the same rows, swipe actions and paging; search results label them "Chats" instead of the raw directory name. Claude-Session: https://claude.ai/code/session_017TK5JAYDfT3Fotc23UEg98 --- packages/ui/src/apps/MobileSessionsSheet.tsx | 92 +++++++++++++++++-- .../session/sidebar/DOCUMENTATION.md | 9 +- packages/ui/src/lib/i18n/messages/de.ts | 1 + packages/ui/src/lib/i18n/messages/en.ts | 1 + packages/ui/src/lib/i18n/messages/es.ts | 1 + packages/ui/src/lib/i18n/messages/fr.ts | 1 + packages/ui/src/lib/i18n/messages/ja.ts | 1 + packages/ui/src/lib/i18n/messages/ko.ts | 1 + packages/ui/src/lib/i18n/messages/pl.ts | 1 + packages/ui/src/lib/i18n/messages/pt-BR.ts | 1 + packages/ui/src/lib/i18n/messages/tr.ts | 1 + packages/ui/src/lib/i18n/messages/uk.ts | 1 + packages/ui/src/lib/i18n/messages/zh-CN.ts | 1 + packages/ui/src/lib/i18n/messages/zh-TW.ts | 1 + 14 files changed, 100 insertions(+), 13 deletions(-) diff --git a/packages/ui/src/apps/MobileSessionsSheet.tsx b/packages/ui/src/apps/MobileSessionsSheet.tsx index a64d94f0..c10b4540 100644 --- a/packages/ui/src/apps/MobileSessionsSheet.tsx +++ b/packages/ui/src/apps/MobileSessionsSheet.tsx @@ -41,6 +41,8 @@ import { ScrollShadow } from '@/components/ui/ScrollShadow'; import { toast } from '@/components/ui'; import { useThemeSystem } from '@/contexts/useThemeSystem'; import { getProjectLabel, normalizePath } from './mobilePaths'; +import { CHAT_DRAFT_PROJECT_ID, isChatDirectoryPath } from '@/lib/chatDirectories'; +import { partitionSidebarSessions } from '@/components/session/sidebar/list/sessionCollection'; import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; import { useI18n } from '@/lib/i18n'; import { matchesRankQuery, rankByQuery } from '@/lib/search/fuzzySearch'; @@ -1022,6 +1024,27 @@ export const MobileSessionsSheet: React.FC = ({ open, return merged.filter((session) => !session.time?.archived); }, [globalActiveSessions, liveSessions]); + // Managed Chats (sessions under ~/.config/openchamber/chats) are not owned + // by any registered project; they get their own section above the project + // tree, the same split the desktop sidebar makes. Temporary /btw forks are + // dropped here as well. + const { projectSessions, chatSessions } = React.useMemo( + () => partitionSidebarSessions(sessions, false), + [sessions], + ); + const chatsBucket = React.useMemo(() => ({ + key: CHAT_DRAFT_PROJECT_ID, + label: '', + path: '', + worktree: null, + sessions: orderSessionsByLifecycleScopes(chatSessions, pinnedSessionIds, sessionOrderRanks), + }), [chatSessions, pinnedSessionIds, sessionOrderRanks]); + const chatsBucketKey = `${CHAT_DRAFT_PROJECT_ID}::${CHAT_DRAFT_PROJECT_ID}`; + const chatRootCount = React.useMemo( + () => chatSessions.filter((session) => !getParentId(session)).length, + [chatSessions], + ); + const normalizedQuery = query.trim().toLowerCase(); // On open, bring the current session (or at least its project) into view — @@ -1070,7 +1093,7 @@ export const MobileSessionsSheet: React.FC = ({ open, for (const worktree of node.project.worktrees) ensureBucket(node, worktree.path, worktree); } - for (const session of sessions) { + for (const session of projectSessions) { const directory = getSessionDirectory(session); if (!directory) continue; const normalizedDirectory = normalizePath(directory); @@ -1093,7 +1116,7 @@ export const MobileSessionsSheet: React.FC = ({ open, } return nodes; - }, [activeProjectId, pinnedSessionIds, projectsMeta, sessionOrderRanks, sessions]); + }, [activeProjectId, pinnedSessionIds, projectSessions, projectsMeta, sessionOrderRanks]); const normalizedDirectory = normalizePath(currentDirectory); @@ -1149,8 +1172,7 @@ export const MobileSessionsSheet: React.FC = ({ open, // Paginated, tree-aware list of a bucket's sessions: top-level sessions paginate, // and a parent with subsessions can be expanded to reveal its children (nested, // recursively). Pagination counts only top-level sessions. - const renderBucketSessions = (node: ProjectNode, bucket: WorktreeBucket, indent: number) => { - const bucketKey = `${node.project.id}::${bucket.key}`; + const renderBucketSessions = (bucketKey: string, bucket: WorktreeBucket, indent: number) => { // Group children by parent within this bucket, and treat sessions whose parent // is not in this bucket as top-level so nothing is hidden. @@ -1336,13 +1358,14 @@ export const MobileSessionsSheet: React.FC = ({ open, const buildSessionContextLabel = React.useCallback( (session: Session): string => { const directory = getSessionDirectory(session); + if (isChatDirectoryPath(directory)) return t('mobile.sessions.section.chats'); const project = findExactProjectMatch(projectsMeta, directory); if (!project) return getProjectLabel(directory) || directory; const matchedWorktree = findExactWorktreeMatch(project, normalizePath(directory)); if (matchedWorktree?.branch) return `${project.label} · ${matchedWorktree.branch}`; return project.label; }, - [projectsMeta], + [projectsMeta, t], ); const handleSelectProject = (project: ProjectMeta) => { @@ -1481,7 +1504,7 @@ export const MobileSessionsSheet: React.FC = ({ open, ) : null} - {projectsMeta.length === 0 ? ( + {projectsMeta.length === 0 && chatSessions.length === 0 ? ( = ({ open, ) : (
- {orderedNodes.map((node, nodeIndex) => { + {(() => { + const chatsExpanded = projectExpandedMap[CHAT_DRAFT_PROJECT_ID] ?? true; + const chatsLabel = t('mobile.sessions.section.chats'); + return ( +
+
+ +
+ {chatsExpanded ? ( +
+ {chatsBucket.sessions.length > 0 ? ( + renderBucketSessions(chatsBucketKey, chatsBucket, PROJECT_SESSION_INDENT) + ) : ( +

+ {t('sessions.sidebar.activity.chatsEmpty')} +

+ )} +
+ ) : null} +
+ ); + })()} + {orderedNodes.map((node) => { const projectExpanded = isProjectExpanded(node); const buckets = normalizedQuery ? node.buckets.filter((bucket) => @@ -1614,7 +1686,7 @@ export const MobileSessionsSheet: React.FC = ({ open, return (
0 && 'border-t border-border/70')} + className="border-t border-border/70" > = ({ open, return ( <> {rootBucket && rootBucket.sessions.length > 0 - ? renderBucketSessions(node, rootBucket, PROJECT_SESSION_INDENT) + ? renderBucketSessions(`${node.project.id}::${rootBucket.key}`, rootBucket, PROJECT_SESSION_INDENT) : null} {worktreeBuckets.map((bucket) => { const worktreeExpanded = isWorktreeExpanded(node, bucket); @@ -1787,7 +1859,7 @@ export const MobileSessionsSheet: React.FC = ({ open, {worktreeExpanded - ? renderBucketSessions(node, bucket, PROJECT_SESSION_INDENT) + ? renderBucketSessions(`${node.project.id}::${bucket.key}`, bucket, PROJECT_SESSION_INDENT) : null}
); diff --git a/packages/ui/src/components/session/sidebar/DOCUMENTATION.md b/packages/ui/src/components/session/sidebar/DOCUMENTATION.md index 68f2fba7..5daf7f68 100644 --- a/packages/ui/src/components/session/sidebar/DOCUMENTATION.md +++ b/packages/ui/src/components/session/sidebar/DOCUMENTATION.md @@ -42,9 +42,12 @@ existing data; it is never treated as an authoritative empty list. Web and desktop show managed Chats before optional Recent activity. Chats use their shared managed root for folders and never expose worktree actions. Project -display can be all projects or one selected project. VS Code excludes worktrees -and managed Chats, while retaining its workspace-scoped grouped list and inline -archived buckets. +display can be all projects or one selected project. The mobile sessions sheet +(`apps/MobileSessionsSheet.tsx`) partitions the same way through +`partitionSidebarSessions` and lists Chats as a collapsible section above the +project tree, with no Recent projection. VS Code excludes worktrees and managed +Chats, while retaining its workspace-scoped grouped list and inline archived +buckets. Directory demand always includes known project roots and worktrees. Visibility only changes priority. Row mounts must not start bootstrap work. Selection and diff --git a/packages/ui/src/lib/i18n/messages/de.ts b/packages/ui/src/lib/i18n/messages/de.ts index fa4074d6..6da29800 100644 --- a/packages/ui/src/lib/i18n/messages/de.ts +++ b/packages/ui/src/lib/i18n/messages/de.ts @@ -103,6 +103,7 @@ export const dict = { 'mobile.sessions.section.worktrees': 'Worktrees', 'mobile.sessions.section.otherProjects': 'Projekt wechseln', 'mobile.sessions.section.projects': 'Projekte', + 'mobile.sessions.section.chats': 'Chats', 'mobile.sessions.empty.noProjectsTitle': 'Noch keine Projekte', 'mobile.sessions.empty.noProjectsDescription': 'Füge ein Projekt hinzu, um mit deinem Code zu chatten.', 'mobile.sessions.empty.noSessionsTitle': 'Noch keine Sitzungen', diff --git a/packages/ui/src/lib/i18n/messages/en.ts b/packages/ui/src/lib/i18n/messages/en.ts index 6279f4fd..4bf7cc24 100644 --- a/packages/ui/src/lib/i18n/messages/en.ts +++ b/packages/ui/src/lib/i18n/messages/en.ts @@ -130,6 +130,7 @@ export const dict = { 'mobile.sessions.section.worktrees': 'Worktrees', 'mobile.sessions.section.otherProjects': 'Switch project', 'mobile.sessions.section.projects': 'Projects', + 'mobile.sessions.section.chats': 'Chats', 'mobile.sessions.empty.noProjectsTitle': 'No projects yet', 'mobile.sessions.empty.noProjectsDescription': 'Add a project to start chatting with your code.', 'mobile.sessions.empty.noSessionsTitle': 'No sessions yet', diff --git a/packages/ui/src/lib/i18n/messages/es.ts b/packages/ui/src/lib/i18n/messages/es.ts index 38eff40c..30cbbe22 100644 --- a/packages/ui/src/lib/i18n/messages/es.ts +++ b/packages/ui/src/lib/i18n/messages/es.ts @@ -131,6 +131,7 @@ export const dict: Record = { "mobile.sessions.section.worktrees": "Worktrees", "mobile.sessions.section.otherProjects": "Cambiar de proyecto", "mobile.sessions.section.projects": "Proyectos", + "mobile.sessions.section.chats": "Chats", "mobile.sessions.empty.noProjectsTitle": "Sin proyectos", "mobile.sessions.empty.noProjectsDescription": "Agrega un proyecto para empezar a chatear con tu código.", "mobile.sessions.empty.noSessionsTitle": "Sin sesiones", diff --git a/packages/ui/src/lib/i18n/messages/fr.ts b/packages/ui/src/lib/i18n/messages/fr.ts index d77d52ce..4ad68386 100644 --- a/packages/ui/src/lib/i18n/messages/fr.ts +++ b/packages/ui/src/lib/i18n/messages/fr.ts @@ -2918,6 +2918,7 @@ export const dict = { 'mobile.sessions.section.worktrees': 'Worktrees', 'mobile.sessions.section.otherProjects': 'Changer de projet', 'mobile.sessions.section.projects': 'Projets', + 'mobile.sessions.section.chats': 'Discussions', 'mobile.sessions.empty.noProjectsTitle': 'Aucun projet pour le moment', 'mobile.sessions.empty.noProjectsDescription': 'Ajoutez un projet pour commencer à discuter avec votre code.', 'mobile.sessions.empty.noSessionsTitle': 'Aucune session pour le moment', diff --git a/packages/ui/src/lib/i18n/messages/ja.ts b/packages/ui/src/lib/i18n/messages/ja.ts index a84c550b..3f58af13 100644 --- a/packages/ui/src/lib/i18n/messages/ja.ts +++ b/packages/ui/src/lib/i18n/messages/ja.ts @@ -131,6 +131,7 @@ export const dict: Record = { 'mobile.sessions.section.worktrees': 'ワークツリー', 'mobile.sessions.section.otherProjects': 'プロジェクトを切り替え', 'mobile.sessions.section.projects': 'プロジェクト', + 'mobile.sessions.section.chats': 'チャット', 'mobile.sessions.empty.noProjectsTitle': 'まだプロジェクトがありません', 'mobile.sessions.empty.noProjectsDescription': 'プロジェクトを追加してコードとチャットを始めましょう。', 'mobile.sessions.empty.noSessionsTitle': 'まだセッションがありません', diff --git a/packages/ui/src/lib/i18n/messages/ko.ts b/packages/ui/src/lib/i18n/messages/ko.ts index 1b9efd89..f09dede4 100644 --- a/packages/ui/src/lib/i18n/messages/ko.ts +++ b/packages/ui/src/lib/i18n/messages/ko.ts @@ -131,6 +131,7 @@ export const dict: Record = { 'mobile.sessions.section.worktrees': '워크트리', 'mobile.sessions.section.otherProjects': '프로젝트 전환', 'mobile.sessions.section.projects': '프로젝트', + 'mobile.sessions.section.chats': '채팅', 'mobile.sessions.empty.noProjectsTitle': '프로젝트 없음', 'mobile.sessions.empty.noProjectsDescription': '코드와 채팅을 시작하려면 프로젝트를 추가하세요.', 'mobile.sessions.empty.noSessionsTitle': '세션 없음', diff --git a/packages/ui/src/lib/i18n/messages/pl.ts b/packages/ui/src/lib/i18n/messages/pl.ts index bdf8e565..cb2003d7 100644 --- a/packages/ui/src/lib/i18n/messages/pl.ts +++ b/packages/ui/src/lib/i18n/messages/pl.ts @@ -132,6 +132,7 @@ export const dict: Record = { 'mobile.sessions.section.worktrees': 'Worktrees', 'mobile.sessions.section.otherProjects': 'Zmień projekt', 'mobile.sessions.section.projects': 'Projekty', + 'mobile.sessions.section.chats': 'Czaty', 'mobile.sessions.empty.noProjectsTitle': 'Brak projektów', 'mobile.sessions.empty.noProjectsDescription': 'Dodaj projekt, aby zacząć rozmawiać ze swoim kodem.', 'mobile.sessions.empty.noSessionsTitle': 'Brak sesji', diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.ts b/packages/ui/src/lib/i18n/messages/pt-BR.ts index a3da93ae..a4c91f42 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.ts @@ -131,6 +131,7 @@ export const dict: Record = { "mobile.sessions.section.worktrees": "Worktrees", "mobile.sessions.section.otherProjects": "Trocar de projeto", "mobile.sessions.section.projects": "Projetos", + "mobile.sessions.section.chats": "Conversas", "mobile.sessions.empty.noProjectsTitle": "Sem projetos", "mobile.sessions.empty.noProjectsDescription": "Adicione um projeto para começar a conversar com seu código.", "mobile.sessions.empty.noSessionsTitle": "Sem sessões", diff --git a/packages/ui/src/lib/i18n/messages/tr.ts b/packages/ui/src/lib/i18n/messages/tr.ts index ad200dd2..2585b96f 100644 --- a/packages/ui/src/lib/i18n/messages/tr.ts +++ b/packages/ui/src/lib/i18n/messages/tr.ts @@ -117,6 +117,7 @@ export const dict = { 'mobile.sessions.section.worktrees': 'Worktree\'ler', 'mobile.sessions.section.otherProjects': 'Proje değiştir', 'mobile.sessions.section.projects': 'Projeler', + 'mobile.sessions.section.chats': 'Sohbetler', 'mobile.sessions.empty.noProjectsTitle': 'Henüz proje yok', 'mobile.sessions.empty.noProjectsDescription': 'Kodunuzla sohbet etmeye başlamak için bir proje ekleyin.', 'mobile.sessions.empty.noSessionsTitle': 'Henüz session yok', diff --git a/packages/ui/src/lib/i18n/messages/uk.ts b/packages/ui/src/lib/i18n/messages/uk.ts index 2e5e3272..35bd94a5 100644 --- a/packages/ui/src/lib/i18n/messages/uk.ts +++ b/packages/ui/src/lib/i18n/messages/uk.ts @@ -131,6 +131,7 @@ export const dict: Record = { "mobile.sessions.section.worktrees": "Worktrees", "mobile.sessions.section.otherProjects": "Інші проєкти", "mobile.sessions.section.projects": "Проєкти", + "mobile.sessions.section.chats": "Чати", "mobile.sessions.empty.noProjectsTitle": "Ще немає проєктів", "mobile.sessions.empty.noProjectsDescription": "Додай проєкт, щоб почати спілкування з кодом.", "mobile.sessions.empty.noSessionsTitle": "Ще немає сесій", diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.ts b/packages/ui/src/lib/i18n/messages/zh-CN.ts index 1019c0fe..c15eed5e 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.ts @@ -131,6 +131,7 @@ export const dict: Record = { 'mobile.sessions.section.worktrees': '工作树', 'mobile.sessions.section.otherProjects': '切换项目', 'mobile.sessions.section.projects': '项目', + 'mobile.sessions.section.chats': '聊天', 'mobile.sessions.empty.noProjectsTitle': '暂无项目', 'mobile.sessions.empty.noProjectsDescription': '添加项目以开始与代码对话。', 'mobile.sessions.empty.noSessionsTitle': '暂无会话', diff --git a/packages/ui/src/lib/i18n/messages/zh-TW.ts b/packages/ui/src/lib/i18n/messages/zh-TW.ts index 887bfedc..3f440ad1 100644 --- a/packages/ui/src/lib/i18n/messages/zh-TW.ts +++ b/packages/ui/src/lib/i18n/messages/zh-TW.ts @@ -131,6 +131,7 @@ export const dict: Record = { 'mobile.sessions.section.worktrees': '工作樹', 'mobile.sessions.section.otherProjects': '切換專案', 'mobile.sessions.section.projects': '專案', + 'mobile.sessions.section.chats': '聊天', 'mobile.sessions.empty.noProjectsTitle': '尚無專案', 'mobile.sessions.empty.noProjectsDescription': '新增專案即可開始與程式碼聊天。', 'mobile.sessions.empty.noSessionsTitle': '尚無會話',