From 57dd868f2913304c887f13daaa24266acafa51ca Mon Sep 17 00:00:00 2001 From: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Date: Wed, 3 Jun 2026 23:08:03 -0400 Subject: [PATCH] Bucket chat threads by source (#3330) --- app/src/components/PillTabBar.tsx | 4 +- .../intelligence/IntelligenceTasksTab.tsx | 2 +- .../__tests__/IntelligenceTasksTab.test.tsx | 8 +- app/src/lib/i18n/ar.ts | 7 +- app/src/lib/i18n/bn.ts | 7 +- app/src/lib/i18n/de.ts | 7 +- app/src/lib/i18n/en.ts | 7 +- app/src/lib/i18n/es.ts | 7 +- app/src/lib/i18n/fr.ts | 7 +- app/src/lib/i18n/hi.ts | 7 +- app/src/lib/i18n/id.ts | 7 +- app/src/lib/i18n/it.ts | 7 +- app/src/lib/i18n/ko.ts | 7 +- app/src/lib/i18n/pl.ts | 7 +- app/src/lib/i18n/pt.ts | 7 +- app/src/lib/i18n/ru.ts | 7 +- app/src/lib/i18n/zh-CN.ts | 7 +- app/src/pages/Conversations.tsx | 60 ++++----- .../__tests__/Conversations.render.test.tsx | 64 ++++----- .../conversations/utils/threadFilter.test.ts | 124 ++++++++++-------- .../pages/conversations/utils/threadFilter.ts | 59 ++++----- .../tools/spawn_subagent.rs | 6 +- .../tools/spawn_worker_thread.rs | 12 +- .../tools/tools_e2e_tests.rs | 2 +- .../tools/worker_thread.rs | 8 +- src/openhuman/memory_conversations/store.rs | 9 +- .../memory_conversations/store_tests.rs | 39 +++++- src/openhuman/subconscious/engine.rs | 2 +- src/openhuman/subconscious/schemas.rs | 2 +- 29 files changed, 248 insertions(+), 251 deletions(-) diff --git a/app/src/components/PillTabBar.tsx b/app/src/components/PillTabBar.tsx index 51571e5fe..a26b7db36 100644 --- a/app/src/components/PillTabBar.tsx +++ b/app/src/components/PillTabBar.tsx @@ -9,6 +9,7 @@ interface PillTabBarProps { activeClassName?: string; containerClassName?: string; inactiveClassName?: string; + itemClassName?: string; items: PillTabBarItem[]; onChange: (value: T) => void; renderItem?: (item: PillTabBarItem, active: boolean) => ReactNode; @@ -19,6 +20,7 @@ export default function PillTabBar({ activeClassName = 'border-primary-200 dark:border-primary-500/40 bg-primary-50 dark:bg-primary-500/15 text-primary-700 dark:text-primary-300', containerClassName = 'flex gap-2 overflow-x-auto pb-1 scrollbar-hide', inactiveClassName = 'border-stone-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 text-stone-600 dark:text-neutral-300 hover:bg-stone-50 dark:hover:bg-neutral-800/60', + itemClassName = 'px-3', items, onChange, renderItem, @@ -39,7 +41,7 @@ export default function PillTabBar({ aria-selected={active} tabIndex={active ? 0 : -1} onClick={() => onChange(item.value)} - className={`flex-shrink-0 rounded-full border px-3 py-1 text-xs font-medium transition-colors ${ + className={`flex-shrink-0 rounded-full border py-1 text-xs font-medium transition-colors ${itemClassName} ${ active ? activeClassName : inactiveClassName }`}> {renderItem ? renderItem(item, active) : item.label} diff --git a/app/src/components/intelligence/IntelligenceTasksTab.tsx b/app/src/components/intelligence/IntelligenceTasksTab.tsx index 4fef3d3f3..406cab331 100644 --- a/app/src/components/intelligence/IntelligenceTasksTab.tsx +++ b/app/src/components/intelligence/IntelligenceTasksTab.tsx @@ -49,7 +49,7 @@ import type { TaskBoard, TaskBoardCard, TaskBoardCardStatus } from '../../types/ import { UserTaskComposer } from './UserTaskComposer'; const log = debug('intelligence:tasks'); -const AGENT_TASK_THREAD_LABEL = 'agent-task'; +const AGENT_TASK_THREAD_LABEL = 'tasks'; const CHAT_MODEL_ID = 'reasoning-v1'; interface ThreadTaskBoard { diff --git a/app/src/components/intelligence/__tests__/IntelligenceTasksTab.test.tsx b/app/src/components/intelligence/__tests__/IntelligenceTasksTab.test.tsx index 3d00c7fde..cebae12ac 100644 --- a/app/src/components/intelligence/__tests__/IntelligenceTasksTab.test.tsx +++ b/app/src/components/intelligence/__tests__/IntelligenceTasksTab.test.tsx @@ -182,7 +182,7 @@ describe('IntelligenceTasksTab', () => { hoisted.createNewThread.mockResolvedValue({ id: 'thread-agent-task', title: 'Agent task', - labels: ['agent-task'], + labels: ['tasks'], chatId: null, isActive: true, messageCount: 0, @@ -192,7 +192,7 @@ describe('IntelligenceTasksTab', () => { hoisted.updateTitle.mockResolvedValue({ id: 'thread-agent-task', title: 'Agent task: My personal task', - labels: ['agent-task'], + labels: ['tasks'], chatId: null, isActive: true, messageCount: 0, @@ -407,7 +407,7 @@ describe('IntelligenceTasksTab', () => { expect(hoisted.todosUpdateStatus).toHaveBeenCalledWith('user-tasks', 'card-0', 'in_progress'); }); - test('starts a labeled agent-task thread from a personal task', async () => { + test('starts a labeled Tasks thread from a personal task', async () => { hoisted.todosList.mockImplementation((threadId: string) => Promise.resolve( threadId === 'user-tasks' @@ -445,7 +445,7 @@ describe('IntelligenceTasksTab', () => { fireEvent.click(screen.getByText('stub-work-task')); - await waitFor(() => expect(hoisted.createNewThread).toHaveBeenCalledWith(['agent-task'])); + await waitFor(() => expect(hoisted.createNewThread).toHaveBeenCalledWith(['tasks'])); expect(hoisted.updateTitle).toHaveBeenCalledWith( 'thread-agent-task', 'Agent task: Implement task source worker' diff --git a/app/src/lib/i18n/ar.ts b/app/src/lib/i18n/ar.ts index 31bf9a290..4e18876c7 100644 --- a/app/src/lib/i18n/ar.ts +++ b/app/src/lib/i18n/ar.ts @@ -1681,16 +1681,13 @@ const messages: TranslationMap = { 'common.disable': 'تعطيل', 'common.enable': 'تفعيل', 'chat.safetyTimeout': 'لا استجابة من الوكيل بعد دقيقتين. حاول مرة أخرى أو تحقق من اتصالك.', - 'chat.filter.all': 'الكل', 'chat.filter.general': 'عام', - 'chat.filter.briefing': 'الإحاطة', - 'chat.filter.notification': 'الإشعار', - 'chat.filter.workers': 'العمال', + 'chat.filter.subconscious': 'اللاوعي', + 'chat.filter.tasks': 'المهام', 'chat.selectThread': 'اختر محادثة', 'chat.threads': 'المحادثات', 'chat.noThreads': 'لا توجد محادثات بعد', 'chat.noLabelThreads': 'لا توجد محادثات "{label}"', - 'chat.noWorkerThreads': 'لا توجد محادثات عمال بعد', 'chat.deleteThread': 'حذف المحادثة', 'chat.deleteThreadConfirm': 'هل أنت متأكد من أنك تريد حذف "{title}"؟', 'chat.untitledThread': 'محادثة بلا عنوان', diff --git a/app/src/lib/i18n/bn.ts b/app/src/lib/i18n/bn.ts index e7f95a789..a078099c6 100644 --- a/app/src/lib/i18n/bn.ts +++ b/app/src/lib/i18n/bn.ts @@ -1715,16 +1715,13 @@ const messages: TranslationMap = { 'common.enable': 'সক্রিয় করুন', 'chat.safetyTimeout': '২ মিনিট পরেও এজেন্টের কোনো সাড়া নেই। আবার চেষ্টা করুন বা সংযোগ পরীক্ষা করুন।', - 'chat.filter.all': 'সব', 'chat.filter.general': 'সাধারণ', - 'chat.filter.briefing': 'ব্রিফিং', - 'chat.filter.notification': 'বিজ্ঞপ্তি', - 'chat.filter.workers': 'ওয়ার্কার', + 'chat.filter.subconscious': 'সাবকনশাস', + 'chat.filter.tasks': 'টাস্ক', 'chat.selectThread': 'একটি থ্রেড বেছে নিন', 'chat.threads': 'থ্রেড', 'chat.noThreads': 'এখনো কোনো থ্রেড নেই', 'chat.noLabelThreads': '"{label}" থ্রেড নেই', - 'chat.noWorkerThreads': 'এখনো কোনো ওয়ার্কার থ্রেড নেই', 'chat.deleteThread': 'থ্রেড মুছুন', 'chat.deleteThreadConfirm': 'আপনি কি "{title}" মুছতে চান?', 'chat.untitledThread': 'শিরোনামহীন থ্রেড', diff --git a/app/src/lib/i18n/de.ts b/app/src/lib/i18n/de.ts index 17e3007e1..140208c58 100644 --- a/app/src/lib/i18n/de.ts +++ b/app/src/lib/i18n/de.ts @@ -1760,16 +1760,13 @@ const messages: TranslationMap = { 'common.enable': 'Aktivieren', 'chat.safetyTimeout': 'Keine Antwort vom Agenten nach 2 Minuten. Versuche es erneut oder prüfe deine Verbindung.', - 'chat.filter.all': 'Alle', 'chat.filter.general': 'Allgemein', - 'chat.filter.briefing': 'Briefing', - 'chat.filter.notification': 'Benachrichtigung', - 'chat.filter.workers': 'Arbeiter', + 'chat.filter.subconscious': 'Unterbewusstsein', + 'chat.filter.tasks': 'Aufgaben', 'chat.selectThread': 'Wähle einen Thread aus', 'chat.threads': 'Themen', 'chat.noThreads': 'Noch keine Threads', 'chat.noLabelThreads': 'Keine „{label}“-Threads', - 'chat.noWorkerThreads': 'Noch keine Arbeitsthreads', 'chat.deleteThread': 'Thread löschen', 'chat.deleteThreadConfirm': 'Bist du sicher, dass du „{title}“ löschen möchtest?', 'chat.untitledThread': 'Thread ohne Titel', diff --git a/app/src/lib/i18n/en.ts b/app/src/lib/i18n/en.ts index d94d5aef1..2fc4ac0f4 100644 --- a/app/src/lib/i18n/en.ts +++ b/app/src/lib/i18n/en.ts @@ -1963,16 +1963,13 @@ const en: TranslationMap = { // Chat (additional) 'chat.safetyTimeout': 'No response from the agent after 2 minutes. Try again or check your connection.', - 'chat.filter.all': 'All', 'chat.filter.general': 'General', - 'chat.filter.briefing': 'Briefing', - 'chat.filter.notification': 'Notification', - 'chat.filter.workers': 'Workers', + 'chat.filter.subconscious': 'Subconscious', + 'chat.filter.tasks': 'Tasks', 'chat.selectThread': 'Select a thread', 'chat.threads': 'Threads', 'chat.noThreads': 'No threads yet', 'chat.noLabelThreads': 'No "{label}" threads', - 'chat.noWorkerThreads': 'No worker threads yet', 'chat.deleteThread': 'Delete thread', 'chat.deleteThreadConfirm': 'Are you sure you want to delete "{title}"?', 'chat.untitledThread': 'Untitled thread', diff --git a/app/src/lib/i18n/es.ts b/app/src/lib/i18n/es.ts index d95078847..04ab1b284 100644 --- a/app/src/lib/i18n/es.ts +++ b/app/src/lib/i18n/es.ts @@ -1754,16 +1754,13 @@ const messages: TranslationMap = { 'common.enable': 'Activar', 'chat.safetyTimeout': 'Sin respuesta del agente después de 2 minutos. Intenta de nuevo o verifica tu conexión.', - 'chat.filter.all': 'Todos', 'chat.filter.general': 'General', - 'chat.filter.briefing': 'Resumen', - 'chat.filter.notification': 'Notificación', - 'chat.filter.workers': 'Trabajadores', + 'chat.filter.subconscious': 'Subconsciente', + 'chat.filter.tasks': 'Tareas', 'chat.selectThread': 'Selecciona un hilo', 'chat.threads': 'Hilos', 'chat.noThreads': 'Sin hilos aún', 'chat.noLabelThreads': 'Sin hilos "{label}"', - 'chat.noWorkerThreads': 'Sin hilos de worker aún', 'chat.deleteThread': 'Eliminar hilo', 'chat.deleteThreadConfirm': '¿Seguro que quieres eliminar "{title}"?', 'chat.untitledThread': 'Hilo sin título', diff --git a/app/src/lib/i18n/fr.ts b/app/src/lib/i18n/fr.ts index 15f48ad92..d2bff47e8 100644 --- a/app/src/lib/i18n/fr.ts +++ b/app/src/lib/i18n/fr.ts @@ -1758,16 +1758,13 @@ const messages: TranslationMap = { 'common.enable': 'Activer', 'chat.safetyTimeout': "Aucune réponse de l'agent après 2 minutes. Réessaie ou vérifie ta connexion.", - 'chat.filter.all': 'Tous', 'chat.filter.general': 'Général', - 'chat.filter.briefing': 'Briefing', - 'chat.filter.notification': 'Notification', - 'chat.filter.workers': 'Travailleurs', + 'chat.filter.subconscious': 'Subconscient', + 'chat.filter.tasks': 'Tâches', 'chat.selectThread': 'Sélectionne un fil', 'chat.threads': 'Fils', 'chat.noThreads': "Aucun fil pour l'instant", 'chat.noLabelThreads': 'Aucun fil « {label} »', - 'chat.noWorkerThreads': "Aucun fil worker pour l'instant", 'chat.deleteThread': 'Supprimer le fil', 'chat.deleteThreadConfirm': 'Es-tu sûr de vouloir supprimer « {title} » ?', 'chat.untitledThread': 'Fil sans titre', diff --git a/app/src/lib/i18n/hi.ts b/app/src/lib/i18n/hi.ts index 24a075758..bbb1a5850 100644 --- a/app/src/lib/i18n/hi.ts +++ b/app/src/lib/i18n/hi.ts @@ -1716,16 +1716,13 @@ const messages: TranslationMap = { 'common.enable': 'चालू करें', 'chat.safetyTimeout': '2 मिनट बाद भी एजेंट से कोई जवाब नहीं मिला। दोबारा कोशिश करें या अपना कनेक्शन चेक करें।', - 'chat.filter.all': 'सभी', 'chat.filter.general': 'सामान्य', - 'chat.filter.briefing': 'ब्रीफिंग', - 'chat.filter.notification': 'नोटिफिकेशन', - 'chat.filter.workers': 'वर्कर्स', + 'chat.filter.subconscious': 'सबकॉन्शस', + 'chat.filter.tasks': 'टास्क', 'chat.selectThread': 'एक थ्रेड चुनें', 'chat.threads': 'थ्रेड्स', 'chat.noThreads': 'अभी कोई थ्रेड नहीं', 'chat.noLabelThreads': 'कोई "{label}" थ्रेड नहीं', - 'chat.noWorkerThreads': 'अभी कोई वर्कर थ्रेड नहीं', 'chat.deleteThread': 'थ्रेड डिलीट करें', 'chat.deleteThreadConfirm': 'क्या आप वाकई "{title}" डिलीट करना चाहते हैं?', 'chat.untitledThread': 'बिना शीर्षक की थ्रेड', diff --git a/app/src/lib/i18n/id.ts b/app/src/lib/i18n/id.ts index 6eff6e3c6..1206d3820 100644 --- a/app/src/lib/i18n/id.ts +++ b/app/src/lib/i18n/id.ts @@ -1718,16 +1718,13 @@ const messages: TranslationMap = { 'common.disable': 'Nonaktifkan', 'common.enable': 'Aktifkan', 'chat.safetyTimeout': 'Tidak ada respons dari agen setelah 2 menit. Coba lagi atau cek koneksi.', - 'chat.filter.all': 'Semua', 'chat.filter.general': 'Umum', - 'chat.filter.briefing': 'Ringkasan', - 'chat.filter.notification': 'Notifikasi', - 'chat.filter.workers': 'Worker', + 'chat.filter.subconscious': 'Bawah sadar', + 'chat.filter.tasks': 'Tugas', 'chat.selectThread': 'Pilih thread', 'chat.threads': 'Thread', 'chat.noThreads': 'Belum ada thread', 'chat.noLabelThreads': 'Tidak ada thread "{label}"', - 'chat.noWorkerThreads': 'Belum ada thread worker', 'chat.deleteThread': 'Hapus thread', 'chat.deleteThreadConfirm': 'Yakin ingin menghapus "{title}"?', 'chat.untitledThread': 'Thread tanpa judul', diff --git a/app/src/lib/i18n/it.ts b/app/src/lib/i18n/it.ts index adb62c3f2..419e69e87 100644 --- a/app/src/lib/i18n/it.ts +++ b/app/src/lib/i18n/it.ts @@ -1744,16 +1744,13 @@ const messages: TranslationMap = { 'common.enable': 'Abilita', 'chat.safetyTimeout': "Nessuna risposta dall'agente dopo 2 minuti. Riprova o controlla la connessione.", - 'chat.filter.all': 'Tutti', 'chat.filter.general': 'Generale', - 'chat.filter.briefing': 'Briefing', - 'chat.filter.notification': 'Notifica', - 'chat.filter.workers': 'Worker', + 'chat.filter.subconscious': 'Subconscio', + 'chat.filter.tasks': 'Attività', 'chat.selectThread': 'Seleziona un thread', 'chat.threads': 'Thread', 'chat.noThreads': 'Nessun thread', 'chat.noLabelThreads': 'Nessun thread "{label}"', - 'chat.noWorkerThreads': 'Nessun thread worker', 'chat.deleteThread': 'Elimina thread', 'chat.deleteThreadConfirm': 'Sei sicuro di voler eliminare "{title}"?', 'chat.untitledThread': 'Thread senza titolo', diff --git a/app/src/lib/i18n/ko.ts b/app/src/lib/i18n/ko.ts index 5ab29ea98..27b1bdf3a 100644 --- a/app/src/lib/i18n/ko.ts +++ b/app/src/lib/i18n/ko.ts @@ -1696,16 +1696,13 @@ const messages: TranslationMap = { 'common.disable': '비활성화', 'common.enable': '활성화', 'chat.safetyTimeout': '2분 후에도 에이전트의 응답이 없습니다. 다시 시도하거나 연결을 확인하세요.', - 'chat.filter.all': '전체', 'chat.filter.general': '일반', - 'chat.filter.briefing': '브리핑', - 'chat.filter.notification': '알림', - 'chat.filter.workers': '워커', + 'chat.filter.subconscious': '잠재의식', + 'chat.filter.tasks': '작업', 'chat.selectThread': '스레드 선택', 'chat.threads': '스레드', 'chat.noThreads': '아직 스레드가 없습니다', 'chat.noLabelThreads': '"{label}" 스레드가 없습니다', - 'chat.noWorkerThreads': '아직 워커 스레드가 없습니다', 'chat.deleteThread': '스레드 삭제', 'chat.deleteThreadConfirm': '"{title}" 스레드를 삭제하시겠습니까?', 'chat.untitledThread': '제목 없는 스레드', diff --git a/app/src/lib/i18n/pl.ts b/app/src/lib/i18n/pl.ts index f7102666e..0b7b0bcee 100644 --- a/app/src/lib/i18n/pl.ts +++ b/app/src/lib/i18n/pl.ts @@ -1737,16 +1737,13 @@ const messages: TranslationMap = { 'common.enable': 'Włącz', 'chat.safetyTimeout': 'Brak odpowiedzi agenta po 2 minutach. Spróbuj ponownie lub sprawdź połączenie.', - 'chat.filter.all': 'Wszystkie', 'chat.filter.general': 'Ogólne', - 'chat.filter.briefing': 'Briefing', - 'chat.filter.notification': 'Powiadomienia', - 'chat.filter.workers': 'Workery', + 'chat.filter.subconscious': 'Podświadomość', + 'chat.filter.tasks': 'Zadania', 'chat.selectThread': 'Wybierz wątek', 'chat.threads': 'Wątki', 'chat.noThreads': 'Brak wątków', 'chat.noLabelThreads': 'Brak wątków „{label}”', - 'chat.noWorkerThreads': 'Brak wątków workerów', 'chat.deleteThread': 'Usuń wątek', 'chat.deleteThreadConfirm': 'Czy na pewno chcesz usunąć „{title}”?', 'chat.untitledThread': 'Wątek bez tytułu', diff --git a/app/src/lib/i18n/pt.ts b/app/src/lib/i18n/pt.ts index d00f95440..1cb0afc74 100644 --- a/app/src/lib/i18n/pt.ts +++ b/app/src/lib/i18n/pt.ts @@ -1752,16 +1752,13 @@ const messages: TranslationMap = { 'common.enable': 'Ativar', 'chat.safetyTimeout': 'Nenhuma resposta do agente após 2 minutos. Tente novamente ou verifique sua conexão.', - 'chat.filter.all': 'Todos', 'chat.filter.general': 'Geral', - 'chat.filter.briefing': 'Resumo', - 'chat.filter.notification': 'Notificação', - 'chat.filter.workers': 'Trabalhadores', + 'chat.filter.subconscious': 'Subconsciente', + 'chat.filter.tasks': 'Tarefas', 'chat.selectThread': 'Selecione uma conversa', 'chat.threads': 'Conversas', 'chat.noThreads': 'Nenhuma conversa ainda', 'chat.noLabelThreads': 'Nenhuma conversa "{label}"', - 'chat.noWorkerThreads': 'Nenhuma thread de worker ainda', 'chat.deleteThread': 'Excluir conversa', 'chat.deleteThreadConfirm': 'Tem certeza de que deseja excluir "{title}"?', 'chat.untitledThread': 'Conversa sem título', diff --git a/app/src/lib/i18n/ru.ts b/app/src/lib/i18n/ru.ts index d497fde9c..c4d0bbbf6 100644 --- a/app/src/lib/i18n/ru.ts +++ b/app/src/lib/i18n/ru.ts @@ -1729,16 +1729,13 @@ const messages: TranslationMap = { 'common.enable': 'Включить', 'chat.safetyTimeout': 'Агент не ответил в течение 2 минут. Попробуй снова или проверь соединение.', - 'chat.filter.all': 'Все', 'chat.filter.general': 'Общее', - 'chat.filter.briefing': 'Брифинг', - 'chat.filter.notification': 'Уведомление', - 'chat.filter.workers': 'Воркеры', + 'chat.filter.subconscious': 'Подсознание', + 'chat.filter.tasks': 'Задачи', 'chat.selectThread': 'Выбери чат', 'chat.threads': 'Чаты', 'chat.noThreads': 'Чатов пока нет', 'chat.noLabelThreads': 'Нет чатов «{label}»', - 'chat.noWorkerThreads': 'Чатов воркеров пока нет', 'chat.deleteThread': 'Удалить чат', 'chat.deleteThreadConfirm': 'Удалить «{title}»?', 'chat.untitledThread': 'Чат без названия', diff --git a/app/src/lib/i18n/zh-CN.ts b/app/src/lib/i18n/zh-CN.ts index a45e92520..2e5d18a7c 100644 --- a/app/src/lib/i18n/zh-CN.ts +++ b/app/src/lib/i18n/zh-CN.ts @@ -1621,16 +1621,13 @@ const messages: TranslationMap = { 'common.disable': '禁用', 'common.enable': '启用', 'chat.safetyTimeout': '助手 2 分钟内未响应。请重试或检查你的连接。', - 'chat.filter.all': '全部', 'chat.filter.general': '常规', - 'chat.filter.briefing': '简报', - 'chat.filter.notification': '通知', - 'chat.filter.workers': '工作线程', + 'chat.filter.subconscious': '潜意识', + 'chat.filter.tasks': '任务', 'chat.selectThread': '选择一个对话', 'chat.threads': '对话列表', 'chat.noThreads': '暂无对话', 'chat.noLabelThreads': '此标签下暂无对话', - 'chat.noWorkerThreads': '暂无工作线程', 'chat.deleteThread': '删除对话', 'chat.deleteThreadConfirm': '确定要删除"{title}"吗?', 'chat.untitledThread': '未命名对话', diff --git a/app/src/pages/Conversations.tsx b/app/src/pages/Conversations.tsx index 702fbe943..53b0f68d2 100644 --- a/app/src/pages/Conversations.tsx +++ b/app/src/pages/Conversations.tsx @@ -100,7 +100,8 @@ import { import { GENERAL_TAB_VALUE, isThreadVisibleInTab, - WORKERS_TAB_VALUE, + SUBCONSCIOUS_TAB_VALUE, + TASKS_TAB_VALUE, } from './conversations/utils/threadFilter'; // Chat uses the reasoning model; `agentic-v1` is reserved for sub-agents @@ -255,6 +256,7 @@ const Conversations = ({ const [editingTitle, setEditingTitle] = useState(false); const [editTitleValue, setEditTitleValue] = useState(''); const editTitleInputRef = useRef(null); + const ignoreNextTitleBlurRef = useRef(false); const { teamUsage, @@ -351,9 +353,12 @@ const Conversations = ({ if (!selectedThreadId) return; const thr = threads.find(t => t.id === selectedThreadId); setEditTitleValue(thr?.title ?? ''); + ignoreNextTitleBlurRef.current = true; setEditingTitle(true); - window.requestAnimationFrame(() => { + const scheduleSelect = window.requestAnimationFrame ?? window.setTimeout; + scheduleSelect(() => { editTitleInputRef.current?.select(); + ignoreNextTitleBlurRef.current = false; }); }; @@ -361,6 +366,8 @@ const Conversations = ({ const trimmed = editTitleValue.trim(); setEditingTitle(false); if (!selectedThreadId || !trimmed) return; + const currentTitle = threads.find(t => t.id === selectedThreadId)?.title?.trim(); + if (trimmed === currentTitle) return; void dispatch(updateThreadTitle({ threadId: selectedThreadId, title: trimmed })); }; @@ -1232,19 +1239,6 @@ const Conversations = ({ }; const filteredThreads = useMemo(() => { - // Worker/subagent threads (any thread with `parentThreadId`) are - // surfaced through two intentional paths (issue #1624): - // 1. The dedicated `Workers` tab in the sidebar — pick that tab to - // see only background work and jump into a worker transcript. - // 2. Inline inside the parent thread via `WorkerThreadRefCard`, - // which now also renders a live running/completed/failed badge - // derived from the parent timeline entry's status. - // The default ("All") and label-scoped tabs hide them so the main - // sidebar is dominated by user-initiated conversations rather than - // background reasoning threads. The actual rule lives in - // `isThreadVisibleInTab` so it is pure, unit-testable, and stays - // in lockstep with the sidebar tab definition (`labelTabs` below) - // via the shared `WORKERS_TAB_VALUE` sentinel. return threads.filter(t => isThreadVisibleInTab(t, selectedLabel)); }, [threads, selectedLabel]); @@ -1254,19 +1248,12 @@ const Conversations = ({ ); }, [filteredThreads]); - // Fixed tab set so categories don't disappear when empty and the active + // Fixed bucket set so categories don't disappear when empty and the active // filter state remains unambiguous regardless of what threads exist. - // The `workers` tab (issue #1624) is the deliberate UI surface for - // background sub-agent / worker threads — selecting it inverts the - // default `parentThreadId` filter in `filteredThreads` above so only - // worker threads show. Without this tab the only way into a worker - // transcript is the inline `WorkerThreadRefCard` inside the parent. const labelTabs = [ - { label: t('chat.filter.all'), value: 'all' }, { label: t('chat.filter.general'), value: GENERAL_TAB_VALUE }, - { label: t('chat.filter.briefing'), value: 'briefing' }, - { label: t('chat.filter.notification'), value: 'notification' }, - { label: t('chat.filter.workers'), value: WORKERS_TAB_VALUE }, + { label: t('chat.filter.subconscious'), value: SUBCONSCIOUS_TAB_VALUE }, + { label: t('chat.filter.tasks'), value: TASKS_TAB_VALUE }, ]; const selectedLabelDisplay = labelTabs.find(tab => tab.value === selectedLabel)?.label ?? selectedLabel; @@ -1283,8 +1270,8 @@ const Conversations = ({ // Resolve the parent of the currently-selected thread, if any. Used to // render the back-to-parent breadcrumb in the chat header so a user who - // dropped into a worker thread (via `WorkerThreadRefCard` or the - // `Workers` sidebar tab) can return to the conversation that spawned it + // dropped into a worker thread (via `WorkerThreadRefCard` or the Tasks + // bucket) can return to the conversation that spawned it // — issue #1624 acceptance criterion "Parent ↔ worker navigation is // bidirectional". Returns `null` when the active thread is a top-level // conversation (no parent), so the header stays unchanged in the @@ -1339,16 +1326,13 @@ const Conversations = ({ selected={selectedLabel} onChange={setSelectedLabel} containerClassName="flex flex-wrap gap-1 py-1" + itemClassName="px-2" />
{sortedThreads.length === 0 ? (

- {selectedLabel === 'all' - ? t('chat.noThreads') - : selectedLabel === WORKERS_TAB_VALUE - ? t('chat.noWorkerThreads') - : t('chat.noLabelThreads').replace('{label}', selectedLabelDisplay)} + {t('chat.noLabelThreads').replace('{label}', selectedLabelDisplay)}

) : ( sortedThreads.map(thread => ( @@ -1498,7 +1482,13 @@ const Conversations = ({ setEditingTitle(false); } }} - onBlur={handleCommitTitle} + onBlur={() => { + if (ignoreNextTitleBlurRef.current) { + ignoreNextTitleBlurRef.current = false; + return; + } + handleCommitTitle(); + }} aria-label={t('chat.editThreadTitle')} className="h-5 text-sm font-medium text-stone-700 dark:text-neutral-200 bg-transparent border-b border-primary-400 outline-none w-full min-w-0 leading-none py-0" autoFocus @@ -1512,6 +1502,10 @@ const Conversations = ({