diff --git a/UI-DESIGN.md b/UI-DESIGN.md index cec50aad..eae0a442 100644 --- a/UI-DESIGN.md +++ b/UI-DESIGN.md @@ -318,10 +318,10 @@ 遮罩或隔离其内容。分栏计算排除最左侧主导航,主工作区与工作栏采用相同的 `300px` 最小宽度;拖动保存两者在可用分栏宽度中的比例,任一侧到达最小值后都不得继续侵占。 所有可容纳两侧最小值的尺寸均支持指针和键盘调整,并须保留全部栏目入口。 -- 工作栏开关位于窗口关闭按钮下方的当前任务或页面上下文栏最右侧,同一个按钮负责展开与 - 收起并持续暴露状态;图标中的箭头方向必须随当前动作切换,收起状态提示展开,展开状态 - 提示收起。按钮保持普通图标按钮视觉,不通过持续高亮表达展开状态。工作栏内部不再重复 - 关闭按钮。上下文栏应为后续任务状态、范围和高频操作预留空间,不能只作为孤立的按钮容器。 +- 工作栏开关固定在窗口关闭按钮正下方,同一个按钮负责展开与收起并持续暴露状态;图标中的 + 箭头方向必须随当前动作切换,收起状态提示展开,展开状态提示收起。按钮保持普通图标按钮 + 视觉,不通过持续高亮表达展开状态。工作栏内部不再重复关闭按钮;当前会话没有 Task 时, + 不得为了放置该开关而保留一条空上下文栏。 - 停靠工作栏不得获得对话框语义、焦点陷阱或背景遮罩。关闭后焦点返回同一个工作栏开关; 主工作区与工作栏在展开期间都保持可达。 - 对话消息流、输入区和输入区元信息必须在主工作区内部保留对称的左右安全边距。工作栏 @@ -330,11 +330,17 @@ ### 6.10 应用顶栏与全局操作 -应用顶栏用于窗口级状态、侧栏开关和低频全局操作,不承担页面标题或主要导航。顶栏必须保持紧凑,不能与页面内容争夺注意力。 +应用顶栏用于当前会话标题、范围、窗口级状态、侧栏开关和低频全局操作,不承担主要导航。 +顶栏必须保持紧凑,不能与页面内容争夺注意力。 -- 无边框窗口使用两层顶部结构:`30px` 窗口标题栏只承载拖动区域和窗口控制;其下 - `46px` 当前任务或页面上下文栏承载侧栏开关、标题、范围、状态和高频操作。普通图标按钮 - 使用 `34px × 34px` 点击区域。 +- 无边框窗口使用一条紧凑标题栏,保留侧栏开关、当前会话标题、范围、Runtime 状态、主题 + 开关和窗口控制,不得为了新增工作栏入口把这一整组控件下移。普通图标按钮使用 + `34px × 34px` 点击区域。 +- 当前会话存在 Task 时,Task 上下文栏位于标题栏下方并承载任务摘要与详情;没有 Task 时 + 整行隐藏。折叠状态保持为 `46px` 单行表面,与右侧工作栏标签栏等高,左右分隔线必须水平 + 对齐。该栏不重复放置新建入口。 +- “新建任务”只位于工作栏“任务索引”标题后方;任务状态筛选占满工作栏可用宽度,四个选项 + 等分,不能保留内容宽度的短控件。 - Runtime 状态、同步状态等短标签使用 `--font-caption`,不得放大为正文标题。 - 浅色与深色切换属于持续可用的窗口级操作,直接显示太阳或月亮图标,并通过可访问名称说明将切换到的主题。选择必须持久化,切换不得改变布局。 - 顶栏只直接显示当前任务所需的高频操作。已有侧栏账户设置入口时,不再重复显示 Runtime/设置入口;使用帮助优先放在相关操作附近,而不是为单个帮助项创建“更多”菜单。 diff --git a/docs/prd/assistant-experience/assistant-workbar-and-execution-spaces-prd.md b/docs/prd/assistant-experience/assistant-workbar-and-execution-spaces-prd.md index cd409f9f..eaa7326c 100644 --- a/docs/prd/assistant-experience/assistant-workbar-and-execution-spaces-prd.md +++ b/docs/prd/assistant-experience/assistant-workbar-and-execution-spaces-prd.md @@ -223,9 +223,11 @@ Runtime 应用同一最小值约束。 - 对话内容与输入区始终保留对称的左右安全边距,工作栏边界不得覆盖或裁切输入框、发送 按钮及主工作区右侧边距。 -- 唯一展开与收起开关位于窗口关闭按钮下方的当前任务或页面上下文栏最右侧。工作栏内部 - 不重复关闭入口。开关图标中的箭头方向随当前动作切换,按钮不以持续高亮表达展开状态; - 该上下文栏为后续任务状态、范围和高频操作扩展预留位置。 +- 唯一展开与收起开关固定在窗口关闭按钮正下方。工作栏内部不重复关闭入口。开关图标中的 + 箭头方向随当前动作切换,按钮不以持续高亮表达展开状态;当前会话没有 Task 时不渲染空 + Task 上下文栏。 +- “新建任务”入口位于工作栏“任务索引”标题后方,不在会话 Task 上下文栏重复出现;索引 + 状态筛选占满可用宽度并等分各筛选项。 - 终端、宽日志和大型成果允许用户切换到底部停靠或独立窗口。 - 应用只建议适合的布局,不因面板内容自动改变用户已经选择的停靠位置。 @@ -759,7 +761,7 @@ Electron、ChildProcess、PTY、SSH Client、Socket 或文件句柄。 - [ ] 宽、中、窄窗口均可访问完整能力目录和用户打开的面板实例。 - [ ] 工作栏在宽、中、窄窗口展开时都推动主工作区回流,不覆盖、遮罩或隔离主内容;唯一 - 开关始终位于当前任务或页面上下文栏最右侧。 + 开关始终固定在窗口关闭按钮正下方。 - [ ] 仅使用键盘可以选择能力、面板实例、目标、执行空间和控制动作。 - [ ] 状态不只依赖颜色,徽标具有文字或可访问名称。 - [ ] 终端、HTML、监督证据和高频日志具有可访问替代或降噪行为。 diff --git a/docs/roadmap/long-term-assistant-roadmap.md b/docs/roadmap/long-term-assistant-roadmap.md index 9d74317c..2efee84e 100644 --- a/docs/roadmap/long-term-assistant-roadmap.md +++ b/docs/roadmap/long-term-assistant-roadmap.md @@ -48,8 +48,8 @@ GoodBuddy 应能够: - 排除最左侧主导航后,主工作区与右侧栏使用相同的 `300px` 最小宽度,并按用户拖动后的 比例分配可用宽度;任一侧到达最小值后停止拖动。 - 主工作区中的消息流和输入区保留对称的左右安全边距,右侧栏不得覆盖或裁切其右边缘。 -- 唯一展开与收起开关位于窗口关闭按钮下方的当前任务或页面上下文栏最右侧,右侧栏内部 - 不重复关闭入口。 +- 唯一展开与收起开关固定在窗口关闭按钮正下方,右侧栏内部不重复关闭入口;当前会话没有 + Task 时不保留空的 Task 上下文栏。 - 右侧栏在对话、知识库和活动视图之间保持状态。 - 知识图谱实体详情复用同一右栏容器,不再维护独立布局。 - 九个标准能力固定可达;能力、连接和数据状态可以变化,但应用不按当前上下文自动隐藏入口。 diff --git a/src/renderer/src/App.test.tsx b/src/renderer/src/App.test.tsx index 2d041b67..ed75c8a7 100644 --- a/src/renderer/src/App.test.tsx +++ b/src/renderer/src/App.test.tsx @@ -1121,17 +1121,29 @@ describe('App', () => { expect( within(assistantSidebar).getByText('Project: Default project') ).toBeInTheDocument() - fireEvent.click( - within(assistantSidebar).getByRole('button', { - name: 'New custom task' - }) + const taskIndexHeading = within(assistantSidebar).getByRole( + 'heading', + { name: 'Task index' } ) + const newTaskButton = within(assistantSidebar).getByRole( + 'button', + { name: 'New task' } + ) + expect(taskIndexHeading.parentElement).toContainElement( + newTaskButton + ) + fireEvent.click(newTaskButton) const taskDialog = screen.getByRole('dialog', { name: 'New custom task' }) expect( within(taskDialog).getByText('Default project') ).toBeInTheDocument() + expect( + within(taskDialog).getByRole('button', { + name: 'Current conversation' + }) + ).toHaveAttribute('aria-pressed', 'true') fireEvent.click( within(taskDialog).getByRole('button', { name: 'Close new custom task' @@ -8015,9 +8027,9 @@ describe('App', () => { expect(sidebar).not.toHaveClass('assistant-sidebar--open') const assistantTrigger = screen.getByLabelText('切换助手工作栏') - expect( - container.querySelector('.topbar') - ).toContainElement(assistantTrigger) + expect(assistantTrigger.parentElement).toBe( + container.querySelector('.app-shell') + ) expect(assistantTrigger).toHaveClass('assistant-sidebar-toggle') expect(assistantTrigger).not.toHaveClass('icon-button--active') expect(assistantTrigger).toHaveAttribute('aria-expanded', 'false') @@ -8032,8 +8044,14 @@ describe('App', () => { assistantTrigger.querySelector('.lucide-panel-right-close') ).not.toBeInTheDocument() expect( - container.querySelector('.window-titlebar') + container.querySelector('.topbar') ).toContainElement(screen.getByLabelText('关闭窗口')) + expect(container.querySelector('.topbar')).toContainElement( + container.querySelector('.conversation-title') + ) + expect( + container.querySelector('.topbar') + ).not.toContainElement(assistantTrigger) expect( screen.queryByLabelText('关闭助手工作栏') ).not.toBeInTheDocument() @@ -8059,9 +8077,18 @@ describe('App', () => { expect( screen.getByRole('tab', { name: '任务中心' }) ).toHaveAttribute('aria-selected', 'true') + const taskIndexHeading = screen.getByRole('heading', { + name: '任务索引' + }) + const newTaskButton = screen.getByRole('button', { + name: '新建任务' + }) + expect(taskIndexHeading.parentElement).toContainElement( + newTaskButton + ) expect( - screen.getByRole('heading', { name: '任务索引' }) - ).toBeInTheDocument() + screen.queryByRole('region', { name: '当前会话的任务' }) + ).not.toBeInTheDocument() expect(screen.queryByText('最近任务')).not.toBeInTheDocument() fireEvent.click(screen.getByRole('tab', { name: '上下文' })) expect( diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index fe8adc73..16bd77aa 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -7390,7 +7390,110 @@ function App(): React.JSX.Element { return (
-
+
+ + {view === 'chat' && ( + <> +
+ + {activeConversation + ? getConversationDisplayTitle( + activeConversation, + t('conversation.defaultTitle') + ) + : + (activeProject?.kind === 'channel' + ? t('conversation.remoteTitle') + : t('conversation.defaultTitle'))} + + {activeConversation?.branch && ( + + )} + {activeConversation?.remote && ( + + { + projectChannelLabels[ + activeConversation.remote.channel + ] + } + + )} +
+ + + )} +
+ + + + {runtime?.label ?? t('runtime.detecting')} + + {runtime?.capability === 'image-generation' && ( + + {t('runtime.imageGeneration')} + + )} + + +
@@ -8045,143 +8148,32 @@ function App(): React.JSX.Element { /> )} + {view === 'chat' && ( + + )} +
-
- - {view === 'chat' && ( - <> -
- - {activeConversation - ? getConversationDisplayTitle( - activeConversation, - t('conversation.defaultTitle') - ) - : - (activeProject?.kind === 'channel' - ? t('conversation.remoteTitle') - : t('conversation.defaultTitle'))} - - {activeConversation?.branch && ( - - )} - {activeConversation?.remote && ( - - { - projectChannelLabels[ - activeConversation.remote.channel - ] - } - - )} -
- - - )} -
- - - - {runtime?.label ?? t('runtime.detecting')} - - {runtime?.capability === 'image-generation' && ( - - {t('runtime.imageGeneration')} - - )} - - -
- {view === 'chat' && ( - - )} -
- {(view === 'chat' || cachedWorkspaceViewKeys.has('chat')) && ( - openCustomTaskDialog('current') - } onRemoveSchedule={removeAssistantSchedule} onRunSchedule={runAssistantSchedule} onSelectTask={setSelectedAssistantTaskId} @@ -9805,7 +9794,7 @@ function App(): React.JSX.Element { conversationTitles={conversationTitles} enabledLibraries={enabledSidebarLibraries} memories={assistantMemories} - onCreateCustomTask={() => openCustomTaskDialog('new')} + onCreateCustomTask={() => openCustomTaskDialog('current')} schedules={assistantSchedules} selectedTaskId={selectedAssistantTaskId} tasks={productAssistantTasks} diff --git a/src/renderer/src/ConversationTaskStrip.test.tsx b/src/renderer/src/ConversationTaskStrip.test.tsx index c7543e10..90cb3272 100644 --- a/src/renderer/src/ConversationTaskStrip.test.tsx +++ b/src/renderer/src/ConversationTaskStrip.test.tsx @@ -24,7 +24,6 @@ describe('ConversationTaskStrip', () => { render( { expect(screen.getByText('每日状态', { selector: 'strong' })) .toBeInTheDocument() + expect( + screen.queryByRole('button', { name: '新建任务' }) + ).not.toBeInTheDocument() expect(screen.getByText('Execute')).toBeInTheDocument() fireEvent.click(screen.getByRole('button', { name: '立即运行' })) await waitFor(() => @@ -87,4 +89,22 @@ describe('ConversationTaskStrip', () => { expect(onRemoveSchedule).toHaveBeenCalledWith(scheduleId) ) }) + + it('hides the conversation task row when there are no tasks', () => { + render( + undefined)} + onRunSchedule={vi.fn(async () => undefined)} + onSelectTask={vi.fn()} + onSetScheduleEnabled={vi.fn(async () => undefined)} + schedules={[]} + tasks={[]} + /> + ) + + expect( + screen.queryByRole('region', { name: '当前会话的任务' }) + ).not.toBeInTheDocument() + }) }) diff --git a/src/renderer/src/ConversationTaskStrip.tsx b/src/renderer/src/ConversationTaskStrip.tsx index 75bb1fbd..bb131b35 100644 --- a/src/renderer/src/ConversationTaskStrip.tsx +++ b/src/renderer/src/ConversationTaskStrip.tsx @@ -2,8 +2,7 @@ import { ChevronDown, ChevronUp, CircleAlert, - ClockFading, - Plus + ClockFading } from 'lucide-react' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -18,7 +17,6 @@ import { type ConversationTaskStripProps = { locale: string - onCreate: () => void onRemoveSchedule: (scheduleId: string) => Promise onRunSchedule: (scheduleId: string) => Promise onSelectTask: (taskId: string) => void @@ -33,7 +31,6 @@ type ConversationTaskStripProps = { export function ConversationTaskStrip({ locale, - onCreate, onRemoveSchedule, onRunSchedule, onSelectTask, @@ -41,7 +38,7 @@ export function ConversationTaskStrip({ schedules, selectedTaskId, tasks -}: ConversationTaskStripProps): React.JSX.Element { +}: ConversationTaskStripProps): React.JSX.Element | null { const { t } = useTranslation('workspace') const [manuallyExpanded, setManuallyExpanded] = useState(false) const [collapsedTaskId, setCollapsedTaskId] = useState('') @@ -58,6 +55,10 @@ export function ConversationTaskStrip({ manuallyExpanded || (Boolean(selectedTaskId) && collapsedTaskId !== selectedTaskId) + if (tasks.length === 0) { + return null + } + return (
{expanded && (
- {tasks.length === 0 ? ( -

- {t('taskStrip.empty')} -

- ) : ( - <> -
+ {tasks.map((task) => ( + - ))} -
- {selectedTask && ( -
-
- - {selectedTask.title} - - {t(`task.status.${selectedTask.status}`)} - - - {selectedTask.status === 'failed' && ( -
-
-
-
{t('task.fields.mode')}
-
- {selectedSchedule - ? t(`task.mode.${selectedSchedule.workMode}`) - : selectedTask.workMode - ? t(`task.mode.${selectedTask.workMode}`) - : t('task.mode.unavailable')} -
-
-
-
{t('task.fields.schedule')}
-
- {selectedSchedule - ? t( - `sidebar.tasks.schedule.recurrence.${selectedSchedule.recurrence}` - ) - : t('task.schedule.none')} -
-
-
-
{t('task.fields.nextRun')}
-
- {selectedSchedule - ? new Date( - selectedSchedule.nextRunAt +
+ {selectedTask && ( +
+
+ + {selectedTask.title} + + {t(`task.status.${selectedTask.status}`)} + + + {selectedTask.status === 'failed' && ( +
+
+
+
{t('task.fields.mode')}
+
+ {selectedSchedule + ? t(`task.mode.${selectedSchedule.workMode}`) + : selectedTask.workMode + ? t(`task.mode.${selectedTask.workMode}`) + : t('task.mode.unavailable')} +
+
+
+
{t('task.fields.schedule')}
+
+ {selectedSchedule + ? t( + `sidebar.tasks.schedule.recurrence.${selectedSchedule.recurrence}` + ) + : t('task.schedule.none')} +
+
+
+
{t('task.fields.nextRun')}
+
+ {selectedSchedule + ? new Date( + selectedSchedule.nextRunAt + ).toLocaleString(locale) + : t('task.notAvailable')} +
+
+
+
{t('task.fields.outcome')}
+
+ {selectedTask.error ?? + (selectedTask.completedAt + ? t('task.completedAt', { + time: new Date( + selectedTask.completedAt ).toLocaleString(locale) - : t('task.notAvailable')} -
-
-
-
{t('task.fields.outcome')}
-
- {selectedTask.error ?? - (selectedTask.completedAt - ? t('task.completedAt', { - time: new Date( - selectedTask.completedAt - ).toLocaleString(locale) - }) - : t('task.noOutcome'))} -
-
-
- {selectedSchedule && ( -
- -
- )} -
+ }) + : t('task.noOutcome'))} + +
+ + {selectedSchedule && ( +
+ +
)} - + )} {actionError && (

diff --git a/src/renderer/src/RightAssistantSidebar.resize.test.tsx b/src/renderer/src/RightAssistantSidebar.resize.test.tsx index 5ba5d352..215cf1eb 100644 --- a/src/renderer/src/RightAssistantSidebar.resize.test.tsx +++ b/src/renderer/src/RightAssistantSidebar.resize.test.tsx @@ -253,11 +253,17 @@ describe('RightAssistantSidebar resizing', () => { renderSidebar({ tab: 'tasks' }) expect(screen.getByText('等待审批')).toBeInTheDocument() + const taskIndexHeading = screen.getByRole('heading', { + name: '任务索引' + }) + const newTaskButton = screen.getByRole('button', { + name: '新建任务' + }) + expect(taskIndexHeading.parentElement).toContainElement( + newTaskButton + ) expect( - screen.getByRole('heading', { name: '任务索引' }) - ).toBeInTheDocument() - expect( - screen.getByRole('button', { name: '新建定制任务' }) + screen.getByRole('group', { name: '筛选任务' }) ).toBeInTheDocument() expect(screen.queryByLabelText('定时任务标题')).not.toBeInTheDocument() expect(screen.queryByText('最近任务')).not.toBeInTheDocument() diff --git a/src/renderer/src/RightAssistantSidebar.tsx b/src/renderer/src/RightAssistantSidebar.tsx index 08d25720..0d820dae 100644 --- a/src/renderer/src/RightAssistantSidebar.tsx +++ b/src/renderer/src/RightAssistantSidebar.tsx @@ -640,19 +640,9 @@ export function RightAssistantSidebar({ ) : null} {tab === 'tasks' && (

-
-

- {t('sidebar.tasks.description')} -

- -
+

+ {t('sidebar.tasks.description')} +

{t('sidebar.tasks.approvalsTitle')} @@ -694,10 +684,20 @@ export function RightAssistantSidebar({ )) )} -

- - {t('sidebar.tasks.taskIndexTitle')} -

+
+

+ + {t('sidebar.tasks.taskIndexTitle')} +

+ +
{ expect(stylesheet).toMatch( /\.composer-wrap\s*\{[^}]*width:\s*100%;[^}]*max-width:\s*100%;[^}]*min-width:\s*0;[^}]*padding:\s*var\(--space-3\)\s*max\(var\(--page-gutter\),\s*calc\(\(100% - var\(--content-reading\)\) \/ 2\)\)\s*var\(--space-2\);/u ) + expect(stylesheet).toMatch( + /\.assistant-sidebar__tabs\s*\{[^}]*grid-template-columns:\s*repeat\(5,\s*minmax\(max-content,\s*1fr\)\);/u + ) + expect(stylesheet).toMatch( + /\.assistant-sidebar__tab\s*\{[^}]*white-space:\s*nowrap;/u + ) + expect(stylesheet).toMatch( + /\.app-frame\s*\{[^}]*display:\s*grid;[^}]*grid-template-rows:\s*58px minmax\(0,\s*1fr\);/u + ) + expect(stylesheet).toMatch( + /\.workspace\s*\{[^}]*grid-template-rows:\s*minmax\(0,\s*1fr\);/u + ) + expect(stylesheet).toMatch( + /\.assistant-sidebar-toggle\s*\{[^}]*position:\s*absolute;[^}]*top:\s*6px;[^}]*right:\s*6px;/u + ) + expect(stylesheet).toMatch( + /\.conversation-task-strip\s*\{[^}]*min-height:\s*46px;[^}]*padding:\s*0\s+max\(/u + ) + expect(stylesheet).toMatch( + /\.conversation-task-strip__header\s*\{[^}]*min-height:\s*45px;/u + ) + expect(stylesheet).toMatch( + /@media \(max-width: 720px\)\s*\{[\s\S]*?\.conversation-task-strip\s*\{[^}]*padding-right:\s*46px;[^}]*padding-left:\s*var\(--space-3\);/u + ) + expect(stylesheet).not.toMatch( + /@media \(max-width: 720px\)\s*\{[\s\S]*?\.conversation-task-strip__header\s*\{[^}]*flex-direction:\s*column;/u + ) + expect(stylesheet).toMatch( + /\.task-center__filters \.segmented-control\s*\{[^}]*width:\s*100%;[^}]*overflow:\s*hidden;/u + ) + expect(stylesheet).toMatch( + /\.task-center__filters \.segmented-control__option\s*\{[^}]*min-width:\s*0;[^}]*flex:\s*1;/u + ) }) it('floats Runtime context compaction without shifting the composer', () => { diff --git a/src/renderer/src/i18n/locales/en-US/workspace.ts b/src/renderer/src/i18n/locales/en-US/workspace.ts index faae53ee..1eee42ca 100644 --- a/src/renderer/src/i18n/locales/en-US/workspace.ts +++ b/src/renderer/src/i18n/locales/en-US/workspace.ts @@ -99,7 +99,6 @@ export const workspace = { tasks: { description: 'Review pending approvals and create or manage automations.', - createCustom: 'New custom task', approvalsTitle: 'Awaiting approval', noApprovals: 'There are no operations awaiting approval.', deny: 'Deny', @@ -248,7 +247,6 @@ export const workspace = { title: 'Conversation tasks', count: '{{count}}', create: 'New task', - empty: 'This conversation has no tasks yet.', taskList: 'Task list' }, files: { diff --git a/src/renderer/src/i18n/locales/zh-CN/workspace.ts b/src/renderer/src/i18n/locales/zh-CN/workspace.ts index 82157b6d..ad1a6bfe 100644 --- a/src/renderer/src/i18n/locales/zh-CN/workspace.ts +++ b/src/renderer/src/i18n/locales/zh-CN/workspace.ts @@ -94,7 +94,6 @@ export const workspace = { }, tasks: { description: '处理当前待审批操作,并创建和管理自动化任务。', - createCustom: '新建定制任务', approvalsTitle: '等待审批', noApprovals: '当前没有等待审批的操作。', deny: '拒绝', @@ -236,7 +235,6 @@ export const workspace = { title: '会话任务', count: '{{count}} 个', create: '新建任务', - empty: '当前会话还没有任务。', taskList: '任务列表' }, files: { diff --git a/src/renderer/src/styles.css b/src/renderer/src/styles.css index fbde7976..ad272160 100644 --- a/src/renderer/src/styles.css +++ b/src/renderer/src/styles.css @@ -196,15 +196,21 @@ .conversation-task-strip { flex: 0 0 auto; - padding: - var(--space-2) + min-height: 46px; + padding: 0 max(var(--page-gutter), calc((100% - var(--content-reading)) / 2)); + padding-right: max( + 46px, + var(--page-gutter), + calc((100% - var(--content-reading)) / 2) + ); border-bottom: 1px solid var(--border-subtle); background: var(--surface-raised); } .conversation-task-strip__header { display: flex; + min-height: 45px; align-items: center; justify-content: space-between; gap: var(--space-2); @@ -239,12 +245,6 @@ font-size: var(--font-caption); } -.conversation-task-strip__create { - min-height: 30px; - padding: 0 var(--space-2); - font-size: var(--font-caption); -} - .conversation-task-strip__content { display: grid; padding: var(--space-2) 0 var(--space-1); @@ -381,7 +381,6 @@ opacity: 0.6; } -.conversation-task-strip__empty, .conversation-task-strip__error { margin: 0; color: var(--text-muted); @@ -392,19 +391,40 @@ color: var(--danger); } -.task-center__heading { - display: grid; - align-items: start; +.task-center__index-heading { + display: flex; + min-width: 0; + align-items: center; + margin: 8px 0 2px; gap: var(--space-2); } -.task-center__heading .primary-button { - justify-self: start; +.assistant-sidebar__section .task-center__index-heading h3 { + min-width: 0; + margin: 0; +} + +.task-center__create { + min-height: 30px; + flex: 0 0 auto; + padding: 0 var(--space-2); } .task-center__filters { min-width: 0; - overflow-x: auto; + overflow: hidden; +} + +.task-center__filters .segmented-control { + width: 100%; + overflow: hidden; +} + +.task-center__filters .segmented-control__option { + min-width: 0; + flex: 1; + padding-right: var(--space-1); + padding-left: var(--space-1); } .task-center__item { @@ -791,19 +811,9 @@ } .conversation-task-strip { - padding-right: var(--space-3); + padding-right: 46px; padding-left: var(--space-3); } - - .conversation-task-strip__header { - align-items: stretch; - flex-direction: column; - } - - .conversation-task-strip__create { - width: 100%; - justify-content: center; - } } .knowledge-scope__retrieval-mode { @@ -2266,18 +2276,7 @@ textarea:focus-visible { min-height: 0; background: var(--surface-canvas); color: var(--text-primary); - grid-template-rows: 30px minmax(0, 1fr); -} - -.window-titlebar { - display: flex; - min-width: 0; - align-items: stretch; - justify-content: flex-end; - border-bottom: 1px solid var(--border-subtle); - background: var(--surface-subtle); - -webkit-app-region: drag; - user-select: none; + grid-template-rows: 58px minmax(0, 1fr); } .app-shell { @@ -2977,7 +2976,7 @@ textarea:focus-visible { flex: 1; overflow: hidden; background: var(--surface-raised); - grid-template-rows: 46px minmax(0, 1fr); + grid-template-rows: minmax(0, 1fr); } .workspace-route-cache { @@ -3003,10 +3002,6 @@ textarea:focus-visible { user-select: none; } -.topbar--with-assistant-toggle { - padding-right: 46px; -} - .topbar :is(button, input, select), .topbar__actions, .window-controls { @@ -3018,7 +3013,7 @@ textarea:focus-visible { height: 100%; flex: 0 0 auto; align-self: stretch; - -webkit-app-region: no-drag; + margin-left: 8px; } .window-control { @@ -3160,7 +3155,7 @@ button > svg { flex: 0 0 46px; align-items: center; gap: 3px; - grid-template-columns: repeat(5, minmax(0, 1fr)); + grid-template-columns: repeat(5, minmax(max-content, 1fr)); } .assistant-sidebar-toggle { @@ -3180,6 +3175,7 @@ button > svg { color: var(--text-muted); cursor: pointer; font-size: 10px; + white-space: nowrap; } .assistant-sidebar__tab:hover, @@ -12579,7 +12575,6 @@ details.settings-section > :not(summary) + :not(summary) { :root[data-theme='dark'] :where( .sidebar, - .window-titlebar, .topbar, .assistant-sidebar, .project-create-card, @@ -12602,7 +12597,6 @@ details.settings-section > :not(summary) + :not(summary) { :root[data-theme='dark'] :where( .sidebar, - .window-titlebar, .topbar ) { background: var(--surface-subtle); @@ -12660,7 +12654,6 @@ details.settings-section > :not(summary) + :not(summary) { :root[data-theme='dark'] :where( .primary-nav, .sidebar-footer, - .window-titlebar, .topbar, .assistant-sidebar, .assistant-sidebar__tabs,