fix: keep assistant workbar docked and resizable
The right assistant workbar could switch to an overlay, duplicate its close control, and let resizing crowd or clip the chat composer. It now uses one directional toggle below the window controls, remains a docked sibling, and preserves equal 300px minimums for the chat and workbar with symmetric composer gutters. Release note: 修复右侧助手工作栏遮挡聊天区及拖动边界不一致的问题;工作栏现在使用单一方向按钮,并在左右区域达到相同最小宽度后停止拖动。
This commit is contained in:
@@ -1138,9 +1138,7 @@ describe('App', () => {
|
||||
})
|
||||
)
|
||||
fireEvent.click(
|
||||
within(assistantSidebar).getByRole('button', {
|
||||
name: 'Close assistant workspace'
|
||||
})
|
||||
screen.getByLabelText('Toggle assistant workspace')
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByLabelText('Project settings'))
|
||||
@@ -5119,7 +5117,7 @@ describe('App', () => {
|
||||
await waitFor(() =>
|
||||
expect(api.workspace.getChanges).toHaveBeenCalledWith(projectId)
|
||||
)
|
||||
fireEvent.click(screen.getByLabelText('关闭助手工作栏'))
|
||||
fireEvent.click(screen.getByLabelText('切换助手工作栏'))
|
||||
selectProjectOption(secondProject.name)
|
||||
await waitFor(() =>
|
||||
expect(api.workspace.getChanges).toHaveBeenCalledWith(
|
||||
@@ -8011,23 +8009,52 @@ describe('App', () => {
|
||||
})
|
||||
|
||||
it('opens the global assistant sidebar and switches work tabs', async () => {
|
||||
render(<App />)
|
||||
const { container } = render(<App />)
|
||||
|
||||
const sidebar = screen.getByLabelText('助手工作栏')
|
||||
expect(sidebar).not.toHaveClass('assistant-sidebar--open')
|
||||
const assistantTrigger =
|
||||
screen.getByLabelText('切换助手工作栏')
|
||||
expect(
|
||||
container.querySelector('.topbar')
|
||||
).toContainElement(assistantTrigger)
|
||||
expect(assistantTrigger).toHaveClass('assistant-sidebar-toggle')
|
||||
expect(assistantTrigger).not.toHaveClass('icon-button--active')
|
||||
expect(assistantTrigger).toHaveAttribute('aria-expanded', 'false')
|
||||
expect(assistantTrigger).toHaveAttribute(
|
||||
'aria-controls',
|
||||
'assistant-sidebar'
|
||||
)
|
||||
expect(
|
||||
assistantTrigger.querySelector('.lucide-panel-right-open')
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
assistantTrigger.querySelector('.lucide-panel-right-close')
|
||||
).not.toBeInTheDocument()
|
||||
expect(
|
||||
container.querySelector('.window-titlebar')
|
||||
).toContainElement(screen.getByLabelText('关闭窗口'))
|
||||
expect(
|
||||
screen.queryByLabelText('关闭助手工作栏')
|
||||
).not.toBeInTheDocument()
|
||||
fireEvent.click(assistantTrigger)
|
||||
expect(sidebar).toHaveClass('assistant-sidebar--open')
|
||||
expect(sidebar).toHaveAttribute('role', 'dialog')
|
||||
expect(sidebar).toHaveAttribute('aria-modal', 'true')
|
||||
expect(sidebar).toHaveAttribute('id', 'assistant-sidebar')
|
||||
expect(sidebar).toHaveAttribute('role', 'complementary')
|
||||
expect(sidebar).not.toHaveAttribute('aria-modal')
|
||||
expect(assistantTrigger).toHaveAttribute('aria-expanded', 'true')
|
||||
expect(assistantTrigger).not.toHaveClass('icon-button--active')
|
||||
expect(
|
||||
assistantTrigger.querySelector('.lucide-panel-right-close')
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
assistantTrigger.querySelector('.lucide-panel-right-open')
|
||||
).not.toBeInTheDocument()
|
||||
const main = document.querySelector('main')
|
||||
expect(main).toHaveAttribute('inert')
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole('tab', { name: '任务中心' })
|
||||
).toHaveFocus()
|
||||
)
|
||||
expect(main).not.toHaveAttribute('inert')
|
||||
expect(
|
||||
container.querySelector('.assistant-sidebar-backdrop')
|
||||
).not.toBeInTheDocument()
|
||||
|
||||
expect(
|
||||
screen.getByRole('tab', { name: '任务中心' })
|
||||
@@ -8056,14 +8083,14 @@ describe('App', () => {
|
||||
expect(
|
||||
screen.queryByRole('tab', { name: '预览' })
|
||||
).not.toBeInTheDocument()
|
||||
fireEvent.click(screen.getByLabelText('关闭助手工作栏'))
|
||||
fireEvent.click(assistantTrigger)
|
||||
expect(sidebar).not.toHaveClass('assistant-sidebar--open')
|
||||
expect(assistantTrigger).toHaveAttribute('aria-expanded', 'false')
|
||||
expect(
|
||||
assistantTrigger.querySelector('.lucide-panel-right-open')
|
||||
).toBeInTheDocument()
|
||||
expect(main).not.toHaveAttribute('inert')
|
||||
await waitFor(() => expect(assistantTrigger).toHaveFocus())
|
||||
|
||||
fireEvent.click(assistantTrigger)
|
||||
fireEvent.keyDown(sidebar, { key: 'Escape' })
|
||||
expect(sidebar).not.toHaveClass('assistant-sidebar--open')
|
||||
})
|
||||
|
||||
it('keeps completed chat replies out of the results sidebar', async () => {
|
||||
@@ -8507,6 +8534,46 @@ describe('App', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('docks the assistant workbar without obscuring a narrow workspace', async () => {
|
||||
const originalWidth = window.innerWidth
|
||||
const { container } = render(<App />)
|
||||
|
||||
try {
|
||||
Object.defineProperty(window, 'innerWidth', {
|
||||
configurable: true,
|
||||
value: 680
|
||||
})
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
|
||||
await waitFor(() =>
|
||||
expect(container.querySelector('.sidebar'))
|
||||
.toHaveClass('sidebar--closed')
|
||||
)
|
||||
fireEvent.click(screen.getByLabelText('切换助手工作栏'))
|
||||
|
||||
const workbar = screen.getByRole('complementary', {
|
||||
name: '助手工作栏'
|
||||
})
|
||||
expect(workbar).toHaveClass('assistant-sidebar--open')
|
||||
expect(workbar.parentElement).toHaveClass('app-shell')
|
||||
expect(
|
||||
workbar.parentElement?.querySelector('main.workspace')
|
||||
).toBeInTheDocument()
|
||||
expect(workbar).not.toHaveAttribute('aria-modal')
|
||||
expect(container.querySelector('main')).not.toHaveAttribute(
|
||||
'inert'
|
||||
)
|
||||
expect(
|
||||
container.querySelector('.assistant-sidebar-backdrop')
|
||||
).not.toBeInTheDocument()
|
||||
} finally {
|
||||
Object.defineProperty(window, 'innerWidth', {
|
||||
configurable: true,
|
||||
value: originalWidth
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('opens Magic Notes as a global first-class workspace', async () => {
|
||||
api.updates = {
|
||||
getSettings: vi.fn(async () => ({
|
||||
|
||||
+37
-43
@@ -31,6 +31,7 @@ import {
|
||||
Settings,
|
||||
RefreshCw,
|
||||
ShieldCheck,
|
||||
PanelRightClose,
|
||||
PanelRightOpen,
|
||||
Sparkles,
|
||||
Square,
|
||||
@@ -2180,8 +2181,6 @@ function App(): React.JSX.Element {
|
||||
const [assistantSidebarOpen, setAssistantSidebarOpen] = useState(
|
||||
() => window.innerWidth >= 1280
|
||||
)
|
||||
const [assistantSidebarOverlay, setAssistantSidebarOverlay] =
|
||||
useState(() => window.innerWidth < 1280)
|
||||
const [assistantSidebarTab, setAssistantSidebarTab] =
|
||||
useState<AssistantSidebarTab>('tasks')
|
||||
const [browserStates, setBrowserStates] = useState<
|
||||
@@ -2605,7 +2604,6 @@ function App(): React.JSX.Element {
|
||||
const collapseSidebarAtNarrowWidth = (): void => {
|
||||
const narrow = window.innerWidth < 900
|
||||
setNarrowWindow(narrow)
|
||||
setAssistantSidebarOverlay(window.innerWidth < 1280)
|
||||
if (narrow) {
|
||||
setSidebarOpen(false)
|
||||
}
|
||||
@@ -7387,23 +7385,23 @@ function App(): React.JSX.Element {
|
||||
runtimeSettings
|
||||
])
|
||||
|
||||
const assistantOverlayOpen =
|
||||
assistantSidebarOverlay &&
|
||||
assistantSidebarOpen &&
|
||||
view === 'chat'
|
||||
const mainSidebarOpen = narrowWindow && sidebarOpen
|
||||
const backgroundIsolated = mainSidebarOpen || assistantOverlayOpen
|
||||
const backgroundIsolated = mainSidebarOpen
|
||||
|
||||
return (
|
||||
<div className="app-shell">
|
||||
<div className="app-frame">
|
||||
<header className="window-titlebar">
|
||||
<WindowControls onError={handleWindowControlError} />
|
||||
</header>
|
||||
<div className="app-shell">
|
||||
<aside
|
||||
aria-label={
|
||||
narrowWindow && sidebarOpen ? t('sidebar.label') : undefined
|
||||
}
|
||||
aria-hidden={!sidebarOpen || assistantOverlayOpen}
|
||||
aria-hidden={!sidebarOpen}
|
||||
aria-modal={narrowWindow && sidebarOpen ? 'true' : undefined}
|
||||
className={sidebarOpen ? 'sidebar' : 'sidebar sidebar--closed'}
|
||||
inert={!sidebarOpen || assistantOverlayOpen}
|
||||
inert={!sidebarOpen}
|
||||
onKeyDown={(event) => {
|
||||
if (mainSidebarOpen) {
|
||||
trapTabFocus(event, sidebarRef.current)
|
||||
@@ -8052,7 +8050,13 @@ function App(): React.JSX.Element {
|
||||
className="workspace"
|
||||
inert={backgroundIsolated}
|
||||
>
|
||||
<header className="topbar">
|
||||
<header
|
||||
className={
|
||||
view === 'chat'
|
||||
? 'topbar topbar--with-assistant-toggle'
|
||||
: 'topbar'
|
||||
}
|
||||
>
|
||||
<button
|
||||
className="icon-button sidebar-toggle"
|
||||
type="button"
|
||||
@@ -8133,24 +8137,6 @@ function App(): React.JSX.Element {
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
{view === 'chat' && (
|
||||
<button
|
||||
aria-label={t('topbar.toggleAssistantSidebar')}
|
||||
aria-pressed={assistantSidebarOpen}
|
||||
className={
|
||||
assistantSidebarOpen
|
||||
? 'icon-button icon-button--active'
|
||||
: 'icon-button'
|
||||
}
|
||||
onClick={() =>
|
||||
setAssistantSidebarOpen((current) => !current)
|
||||
}
|
||||
ref={assistantSidebarToggleRef}
|
||||
type="button"
|
||||
>
|
||||
<PanelRightOpen size={18} />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
aria-label={
|
||||
resolvedAppearanceTheme === 'dark'
|
||||
@@ -8174,9 +8160,26 @@ function App(): React.JSX.Element {
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<WindowControls
|
||||
onError={handleWindowControlError}
|
||||
/>
|
||||
{view === 'chat' && (
|
||||
<button
|
||||
aria-controls="assistant-sidebar"
|
||||
aria-expanded={assistantSidebarOpen}
|
||||
aria-label={t('topbar.toggleAssistantSidebar')}
|
||||
className="icon-button assistant-sidebar-toggle"
|
||||
onClick={() =>
|
||||
setAssistantSidebarOpen((current) => !current)
|
||||
}
|
||||
ref={assistantSidebarToggleRef}
|
||||
title={t('topbar.toggleAssistantSidebar')}
|
||||
type="button"
|
||||
>
|
||||
{assistantSidebarOpen ? (
|
||||
<PanelRightClose aria-hidden="true" size={18} />
|
||||
) : (
|
||||
<PanelRightOpen aria-hidden="true" size={18} />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{(view === 'chat' || cachedWorkspaceViewKeys.has('chat')) && (
|
||||
@@ -9794,14 +9797,6 @@ function App(): React.JSX.Element {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{assistantOverlayOpen && (
|
||||
<button
|
||||
aria-label={tWorkspace('sidebar.dismissOverlay')}
|
||||
className="assistant-sidebar-backdrop"
|
||||
onClick={() => setAssistantSidebarOpen(false)}
|
||||
type="button"
|
||||
/>
|
||||
)}
|
||||
<RightAssistantSidebar
|
||||
approvals={pendingSidebarApprovals}
|
||||
artifacts={sidebarArtifacts}
|
||||
@@ -9815,7 +9810,6 @@ function App(): React.JSX.Element {
|
||||
selectedTaskId={selectedAssistantTaskId}
|
||||
tasks={productAssistantTasks}
|
||||
projectNames={projectNames}
|
||||
onClose={() => setAssistantSidebarOpen(false)}
|
||||
onInteractBrowser={async () => {
|
||||
if (!activeId) {
|
||||
return
|
||||
@@ -9895,12 +9889,12 @@ function App(): React.JSX.Element {
|
||||
open={
|
||||
assistantSidebarOpen && view === 'chat'
|
||||
}
|
||||
overlay={assistantSidebarOverlay}
|
||||
restoreFocusRef={assistantSidebarToggleRef}
|
||||
tab={assistantSidebarTab}
|
||||
workspaceChanges={workspaceChanges}
|
||||
workspaceProjectId={activeProjectId || undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,63 +25,60 @@ function renderSidebar({
|
||||
tab = 'context',
|
||||
artifacts = [],
|
||||
onLoadArtifact = vi.fn(async () => undefined),
|
||||
onClose = vi.fn(),
|
||||
overlay = false,
|
||||
restoreFocusRef
|
||||
}: {
|
||||
tab?: AssistantSidebarTab
|
||||
artifacts?: SidebarArtifact[]
|
||||
onLoadArtifact?: (artifactId: string) => Promise<void>
|
||||
onClose?: () => void
|
||||
overlay?: boolean
|
||||
restoreFocusRef?: { current: HTMLElement | null }
|
||||
} = {}): HTMLElement {
|
||||
render(
|
||||
<RightAssistantSidebar
|
||||
approvals={[]}
|
||||
artifacts={artifacts}
|
||||
attachments={[]}
|
||||
enabledLibraries={[]}
|
||||
memories={[]}
|
||||
schedules={[]}
|
||||
tasks={[]}
|
||||
conversationTitles={new Map()}
|
||||
projectNames={new Map()}
|
||||
onClose={onClose}
|
||||
onCreateCustomTask={vi.fn()}
|
||||
onImportArtifacts={vi.fn(async () => undefined)}
|
||||
onListWorkspaceDirectory={vi.fn(async (path: string) => ({
|
||||
path,
|
||||
entries: [],
|
||||
truncated: false
|
||||
}))}
|
||||
onLoadArtifact={onLoadArtifact}
|
||||
onLoadWorkspaceFile={vi.fn()}
|
||||
onOpenWorkspaceEntry={vi.fn(async () => undefined)}
|
||||
onInteractBrowser={vi.fn(async () => undefined)}
|
||||
onRefreshChanges={vi.fn(async () => undefined)}
|
||||
onRemoveAttachment={vi.fn()}
|
||||
onRemoveSchedule={vi.fn(async () => undefined)}
|
||||
onRespondApproval={vi.fn()}
|
||||
onRunSchedule={vi.fn(async () => undefined)}
|
||||
onSetScheduleEnabled={vi.fn(async () => undefined)}
|
||||
onOpenTask={vi.fn()}
|
||||
onStopBrowser={vi.fn(async () => undefined)}
|
||||
onTabChange={vi.fn()}
|
||||
open
|
||||
overlay={overlay}
|
||||
restoreFocusRef={restoreFocusRef}
|
||||
tab={tab}
|
||||
/>
|
||||
<div>
|
||||
<main className="workspace" />
|
||||
<RightAssistantSidebar
|
||||
approvals={[]}
|
||||
artifacts={artifacts}
|
||||
attachments={[]}
|
||||
enabledLibraries={[]}
|
||||
memories={[]}
|
||||
schedules={[]}
|
||||
tasks={[]}
|
||||
conversationTitles={new Map()}
|
||||
projectNames={new Map()}
|
||||
onCreateCustomTask={vi.fn()}
|
||||
onImportArtifacts={vi.fn(async () => undefined)}
|
||||
onListWorkspaceDirectory={vi.fn(async (path: string) => ({
|
||||
path,
|
||||
entries: [],
|
||||
truncated: false
|
||||
}))}
|
||||
onLoadArtifact={onLoadArtifact}
|
||||
onLoadWorkspaceFile={vi.fn()}
|
||||
onOpenWorkspaceEntry={vi.fn(async () => undefined)}
|
||||
onInteractBrowser={vi.fn(async () => undefined)}
|
||||
onRefreshChanges={vi.fn(async () => undefined)}
|
||||
onRemoveAttachment={vi.fn()}
|
||||
onRemoveSchedule={vi.fn(async () => undefined)}
|
||||
onRespondApproval={vi.fn()}
|
||||
onRunSchedule={vi.fn(async () => undefined)}
|
||||
onSetScheduleEnabled={vi.fn(async () => undefined)}
|
||||
onOpenTask={vi.fn()}
|
||||
onStopBrowser={vi.fn(async () => undefined)}
|
||||
onTabChange={vi.fn()}
|
||||
open
|
||||
restoreFocusRef={restoreFocusRef}
|
||||
tab={tab}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
return screen.getByRole(overlay ? 'dialog' : 'complementary', {
|
||||
return screen.getByRole('complementary', {
|
||||
name: '助手工作栏'
|
||||
})
|
||||
}
|
||||
|
||||
describe('RightAssistantSidebar resizing', () => {
|
||||
it('resizes with pointer capture and preserves the minimum app width', () => {
|
||||
it('resizes with pointer capture and preserves equal pane minima', () => {
|
||||
const sidebar = renderSidebar()
|
||||
const separator = screen.getByRole('separator', {
|
||||
name: '调整助手工作栏宽度'
|
||||
@@ -108,7 +105,9 @@ describe('RightAssistantSidebar resizing', () => {
|
||||
expect(sidebar).toHaveClass('assistant-sidebar--resizing')
|
||||
expect(
|
||||
sidebar.style.getPropertyValue('--assistant-sidebar-width')
|
||||
).toBe('880px')
|
||||
).toBe('1100px')
|
||||
expect(separator).toHaveAttribute('aria-valuemin', '300')
|
||||
expect(separator).toHaveAttribute('aria-valuemax', '1100')
|
||||
|
||||
fireEvent.pointerUp(separator, { pointerId: 7 })
|
||||
expect(releasePointerCapture).toHaveBeenCalledWith(7)
|
||||
@@ -124,8 +123,8 @@ describe('RightAssistantSidebar resizing', () => {
|
||||
fireEvent.keyDown(separator, { key: 'ArrowLeft' })
|
||||
expect(
|
||||
sidebar.style.getPropertyValue('--assistant-sidebar-width')
|
||||
).toBe('366px')
|
||||
expect(separator).toHaveAttribute('aria-valuenow', '366')
|
||||
).toBe('436px')
|
||||
expect(separator).toHaveAttribute('aria-valuenow', '436')
|
||||
|
||||
fireEvent.keyDown(separator, { key: 'Home' })
|
||||
expect(
|
||||
@@ -135,10 +134,10 @@ describe('RightAssistantSidebar resizing', () => {
|
||||
fireEvent.keyDown(separator, { key: 'End' })
|
||||
expect(
|
||||
sidebar.style.getPropertyValue('--assistant-sidebar-width')
|
||||
).toBe('880px')
|
||||
).toBe('1100px')
|
||||
})
|
||||
|
||||
it('remains resizable when the sidebar overlays a medium window', () => {
|
||||
it('remains resizable when the sidebar docks in a medium window', () => {
|
||||
Object.defineProperty(window, 'innerWidth', {
|
||||
configurable: true,
|
||||
value: 1024
|
||||
@@ -169,6 +168,69 @@ describe('RightAssistantSidebar resizing', () => {
|
||||
).toBe('424px')
|
||||
})
|
||||
|
||||
it('keeps the equal minimum dock resizable in a narrow window', () => {
|
||||
Object.defineProperty(window, 'innerWidth', {
|
||||
configurable: true,
|
||||
value: 680
|
||||
})
|
||||
const sidebar = renderSidebar()
|
||||
const separator = screen.getByRole('separator', {
|
||||
name: '调整助手工作栏宽度'
|
||||
})
|
||||
|
||||
expect(
|
||||
sidebar.style.getPropertyValue('--assistant-sidebar-width')
|
||||
).toBe('300px')
|
||||
expect(sidebar).not.toHaveAttribute('aria-modal')
|
||||
expect(separator).toHaveAttribute('tabindex', '0')
|
||||
expect(separator).toHaveAttribute('aria-valuemin', '300')
|
||||
expect(separator).toHaveAttribute('aria-valuemax', '380')
|
||||
|
||||
fireEvent.keyDown(separator, { key: 'End' })
|
||||
expect(
|
||||
sidebar.style.getPropertyValue('--assistant-sidebar-width')
|
||||
).toBe('380px')
|
||||
})
|
||||
|
||||
it('excludes the primary sidebar from the equal pane limits', async () => {
|
||||
const getBoundingClientRect = vi
|
||||
.spyOn(HTMLElement.prototype, 'getBoundingClientRect')
|
||||
.mockImplementation(function (this: HTMLElement) {
|
||||
const [left, width] = this.classList.contains('workspace')
|
||||
? [278, 630]
|
||||
: this.classList.contains('assistant-sidebar')
|
||||
? [908, 270]
|
||||
: [0, 0]
|
||||
return {
|
||||
bottom: 700,
|
||||
height: 700,
|
||||
left,
|
||||
right: left + width,
|
||||
top: 0,
|
||||
width,
|
||||
x: left,
|
||||
y: 0,
|
||||
toJSON: () => ({})
|
||||
}
|
||||
})
|
||||
try {
|
||||
const sidebar = renderSidebar()
|
||||
const separator = screen.getByRole('separator', {
|
||||
name: '调整助手工作栏宽度'
|
||||
})
|
||||
await waitFor(() =>
|
||||
expect(separator).toHaveAttribute('aria-valuemax', '600')
|
||||
)
|
||||
|
||||
fireEvent.keyDown(separator, { key: 'End' })
|
||||
expect(
|
||||
sidebar.style.getPropertyValue('--assistant-sidebar-width')
|
||||
).toBe('600px')
|
||||
} finally {
|
||||
getBoundingClientRect.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
it('exposes the task center and four reusable work surfaces', () => {
|
||||
renderSidebar()
|
||||
|
||||
@@ -180,25 +242,11 @@ describe('RightAssistantSidebar resizing', () => {
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('treats overlay mode as a focus-trapped dismissible dialog', async () => {
|
||||
const onClose = vi.fn()
|
||||
const trigger = document.createElement('button')
|
||||
document.body.append(trigger)
|
||||
const sidebar = renderSidebar({
|
||||
onClose,
|
||||
overlay: true,
|
||||
restoreFocusRef: { current: trigger }
|
||||
})
|
||||
it('keeps the docked sidebar non-modal', () => {
|
||||
const sidebar = renderSidebar()
|
||||
|
||||
expect(sidebar).toHaveAttribute('aria-modal', 'true')
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole('tab', { name: '上下文' })
|
||||
).toHaveFocus()
|
||||
)
|
||||
fireEvent.keyDown(sidebar, { key: 'Escape' })
|
||||
expect(onClose).toHaveBeenCalledOnce()
|
||||
trigger.remove()
|
||||
expect(sidebar).not.toHaveAttribute('aria-modal')
|
||||
expect(sidebar).toHaveAttribute('role', 'complementary')
|
||||
})
|
||||
|
||||
it('keeps the product Task index in the task center', () => {
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
FolderTree,
|
||||
ClockFading,
|
||||
Monitor,
|
||||
PanelRightClose,
|
||||
Plus,
|
||||
RefreshCw,
|
||||
ShieldAlert,
|
||||
@@ -33,7 +32,6 @@ import type {
|
||||
KnowledgeLibrary
|
||||
} from '../../shared/contracts'
|
||||
import { WorkspaceFilesPanel } from './WorkspaceFilesPanel'
|
||||
import { trapTabFocus } from './dialog-focus'
|
||||
import { SegmentedControl } from './WorkspacePrimitives'
|
||||
import {
|
||||
findTaskSchedule,
|
||||
@@ -80,9 +78,7 @@ type RightAssistantSidebarProps = {
|
||||
workspaceChanges?: WorkspaceChanges
|
||||
workspaceProjectId?: string
|
||||
browserState?: BrowserLiveState
|
||||
overlay?: boolean
|
||||
restoreFocusRef?: { current: HTMLElement | null }
|
||||
onClose: () => void
|
||||
onInteractBrowser: () => Promise<void>
|
||||
onStopBrowser: () => Promise<void>
|
||||
onCreateCustomTask: () => void
|
||||
@@ -120,29 +116,58 @@ const tabIds: AssistantSidebarTab[] = [
|
||||
'results'
|
||||
]
|
||||
const emptyChangedFiles: WorkspaceChanges['files'] = []
|
||||
const defaultSidebarWidth = 350
|
||||
const minimumSidebarWidth = 300
|
||||
const minimumRemainingAppWidth = 520
|
||||
const compactSidebarBreakpoint = 720
|
||||
const defaultSidebarRatio = 0.3
|
||||
const minimumPaneWidth = 300
|
||||
const keyboardResizeStep = 16
|
||||
function getSidebarWidthLimits(viewportWidth: number): {
|
||||
|
||||
function getSidebarWidthLimits(layoutWidth: number): {
|
||||
minimum: number
|
||||
maximum: number
|
||||
} {
|
||||
const boundedLayoutWidth = Math.max(0, Math.floor(layoutWidth))
|
||||
const minimum = Math.min(
|
||||
minimumPaneWidth,
|
||||
Math.floor(boundedLayoutWidth / 2)
|
||||
)
|
||||
return {
|
||||
minimum: minimumSidebarWidth,
|
||||
maximum: Math.max(
|
||||
minimumSidebarWidth,
|
||||
viewportWidth - minimumRemainingAppWidth
|
||||
)
|
||||
minimum,
|
||||
maximum: Math.max(minimum, boundedLayoutWidth - minimum)
|
||||
}
|
||||
}
|
||||
|
||||
function clampSidebarWidth(width: number, viewportWidth: number): number {
|
||||
const limits = getSidebarWidthLimits(viewportWidth)
|
||||
function clampSidebarWidth(width: number, layoutWidth: number): number {
|
||||
const limits = getSidebarWidthLimits(layoutWidth)
|
||||
return Math.min(limits.maximum, Math.max(limits.minimum, width))
|
||||
}
|
||||
|
||||
function findWorkspaceSibling(
|
||||
sidebar: HTMLElement | null
|
||||
): HTMLElement | undefined {
|
||||
const parent = sidebar?.parentElement
|
||||
if (!parent) {
|
||||
return undefined
|
||||
}
|
||||
return Array.from(parent.children).find(
|
||||
(element): element is HTMLElement =>
|
||||
element instanceof HTMLElement &&
|
||||
element.classList.contains('workspace')
|
||||
)
|
||||
}
|
||||
|
||||
function measureSplitLayoutWidth(
|
||||
sidebar: HTMLElement | null,
|
||||
fallbackWidth: number
|
||||
): number {
|
||||
const workspace = findWorkspaceSibling(sidebar)
|
||||
if (!sidebar || !workspace) {
|
||||
return fallbackWidth
|
||||
}
|
||||
const measuredWidth =
|
||||
workspace.getBoundingClientRect().width +
|
||||
sidebar.getBoundingClientRect().width
|
||||
return measuredWidth > 0 ? measuredWidth : fallbackWidth
|
||||
}
|
||||
|
||||
export function RightAssistantSidebar({
|
||||
open,
|
||||
tab,
|
||||
@@ -159,9 +184,7 @@ export function RightAssistantSidebar({
|
||||
workspaceChanges,
|
||||
workspaceProjectId,
|
||||
browserState,
|
||||
overlay = false,
|
||||
restoreFocusRef,
|
||||
onClose,
|
||||
onInteractBrowser,
|
||||
onStopBrowser,
|
||||
onCreateCustomTask,
|
||||
@@ -198,13 +221,18 @@ export function RightAssistantSidebar({
|
||||
})),
|
||||
[t]
|
||||
)
|
||||
const [viewportWidth, setViewportWidth] = useState(window.innerWidth)
|
||||
const [sidebarWidth, setSidebarWidth] = useState(defaultSidebarWidth)
|
||||
const [splitLayoutWidth, setSplitLayoutWidth] = useState(
|
||||
window.innerWidth
|
||||
)
|
||||
const [sidebarRatio, setSidebarRatio] = useState(defaultSidebarRatio)
|
||||
const [isResizing, setIsResizing] = useState(false)
|
||||
const sidebarRef = useRef<HTMLElement>(null)
|
||||
const wasOpen = useRef(false)
|
||||
const wasOverlayOpen = useRef(false)
|
||||
const liveSidebarWidth = useRef(defaultSidebarWidth)
|
||||
const sidebarWidth = clampSidebarWidth(
|
||||
splitLayoutWidth * sidebarRatio,
|
||||
splitLayoutWidth
|
||||
)
|
||||
const liveSidebarWidth = useRef(sidebarWidth)
|
||||
const resizePointerId = useRef<number | undefined>(undefined)
|
||||
const [selectedArtifactId, setSelectedArtifactId] = useState<string>()
|
||||
const [workspacePreview, setWorkspacePreview] = useState<
|
||||
@@ -245,9 +273,10 @@ export function RightAssistantSidebar({
|
||||
workspacePreview?.projectId === workspaceProjectId
|
||||
? workspacePreview
|
||||
: undefined
|
||||
const sidebarWidthLimits = getSidebarWidthLimits(viewportWidth)
|
||||
const sidebarWidthLimits = getSidebarWidthLimits(splitLayoutWidth)
|
||||
const canResize =
|
||||
open && viewportWidth >= compactSidebarBreakpoint
|
||||
open &&
|
||||
sidebarWidthLimits.maximum > sidebarWidthLimits.minimum
|
||||
const topLevelTasks = useMemo(
|
||||
() => tasks.filter((task) => !task.parentTaskId),
|
||||
[tasks]
|
||||
@@ -278,51 +307,74 @@ export function RightAssistantSidebar({
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const handleViewportResize = (): void => {
|
||||
setViewportWidth(window.innerWidth)
|
||||
setSidebarWidth((currentWidth) => {
|
||||
const width = clampSidebarWidth(
|
||||
currentWidth,
|
||||
window.innerWidth
|
||||
)
|
||||
liveSidebarWidth.current = width
|
||||
return width
|
||||
})
|
||||
const sidebar = sidebarRef.current
|
||||
const workspace = findWorkspaceSibling(sidebar)
|
||||
const handleLayoutResize = (): void => {
|
||||
const width = measureSplitLayoutWidth(
|
||||
sidebar,
|
||||
window.innerWidth
|
||||
)
|
||||
setSplitLayoutWidth((currentWidth) =>
|
||||
Math.abs(currentWidth - width) >= 0.5
|
||||
? width
|
||||
: currentWidth
|
||||
)
|
||||
}
|
||||
|
||||
window.addEventListener('resize', handleViewportResize)
|
||||
return () => window.removeEventListener('resize', handleViewportResize)
|
||||
handleLayoutResize()
|
||||
window.addEventListener('resize', handleLayoutResize)
|
||||
const resizeObserver =
|
||||
typeof ResizeObserver === 'undefined'
|
||||
? undefined
|
||||
: new ResizeObserver(handleLayoutResize)
|
||||
if (resizeObserver && workspace && sidebar) {
|
||||
resizeObserver.observe(workspace)
|
||||
resizeObserver.observe(sidebar)
|
||||
}
|
||||
return () => {
|
||||
resizeObserver?.disconnect()
|
||||
window.removeEventListener('resize', handleLayoutResize)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (open && overlay && !wasOverlayOpen.current) {
|
||||
const focusFrame = requestAnimationFrame(() => {
|
||||
document
|
||||
.getElementById(`assistant-sidebar-tab-${tab}`)
|
||||
?.focus()
|
||||
})
|
||||
wasOpen.current = open
|
||||
wasOverlayOpen.current = true
|
||||
return () => cancelAnimationFrame(focusFrame)
|
||||
if (!isResizing) {
|
||||
liveSidebarWidth.current = sidebarWidth
|
||||
}
|
||||
}, [isResizing, sidebarWidth])
|
||||
|
||||
useEffect(() => {
|
||||
if (!open && wasOpen.current) {
|
||||
requestAnimationFrame(() => restoreFocusRef?.current?.focus())
|
||||
}
|
||||
wasOpen.current = open
|
||||
wasOverlayOpen.current = open && overlay
|
||||
}, [open, overlay, restoreFocusRef, tab])
|
||||
}, [open, restoreFocusRef])
|
||||
|
||||
const resizeFromClientX = (
|
||||
clientX: number,
|
||||
commit: boolean
|
||||
): void => {
|
||||
const layoutWidth = measureSplitLayoutWidth(
|
||||
sidebarRef.current,
|
||||
splitLayoutWidth
|
||||
)
|
||||
if (Math.abs(splitLayoutWidth - layoutWidth) >= 0.5) {
|
||||
setSplitLayoutWidth(layoutWidth)
|
||||
}
|
||||
const sidebarBounds = sidebarRef.current?.getBoundingClientRect()
|
||||
const layoutRight =
|
||||
sidebarBounds && sidebarBounds.width > 0
|
||||
? sidebarBounds.right
|
||||
: window.innerWidth
|
||||
const width = clampSidebarWidth(
|
||||
window.innerWidth - clientX,
|
||||
window.innerWidth
|
||||
layoutRight - clientX,
|
||||
layoutWidth
|
||||
)
|
||||
liveSidebarWidth.current = width
|
||||
if (commit) {
|
||||
setSidebarWidth(width)
|
||||
setSidebarRatio(
|
||||
layoutWidth > 0 ? width / layoutWidth : defaultSidebarRatio
|
||||
)
|
||||
return
|
||||
}
|
||||
sidebarRef.current?.style.setProperty(
|
||||
@@ -341,7 +393,15 @@ export function RightAssistantSidebar({
|
||||
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
||||
event.currentTarget.releasePointerCapture(event.pointerId)
|
||||
}
|
||||
setSidebarWidth(liveSidebarWidth.current)
|
||||
const layoutWidth = measureSplitLayoutWidth(
|
||||
sidebarRef.current,
|
||||
splitLayoutWidth
|
||||
)
|
||||
setSidebarRatio(
|
||||
layoutWidth > 0
|
||||
? liveSidebarWidth.current / layoutWidth
|
||||
: defaultSidebarRatio
|
||||
)
|
||||
setIsResizing(false)
|
||||
}
|
||||
|
||||
@@ -351,7 +411,14 @@ export function RightAssistantSidebar({
|
||||
if (!canResize) {
|
||||
return
|
||||
}
|
||||
const limits = getSidebarWidthLimits(window.innerWidth)
|
||||
const layoutWidth = measureSplitLayoutWidth(
|
||||
sidebarRef.current,
|
||||
splitLayoutWidth
|
||||
)
|
||||
if (Math.abs(splitLayoutWidth - layoutWidth) >= 0.5) {
|
||||
setSplitLayoutWidth(layoutWidth)
|
||||
}
|
||||
const limits = getSidebarWidthLimits(layoutWidth)
|
||||
const nextWidth =
|
||||
event.key === 'Home'
|
||||
? limits.minimum
|
||||
@@ -367,8 +434,9 @@ export function RightAssistantSidebar({
|
||||
return
|
||||
}
|
||||
event.preventDefault()
|
||||
setSidebarWidth(
|
||||
clampSidebarWidth(nextWidth, window.innerWidth)
|
||||
const width = clampSidebarWidth(nextWidth, layoutWidth)
|
||||
setSidebarRatio(
|
||||
layoutWidth > 0 ? width / layoutWidth : defaultSidebarRatio
|
||||
)
|
||||
}
|
||||
|
||||
@@ -455,28 +523,17 @@ export function RightAssistantSidebar({
|
||||
ref={sidebarRef}
|
||||
aria-label={t('sidebar.ariaLabel')}
|
||||
aria-hidden={!open}
|
||||
aria-modal={open && overlay ? 'true' : undefined}
|
||||
className={
|
||||
open
|
||||
? `assistant-sidebar assistant-sidebar--open${isResizing && canResize ? ' assistant-sidebar--resizing' : ''}`
|
||||
: 'assistant-sidebar'
|
||||
}
|
||||
id="assistant-sidebar"
|
||||
inert={!open}
|
||||
onKeyDown={(event) => {
|
||||
if (!open || !overlay) {
|
||||
return
|
||||
}
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault()
|
||||
onClose()
|
||||
return
|
||||
}
|
||||
trapTabFocus(event, sidebarRef.current)
|
||||
}}
|
||||
role={open && overlay ? 'dialog' : undefined}
|
||||
role="complementary"
|
||||
style={
|
||||
{
|
||||
'--assistant-sidebar-width': `${sidebarWidth}px`
|
||||
'--assistant-sidebar-width': `${Math.round(sidebarWidth)}px`
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
@@ -484,11 +541,11 @@ export function RightAssistantSidebar({
|
||||
aria-controls="assistant-sidebar-panel"
|
||||
aria-label={t('sidebar.resizeAriaLabel')}
|
||||
aria-orientation="vertical"
|
||||
aria-valuemax={sidebarWidthLimits.maximum}
|
||||
aria-valuemin={sidebarWidthLimits.minimum}
|
||||
aria-valuenow={sidebarWidth}
|
||||
aria-valuemax={Math.round(sidebarWidthLimits.maximum)}
|
||||
aria-valuemin={Math.round(sidebarWidthLimits.minimum)}
|
||||
aria-valuenow={Math.round(sidebarWidth)}
|
||||
aria-valuetext={t('sidebar.resizeValue', {
|
||||
width: sidebarWidth
|
||||
width: Math.round(sidebarWidth)
|
||||
})}
|
||||
aria-disabled={!canResize}
|
||||
className="assistant-sidebar__resize-handle"
|
||||
@@ -496,7 +553,15 @@ export function RightAssistantSidebar({
|
||||
onLostPointerCapture={(event) => {
|
||||
if (resizePointerId.current === event.pointerId) {
|
||||
resizePointerId.current = undefined
|
||||
setSidebarWidth(liveSidebarWidth.current)
|
||||
const layoutWidth = measureSplitLayoutWidth(
|
||||
sidebarRef.current,
|
||||
splitLayoutWidth
|
||||
)
|
||||
setSidebarRatio(
|
||||
layoutWidth > 0
|
||||
? liveSidebarWidth.current / layoutWidth
|
||||
: defaultSidebarRatio
|
||||
)
|
||||
setIsResizing(false)
|
||||
}
|
||||
}}
|
||||
@@ -526,18 +591,6 @@ export function RightAssistantSidebar({
|
||||
role="separator"
|
||||
tabIndex={canResize ? 0 : -1}
|
||||
/>
|
||||
<header className="assistant-sidebar__header">
|
||||
<strong>{t('sidebar.title')}</strong>
|
||||
<button
|
||||
aria-label={t('sidebar.close')}
|
||||
className="icon-button"
|
||||
onClick={onClose}
|
||||
type="button"
|
||||
>
|
||||
<PanelRightClose size={17} />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<nav
|
||||
aria-label={t('sidebar.categoriesAriaLabel')}
|
||||
className="assistant-sidebar__tabs"
|
||||
|
||||
@@ -158,7 +158,7 @@ describe('WorkspacePrimitives', () => {
|
||||
expect(screen.getByLabelText('项目 + 全局')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('keeps forced-color and narrow assistant overrides authoritative', () => {
|
||||
it('keeps forced-color and docked split contracts authoritative', () => {
|
||||
const forcedColorsStart = stylesheet.indexOf(
|
||||
'@media (forced-colors: active)'
|
||||
)
|
||||
@@ -185,15 +185,12 @@ describe('WorkspacePrimitives', () => {
|
||||
/\.heartbeat-center__meter\s*\{[^}]*forced-color-adjust:\s*none;/u
|
||||
)
|
||||
|
||||
const narrowAssistant = stylesheet.slice(
|
||||
stylesheet.indexOf('@media (max-width: 719px)'),
|
||||
stylesheet.indexOf(
|
||||
'@media (max-width: 720px)',
|
||||
stylesheet.indexOf('@media (max-width: 719px)')
|
||||
)
|
||||
expect(stylesheet).toMatch(
|
||||
/\.assistant-sidebar--open\s*\{[^}]*width:\s*var\(--assistant-sidebar-width,\s*30%\);[^}]*flex-basis:\s*var\(--assistant-sidebar-width,\s*30%\);/u
|
||||
)
|
||||
expect(narrowAssistant).toMatch(
|
||||
/\.assistant-sidebar--open\s*\{[^}]*width:\s*calc\(100vw - 16px\);[^}]*flex-basis:\s*calc\(100vw - 16px\);/u
|
||||
expect(stylesheet).not.toContain('@media (max-width: 719px)')
|
||||
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
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -72,9 +72,6 @@ export const workspace = {
|
||||
ariaLabel: 'Assistant workspace',
|
||||
resizeAriaLabel: 'Resize assistant workspace',
|
||||
resizeValue: '{{width}} pixels',
|
||||
title: 'Workspace',
|
||||
close: 'Close assistant workspace',
|
||||
dismissOverlay: 'Dismiss assistant workspace overlay',
|
||||
categoriesAriaLabel: 'Workspace categories',
|
||||
tabs: {
|
||||
tasks: {
|
||||
|
||||
@@ -69,9 +69,6 @@ export const workspace = {
|
||||
ariaLabel: '助手工作栏',
|
||||
resizeAriaLabel: '调整助手工作栏宽度',
|
||||
resizeValue: '{{width}} 像素',
|
||||
title: '工作栏',
|
||||
close: '关闭助手工作栏',
|
||||
dismissOverlay: '关闭助手工作栏背景',
|
||||
categoriesAriaLabel: '工作栏分类',
|
||||
tabs: {
|
||||
tasks: {
|
||||
|
||||
+58
-85
@@ -2257,11 +2257,36 @@ textarea:focus-visible {
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.app-frame {
|
||||
position: relative;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
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;
|
||||
}
|
||||
|
||||
.app-shell {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
background: var(--surface-canvas);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
@@ -2952,7 +2977,7 @@ textarea:focus-visible {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background: var(--surface-raised);
|
||||
grid-template-rows: 58px minmax(0, 1fr) auto;
|
||||
grid-template-rows: 46px minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.workspace-route-cache {
|
||||
@@ -2978,6 +3003,10 @@ textarea:focus-visible {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.topbar--with-assistant-toggle {
|
||||
padding-right: 46px;
|
||||
}
|
||||
|
||||
.topbar :is(button, input, select),
|
||||
.topbar__actions,
|
||||
.window-controls {
|
||||
@@ -2989,7 +3018,7 @@ textarea:focus-visible {
|
||||
height: 100%;
|
||||
flex: 0 0 auto;
|
||||
align-self: stretch;
|
||||
margin-left: 8px;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.window-control {
|
||||
@@ -3074,8 +3103,8 @@ button > svg {
|
||||
}
|
||||
|
||||
.assistant-sidebar--open {
|
||||
width: var(--assistant-sidebar-width, 350px);
|
||||
flex-basis: var(--assistant-sidebar-width, 350px);
|
||||
width: var(--assistant-sidebar-width, 30%);
|
||||
flex-basis: var(--assistant-sidebar-width, 30%);
|
||||
border-left-width: 1px;
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -3123,30 +3152,28 @@ button > svg {
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.assistant-sidebar__header {
|
||||
display: flex;
|
||||
height: 58px;
|
||||
flex: 0 0 58px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 14px 0 18px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.assistant-sidebar__header strong {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.assistant-sidebar__tabs {
|
||||
display: grid;
|
||||
padding: 8px;
|
||||
min-height: 46px;
|
||||
padding: 6px 46px 6px 8px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
flex: 0 0 46px;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.assistant-sidebar-toggle {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.assistant-sidebar__tab {
|
||||
position: relative;
|
||||
min-height: 32px;
|
||||
padding: 7px 2px;
|
||||
border-radius: 7px;
|
||||
background: transparent;
|
||||
@@ -5111,6 +5138,9 @@ button > svg {
|
||||
}
|
||||
|
||||
.composer-wrap {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
padding:
|
||||
var(--space-3)
|
||||
max(var(--page-gutter), calc((100% - var(--content-reading)) / 2))
|
||||
@@ -5119,6 +5149,9 @@ button > svg {
|
||||
}
|
||||
|
||||
.composer {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
border: 1px solid var(--border-control);
|
||||
border-radius: var(--radius-card);
|
||||
@@ -5563,6 +5596,8 @@ button > svg {
|
||||
|
||||
.composer__toolbar {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 56px;
|
||||
padding: 10px var(--space-3);
|
||||
@@ -12527,7 +12562,7 @@ details.settings-section > :not(summary) + :not(summary) {
|
||||
--shadow-dialog: 0 18px 48px rgb(0 4 10 / 48%);
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] .app-shell {
|
||||
:root[data-theme='dark'] :where(.app-frame, .app-shell) {
|
||||
background: var(--surface-canvas);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
@@ -12544,6 +12579,7 @@ details.settings-section > :not(summary) + :not(summary) {
|
||||
|
||||
:root[data-theme='dark'] :where(
|
||||
.sidebar,
|
||||
.window-titlebar,
|
||||
.topbar,
|
||||
.assistant-sidebar,
|
||||
.project-create-card,
|
||||
@@ -12566,6 +12602,7 @@ details.settings-section > :not(summary) + :not(summary) {
|
||||
|
||||
:root[data-theme='dark'] :where(
|
||||
.sidebar,
|
||||
.window-titlebar,
|
||||
.topbar
|
||||
) {
|
||||
background: var(--surface-subtle);
|
||||
@@ -12623,9 +12660,9 @@ details.settings-section > :not(summary) + :not(summary) {
|
||||
:root[data-theme='dark'] :where(
|
||||
.primary-nav,
|
||||
.sidebar-footer,
|
||||
.window-titlebar,
|
||||
.topbar,
|
||||
.assistant-sidebar,
|
||||
.assistant-sidebar__header,
|
||||
.assistant-sidebar__tabs,
|
||||
.assistant-sidebar__context,
|
||||
.assistant-sidebar__preview > header,
|
||||
@@ -12944,38 +12981,6 @@ details.settings-section > :not(summary) + :not(summary) {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1279px) {
|
||||
.assistant-sidebar-backdrop {
|
||||
position: absolute;
|
||||
z-index: 25;
|
||||
display: block;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: var(--overlay-backdrop);
|
||||
inset: 58px 0 0;
|
||||
}
|
||||
|
||||
.assistant-sidebar {
|
||||
position: absolute;
|
||||
z-index: 30;
|
||||
top: 58px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
box-shadow: -12px 0 30px rgb(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
.assistant-sidebar--open {
|
||||
width: min(
|
||||
var(--assistant-sidebar-width, 350px),
|
||||
calc(100vw - 36px)
|
||||
);
|
||||
flex-basis: min(
|
||||
var(--assistant-sidebar-width, 350px),
|
||||
calc(100vw - 36px)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1020px) {
|
||||
.sidebar {
|
||||
flex-basis: 236px;
|
||||
@@ -12991,17 +12996,6 @@ details.settings-section > :not(summary) + :not(summary) {
|
||||
min-width: 206px;
|
||||
}
|
||||
|
||||
.assistant-sidebar--open {
|
||||
width: min(
|
||||
var(--assistant-sidebar-width, 350px),
|
||||
calc(100vw - 36px)
|
||||
);
|
||||
flex-basis: min(
|
||||
var(--assistant-sidebar-width, 350px),
|
||||
calc(100vw - 36px)
|
||||
);
|
||||
}
|
||||
|
||||
.settings-page .settings-panel__body {
|
||||
display: grid;
|
||||
overflow: hidden;
|
||||
@@ -13062,27 +13056,6 @@ details.settings-section > :not(summary) + :not(summary) {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 719px) {
|
||||
.assistant-sidebar {
|
||||
top: 0;
|
||||
z-index: 45;
|
||||
}
|
||||
|
||||
.assistant-sidebar--open {
|
||||
width: calc(100vw - 16px);
|
||||
flex-basis: calc(100vw - 16px);
|
||||
}
|
||||
|
||||
.assistant-sidebar-backdrop {
|
||||
z-index: 40;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.assistant-sidebar__resize-handle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.release-notes-backdrop {
|
||||
padding: var(--space-4);
|
||||
|
||||
Reference in New Issue
Block a user