diff --git a/apps/desktop/e2e-budget.json b/apps/desktop/e2e-budget.json index d56aab798f..028371a50d 100644 --- a/apps/desktop/e2e-budget.json +++ b/apps/desktop/e2e-budget.json @@ -49,6 +49,10 @@ "tests": 2, "electron": "Code Mode reads back Host settings through IPC; opening Settings commits a pending title rename to the Host, with workbar hide/restore asserted in that same Session." }, + "sidebar-layout.spec.ts": { + "tests": 2, + "electron": "native Electron keyboard input must reach titlebar search and the new-task composer, including focus handoff from the separate WorkHub WebContentsView, and resize the remounted sidebar handle after drag-collapse" + }, "sidebar-project-reload.spec.ts": { "tests": 1, "electron": "rail grouping has to be rebuilt from persisted project state after a renderer reload" diff --git a/apps/desktop/e2e/new-task-reload.spec.ts b/apps/desktop/e2e/new-task-reload.spec.ts index b3af053dd9..ea76a02939 100644 --- a/apps/desktop/e2e/new-task-reload.spec.ts +++ b/apps/desktop/e2e/new-task-reload.spec.ts @@ -98,7 +98,8 @@ test('an explicit new task survives a renderer reload without reopening history' }); await ensureSidebarExpanded(page); - await page.getByRole('button', { name: '新任务', exact: true }).click(); + await page.locator('.maka-shell-topbar-rail') + .getByRole('button', { name: '新任务', exact: true }).click(); await expect(page.locator('.maka-turn')).toHaveCount(0); await composer.fill('draft survives renderer replacement'); diff --git a/apps/desktop/e2e/sidebar-layout.spec.ts b/apps/desktop/e2e/sidebar-layout.spec.ts new file mode 100644 index 0000000000..eb2b2a23ba --- /dev/null +++ b/apps/desktop/e2e/sidebar-layout.spec.ts @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { COMPOSER_INPUT, ensureSidebarExpanded, expect, getWorkHubPage, test } from './fixtures'; + +// Native keyboard delivery must follow focus from the titlebar search action +// through dialog dismissal into the new-task composer. DOM focus alone would +// miss input lost when the separate WorkHub WebContentsView relinquishes focus. +test('titlebar search and new task receive native input, including when leaving WorkHub', async ({ + sessionLocalWindow: { page, app }, +}) => { + const nativeWindow = await app.browserWindow(page); + await nativeWindow.evaluate((window) => { + window.show(); + window.focus(); + window.webContents.focus(); + }); + const chrome = page.locator('.maka-shell-topbar-rail'); + const sidebar = page.locator('.maka-sidenav-motion'); + const composer = page.locator(COMPOSER_INPUT); + + const search = chrome.getByRole('button', { name: '搜索任务' }); + await search.focus(); + await search.press('Enter'); + const searchInput = page.getByRole('combobox', { name: '搜索任务' }); + await expect(searchInput).toBeFocused(); + await nativeWindow.evaluate((window) => { + for (const character of 'sidebar') { + window.webContents.sendInputEvent({ type: 'char', keyCode: character }); + } + }); + await expect(searchInput).toHaveValue('sidebar'); + await page.keyboard.press('Escape'); + await expect(search).toBeFocused(); + await page.keyboard.press('Tab'); + const newTask = chrome.getByRole('button', { name: '新任务', exact: true }); + await expect(newTask).toBeFocused(); + + await newTask.click(); + await expect(composer).toBeFocused(); + await expect(composer).toHaveText(''); + await nativeWindow.evaluate((window) => { + for (const character of 'new task draft') { + window.webContents.sendInputEvent({ type: 'char', keyCode: character }); + } + }); + await expect(composer).toHaveText('new task draft'); + + await ensureSidebarExpanded(page); + await page.getByRole('button', { name: '设置', exact: true }).click(); + await page.getByRole('button', { name: '通用', exact: true }).click(); + const enableWorkHub = page.getByRole('switch', { name: '启用 WorkHub', exact: true }); + await enableWorkHub.click(); + await expect(enableWorkHub).toBeChecked(); + await page.keyboard.press('Escape'); + await sidebar.getByRole('button', { name: 'WorkHub', exact: true }).click(); + const workhub = await getWorkHubPage(app); + await expect(page.locator('.workHubDock')).toBeVisible(); + await chrome.getByRole('button', { name: '收起侧边栏' }).click(); + await workhub.locator(COMPOSER_INPUT).click(); + await expect(workhub.locator(COMPOSER_INPUT)).toBeFocused(); + + await newTask.click(); + await expect(page.locator('.workHubDock')).toBeHidden(); + await expect(composer).toBeVisible(); + await expect(composer).toBeFocused(); + await expect(composer).toHaveText('new task draft'); + // Do not focus webContents here: the new-task action must hand input back. + await nativeWindow.evaluate((window) => { + for (const character of ' returned from WorkHub ') { + window.webContents.sendInputEvent({ type: 'char', keyCode: character }); + } + }); + await expect(composer).toContainText('returned from WorkHub'); +}); + +// After drag-collapse remounts Astryx's handle, Electron key events must still +// resize it. Geometry confirms native input reached the remounted control, +// rather than merely checking DOM focus or dispatching a renderer key event. +test('native keyboard resizing reaches the restored handle after drag-collapse', async ({ + sessionLocalWindow: { page, app }, +}) => { + const nativeWindow = await app.browserWindow(page); + await nativeWindow.evaluate((window) => { + window.show(); + window.focus(); + window.webContents.focus(); + }); + const chrome = page.locator('.maka-shell-topbar-rail'); + const sidebar = page.locator('.maka-sidenav-motion'); + const handle = page.getByTestId('astryx-sidenav-resize-handle'); + const sidebarWidth = async () => Math.round((await sidebar.boundingBox())?.width ?? 0); + await ensureSidebarExpanded(page); + await expect.poll(sidebarWidth).toBe(260); + + const box = await handle.boundingBox(); + if (!box) throw new Error('Missing sidebar resize handle'); + const x = box.x + box.width / 2; + const y = box.y + 80; + await page.mouse.move(x, y); + await page.mouse.down(); + await page.mouse.move(x + 60, y, { steps: 6 }); + await page.mouse.up(); + await expect.poll(sidebarWidth).toBe(320); + + const widened = await handle.boundingBox(); + if (!widened) throw new Error('Missing resized sidebar handle'); + await page.mouse.move(widened.x + widened.width / 2, y); + await page.mouse.down(); + // Cross the threshold in one move so an intermediate drag width is not saved. + await page.mouse.move(widened.x - 230, y); + await page.mouse.up(); + await expect(sidebar).not.toBeVisible(); + await expect.poll(sidebarWidth).toBe(0); + + await chrome.getByRole('button', { name: '展开侧边栏' }).click(); + await expect.poll(sidebarWidth).toBe(320); + await handle.focus(); + await expect(handle).toBeFocused(); + await nativeWindow.evaluate((window) => { + window.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Right' }); + window.webContents.sendInputEvent({ type: 'keyUp', keyCode: 'Right' }); + }); + await expect.poll(sidebarWidth).toBe(330); +}); diff --git a/apps/desktop/renderer-architecture.json b/apps/desktop/renderer-architecture.json index d15396c9eb..45e60a9ce4 100644 --- a/apps/desktop/renderer-architecture.json +++ b/apps/desktop/renderer-architecture.json @@ -5,7 +5,6 @@ "src/renderer/agent-graph-panel.tsx", "src/renderer/agent-graph-refresh.ts", "src/renderer/app-shell-chat-actions.ts", - "src/renderer/app-shell-chrome-actions.tsx", "src/renderer/app-shell-command-actions.ts", "src/renderer/app-shell-context-compaction.ts", "src/renderer/app-shell-copy.ts", @@ -324,26 +323,6 @@ "importSpecifiers": 10, "nonTriviaTokens": 3618 }, - "src/renderer/app-shell-chrome-actions.tsx": { - "importDeclarations": 4, - "bridgePaths": {}, - "environmentCapabilities": {}, - "hookCalls": { - "useUiLocale": 1 - }, - "lifecycleMethods": {}, - "unresolvedDependencies": 0, - "actionFactories": [], - "dependencyPaths": { - "./locales/shell-copy": 1, - "@astryxdesign/core/Icon": 1, - "@astryxdesign/core/Tooltip": 1, - "@maka/ui": 1, - "@maka/ui/icons": 1 - }, - "importSpecifiers": 7, - "nonTriviaTokens": 408 - }, "src/renderer/app-shell-command-actions.ts": { "importDeclarations": 5, "bridgePaths": { @@ -698,7 +677,7 @@ "nonTriviaTokens": 1273 }, "src/renderer/app-shell.tsx": { - "importDeclarations": 68, + "importDeclarations": 67, "bridgePaths": { "window.maka.attachments": 1, "window.maka.attachments.readBytes": 1, @@ -772,7 +751,6 @@ "dependencyPaths": { "./agent-graph-panel": 1, "./app-shell-chat-actions": 1, - "./app-shell-chrome-actions": 1, "./app-shell-context-compaction": 1, "./app-shell-detail-panel": 1, "./app-shell-e2e-fixture": 1, @@ -823,6 +801,7 @@ "./settings/provider-brand-marks": 1, "./settings/provider-display": 1, "./settings/runtime-host-ssh-terminal-dialog.js": 1, + "./shell/topbar-actions": 1, "./stale-sessions": 1, "./use-active-execution-boundary": 1, "./use-app-shell-composer-quotes": 1, @@ -853,7 +832,7 @@ "@maka/ui": 1, "react": 1 }, - "importSpecifiers": 104, + "importSpecifiers": 103, "nonTriviaTokens": 13386 }, "src/renderer/use-app-shell-composer-quotes.ts": { @@ -4564,7 +4543,6 @@ "capability": "shell-root-and-frame", "targetZone": "shell", "legacyPaths": [ - "src/renderer/app-shell-chrome-actions.tsx", "src/renderer/app-shell-detail-panel.tsx" ] }, diff --git a/apps/desktop/src/renderer/app-shell.tsx b/apps/desktop/src/renderer/app-shell.tsx index 47068f73dc..d4f7ed1521 100644 --- a/apps/desktop/src/renderer/app-shell.tsx +++ b/apps/desktop/src/renderer/app-shell.tsx @@ -143,7 +143,7 @@ import { createContextCompactionPresentation, presentContextCompactionResult, } from './app-shell-context-compaction'; -import { AppShellTopbarActions } from './app-shell-chrome-actions'; +import { AppShellTopbarActions } from './shell/topbar-actions'; import { AppShellDetailPanel } from './app-shell-detail-panel'; import { AppShellOverlays } from './app-shell-overlays'; import type { ArchivedTasksBridge } from './settings/tasks-settings-page'; @@ -1177,7 +1177,7 @@ function AppShellContent({ sessionProfileKind: sharedSessionActive ? undefined : activeDesktopSession?.profileKind, onProjectSelected: (ownerSessionId) => { void moduleHubCommands.refreshProjectSkills(); - if (ownerSessionId && activeIdRef.current === ownerSessionId) openNewTaskSurface(); + if (ownerSessionId && activeIdRef.current === ownerSessionId) void createSession(); }, toastApi, }); @@ -1239,7 +1239,8 @@ function AppShellContent({ showModelSetupToast, toastApi, }); - const openNewTaskSurface = useCallback(() => { + const createSession = useCallback(async () => { + setWorkHubActive(false); imageNoticeLifecycle.reset(NEW_TASK_PENDING_KEY); startNewSession(); // Only Plan resets: a new task starts out of Plan, in whatever @@ -1252,18 +1253,13 @@ function AppShellContent({ window.requestAnimationFrame(() => composerRef.current?.focus()); }, [imageNoticeLifecycle, setNavSelection, setSearchScrollTarget, startNewSession]); - const createSession = useCallback(async () => { - openNewTaskSurface(); - }, [openNewTaskSurface]); - // Stable, because the rail's Project rows carry it: a fresh identity here // rebuilt the whole list on every AppShell commit (#4109). const createSessionInProject = useCallback( async (projectId: string) => { - if (!selectLocalProject(projectId)) return; - openNewTaskSurface(); + if (selectLocalProject(projectId)) return createSession(); }, - [openNewTaskSurface, selectLocalProject], + [createSession, selectLocalProject], ); // Sidebar Project groups are Local. Their catalog mutations remain on the @@ -2317,9 +2313,11 @@ function AppShellContent({ {!settingsOpen && ( <> sessionSideNavHandleRef.current?.getCollapseState()?.toggle()} onOpenSearchModal={() => setSearchModalOpen(true)} + onNewTask={createSession} /> {/* Only a session has an identity to state. The other views name themselves in the nav column they are selected from, and the @@ -2737,7 +2735,7 @@ function AppShellContent({ onTaskReadinessAction={ taskReadinessNotice?.action === 'workspace_picker' ? activeSession - ? openNewTaskSurface + ? createSession : taskEntry.selectors.canAddProject ? taskEntry.commands.addProject : undefined @@ -2846,7 +2844,7 @@ function AppShellContent({ }} onRemoteHostAdded={(profileId) => { closeSettings(); - openNewTaskSurface(); + void createSession(); void taskEntry.commands.chooseProjectForProfile(profileId).catch(() => undefined); }} onSelectedRuntimeHostProfileIdChange={setSettingsProfileId} diff --git a/apps/desktop/src/renderer/app-shell-chrome-actions.tsx b/apps/desktop/src/renderer/shell/topbar-actions.tsx similarity index 54% rename from apps/desktop/src/renderer/app-shell-chrome-actions.tsx rename to apps/desktop/src/renderer/shell/topbar-actions.tsx index 06eae61b58..b8a59394fa 100644 --- a/apps/desktop/src/renderer/app-shell-chrome-actions.tsx +++ b/apps/desktop/src/renderer/shell/topbar-actions.tsx @@ -21,14 +21,11 @@ import { PanelLeftClose, PanelLeftOpen, Search, + SquarePen, } from '@maka/ui/icons'; -import { - IconButton, - useUiLocale, -} from '@maka/ui'; +import { IconButton } from '@maka/ui'; import { Icon } from '@astryxdesign/core/Icon'; import { Tooltip } from '@astryxdesign/core/Tooltip'; -import { getShellCopy } from './locales/shell-copy'; /** * Match SideNavItem collapsed/expanded icon slot: Astryx `renderIconSlot` @@ -42,17 +39,6 @@ function ChromeIcon(props: { icon: typeof Search }) { /** * A titlebar column toggle: one button whose icon, tooltip, accessible name and * `aria-expanded` all read from the same boolean, in both directions. - * - * The sidebar's toggle used to be Astryx's `SideNavCollapseButton`, wired - * across the tree by a `handleRef` so a click reached SideNav's internal - * collapse state, which called `onCollapsedChange`, which set the shell state - * that was already being passed back down as `isCollapsed`. Three hops to a - * setter the shell holds directly — and none of the component's own outputs - * survived: the shell overrode its label and its icon, its `isCollapsible` - * check could not fail (the ref reads `null` on first render and falls back to - * `true`), and its mobile branch is unreachable behind `breakpoint: 'none'`. - * What is left after removing all of that is this button, which is also exactly - * what the workbar's toggle already was. */ function ChromeColumnToggle(props: { collapsed: boolean; @@ -60,7 +46,6 @@ function ChromeColumnToggle(props: { collapseLabel: string; expandIcon: typeof Search; collapseIcon: typeof Search; - className?: string; onToggle(): void; }) { const label = props.collapsed ? props.expandLabel : props.collapseLabel; @@ -71,11 +56,7 @@ function ChromeColumnToggle(props: { icon={} variant="ghost" size="md" - className={ - props.className - ? `maka-titlebar-action ${props.className}` - : 'maka-titlebar-action' - } + className="maka-titlebar-action" onClick={props.onToggle} aria-expanded={!props.collapsed} /> @@ -84,14 +65,29 @@ function ChromeColumnToggle(props: { } export function AppShellTopbarActions(props: { + copy: { + windowActions: string; + expandSidebar: string; + collapseSidebar: string; + searchConversations: string; + newTask: string; + }; sidebarCollapsed: boolean; onToggleSidebar(): void; onOpenSearchModal(): void; + onNewTask(): void; }) { - const locale = useUiLocale(); - const copy = getShellCopy(locale).chrome; + const { copy } = props; return (
+ - - {/* Collapsed "new task" lives on the SideNav rail (SessionSidebarNav), - not here — a third titlebar button duplicated the rail icon and made - left-cluster width state-dependent for drag-region math. */} + + } + variant="ghost" + size="md" + className="maka-titlebar-action" + onClick={props.onNewTask} + /> +
); } - -/** - * The titlebar's right edge. - * - * It used to also carry a `…` menu holding 问题反馈 / 打开命令面板 / 打开帮助 / - * 打开健康中心 — a drawer of four things that each belong somewhere else. - * 健康中心 was a duplicate of the Settings nav entry, 问题反馈 only opened - * Settings → 关于, and the other two now have real homes: the keyboard sheet is - * a row on 关于, and the palette keeps ⌘K, which that sheet documents. - * - * What is left is the collapsed workbar's restore affordance. While the - * workbar is open, the matching toggle belongs to the workbar's tab toolbar: - * tabs, new-tab and panel visibility are one local control group. That toolbar - * occupies this same titlebar band, so the control keeps its screen position - * across the hand-off instead of moving down into a second row. - * - * The titlebar is also the only row in the app that already knows where the - * platform's native window controls are: `.maka-window-titlebar` reserves - * `env(titlebar-area-*)` on both sides, so this button clears the macOS traffic - * lights and the Windows caption strip without either platform being named - * here. A toggle parked anywhere else would have to restate that. - */ diff --git a/apps/desktop/src/renderer/styles/shell-layout.css b/apps/desktop/src/renderer/styles/shell-layout.css index ef1368dc9f..1ded6e9867 100644 --- a/apps/desktop/src/renderer/styles/shell-layout.css +++ b/apps/desktop/src/renderer/styles/shell-layout.css @@ -235,8 +235,7 @@ neighbours with a hand-summed `margin-left`/`margin-right` ruler. Those rulers encoded "how many titlebar buttons exist right now" — state-dependent when a third titlebar button (新任务) only appeared while the sidebar was - collapsed (that button is gone now; 新任务 stays on the SideNav rail). The - sidebar's strip reserved room for two, the third button landed in the space + collapsed. The sidebar's strip reserved room for two, the third button landed in the space nobody reserved, and clicks on it reached the OS as window drags. The inset keeps the drag rect off the window frame on the three sides it @@ -290,10 +289,9 @@ `--maka-titlebar-gap` (8px) of overhang instead of the intended flush start. - `minmax(..., auto)` rather than a fixed width for the collapsed state, - where the navigation column is absent but the two titlebar actions remain: - the track grows to those actions and the gap then holds the breadcrumb off - them, which is the same clearance by a different measure. + Clamp the floor to zero when the sidebar is absent. `max-content` keeps + the fixed action cluster in that track without absorbing spare titlebar + space; the middle track owns the remaining room for the breadcrumb. Every child names its own column. Two of the three are conditional — the breadcrumb only in a session, the workspace actions only in views that have @@ -302,11 +300,14 @@ display: grid; grid-template-columns: minmax( - calc( - var(--maka-sidenav-width) - var(--maka-window-resize-edge) - - var(--maka-titlebar-gutter-left) - var(--maka-titlebar-gap) + max( + 0px, + calc( + var(--maka-sidenav-width) - var(--maka-window-resize-edge) - + var(--maka-titlebar-gutter-left) - var(--maka-titlebar-gap) + ) ), - auto + max-content ) minmax(0, 1fr) auto; @@ -342,11 +343,8 @@ dialog:modal { .maka-shell-topbar-rail { grid-column: 1; - /* These controls operate on the sidebar, so their anchor is its trailing - edge — not the platform-safe area's leading edge beside the traffic - lights. The first titlebar grid track already ends one gap before the - sidebar seam and follows the user-resized sidebar width. */ - justify-self: end; + /* Collapse and resize move the column edge, never the global actions. */ + justify-self: start; display: flex; align-items: center; /* Astryx Toolbar spaces independent actions by spacing-1 (4px). */ @@ -466,7 +464,7 @@ dialog:modal { } /* Titlebar chrome glyph ink is set where the glyph is: `ChromeIcon` - (app-shell-chrome-actions.tsx) renders Astryx Icon with `color="secondary"`, + (shell/topbar-actions.tsx) renders Astryx Icon with `color="secondary"`, and Icon puts `color: var(--color-icon-secondary)` on the svg ITSELF. A button-level rule cannot reach past that — a child's own `color` always beats an inherited one — so the ghost variant's primary text never applied to these diff --git a/apps/desktop/stories/app-shell.stories.tsx b/apps/desktop/stories/app-shell.stories.tsx index f5fe9688dd..aa471a68b5 100644 --- a/apps/desktop/stories/app-shell.stories.tsx +++ b/apps/desktop/stories/app-shell.stories.tsx @@ -34,7 +34,8 @@ import { } from '@maka/ui'; import type { ChatModelChoice, SessionViewMode, TurnViewModel } from '@maka/ui'; import { SessionRail, type SessionRailStoryProps } from '../../../packages/ui/stories/session-rail-harness.js'; -import { AppShellTopbarActions } from '../src/renderer/app-shell-chrome-actions'; +import { AppShellTopbarActions } from '../src/renderer/shell/topbar-actions'; +import { getShellCopy } from '../src/renderer/locales/shell-copy'; import { SettingsOverlay } from '../src/renderer/app-shell-overlays'; import { WorkbarServicesProvider, @@ -413,9 +414,11 @@ function ComposedShell(props: { >
setCollapsed((current) => !current)} onOpenSearchModal={noop} + onNewTask={noop} /> {/* Derived from the same session and project catalog the sidebar reads, not hand-passed: a story cannot show a project the session does not @@ -529,12 +532,22 @@ export const DefaultLayout: Story = { if (!actions) throw new Error('Shell topbar rail did not render'); await expect(sidebar).toBeVisible(); await expect(actions).toBeVisible(); - const sidebarBox = sidebar.getBoundingClientRect(); - const actionsBox = actions.getBoundingClientRect(); - const trailingInset = sidebarBox.right - actionsBox.right; - expect(trailingInset).toBeGreaterThanOrEqual(0); - expect(trailingInset).toBeLessThanOrEqual(16); + const actionBounds = () => Array.from(actions.querySelectorAll('button'), (button) => { + const { x, y, width, height } = button.getBoundingClientRect(); + return { x, y, width, height }; + }); + const expandedBounds = actionBounds(); + expect(expandedBounds).toHaveLength(3); + const titlebar = actions.closest('header')!; + const leadingInset = actions.getBoundingClientRect().left - titlebar.getBoundingClientRect().left; + expect(leadingInset).toBe(parseFloat(getComputedStyle(titlebar).paddingLeft)); expect(getComputedStyle(actions).columnGap).toBe('4px'); + await userEvent.click(canvas.getByRole('button', { name: '收起侧边栏' })); + await waitFor(() => expect(actionBounds()).toEqual(expandedBounds)); + expect(canvas.queryByRole('navigation', { name: '任务列表' })).toBeNull(); + await userEvent.click(canvas.getByRole('button', { name: '展开侧边栏' })); + await waitFor(() => expect(canvas.getByRole('navigation', { name: '任务列表' })).toBeVisible()); + expect(actionBounds()).toEqual(expandedBounds); }, }; diff --git a/docs/astryx-surface-file-inventory.md b/docs/astryx-surface-file-inventory.md index ff6a01a028..07f5cf508d 100644 --- a/docs/astryx-surface-file-inventory.md +++ b/docs/astryx-surface-file-inventory.md @@ -28,7 +28,6 @@ Wiki bar: Design Conventions · API Use-the-System · Theming · Container Paddi | Path | Role | Astryx used | Gap / note | Severity | |------|------|-------------|------------|----------| | `apps/desktop/src/renderer/agent-graph-panel.tsx` | shell-chrome-or-panel | Banner, Button, EmptyState, IconButton, Selector, Spinner | aligned — uses Astryx (Banner, Button, EmptyState, IconButton, Selector, Spinner) | aligned | -| `apps/desktop/src/renderer/app-shell-chrome-actions.tsx` | shell-chrome-or-panel | Icon, IconButton, Tooltip | aligned — uses Astryx (Icon, IconButton, Tooltip) | aligned | | `apps/desktop/src/renderer/app-shell-detail-panel.tsx` | shell-chrome-or-panel | none | aligned — no raw controls; no Astryx JSX usage | aligned | | `apps/desktop/src/renderer/app-shell-overlays.tsx` | shell-chrome-or-panel | Spinner | aligned — uses Astryx (Spinner) | aligned | | `apps/desktop/src/renderer/app-shell.tsx` | shell-chrome-or-panel | AppShell | aligned — uses Astryx (AppShell) | aligned | @@ -178,6 +177,7 @@ Wiki bar: Design Conventions · API Use-the-System · Theming · Container Paddi | `apps/desktop/src/renderer/settings/tasks-settings-page.tsx` | settings-page | Button, EmptyState, HStack, List, ListItem, MoreMenu, StackItem, TextInput | aligned — uses Astryx (Button, EmptyState, HStack, List, ListItem, MoreMenu, StackItem, TextInput) | aligned | | `apps/desktop/src/renderer/settings/usage-settings-page.tsx` | settings-page | none | aligned — no raw controls; no Astryx JSX usage | aligned | | `apps/desktop/src/renderer/settings/web-search-settings-page.tsx` | settings-page | Banner, Button, EmptyState, Link, Selector, StatusDot, Switch, TextInput | aligned — uses Astryx (Banner, Button, EmptyState, Link, Selector, StatusDot, Switch, TextInput) | aligned | +| `apps/desktop/src/renderer/shell/topbar-actions.tsx` | other | Icon, IconButton, Tooltip | aligned — uses Astryx (Icon, IconButton, Tooltip) | aligned | | `apps/desktop/src/renderer/styles.css` | styles | n/a (css) | aligned — no off-rhythm control heights flagged | aligned | | `apps/desktop/src/renderer/styles/agent-graph.css` | styles | n/a (css) | aligned — no off-rhythm control heights flagged | aligned | | `apps/desktop/src/renderer/styles/astryx-mount.css` | styles | n/a (css) | aligned — no off-rhythm control heights flagged | aligned | diff --git a/docs/astryx-surface-file-inventory.paths b/docs/astryx-surface-file-inventory.paths index 222b8df18c..5776722bc7 100644 --- a/docs/astryx-surface-file-inventory.paths +++ b/docs/astryx-surface-file-inventory.paths @@ -1,5 +1,4 @@ apps/desktop/src/renderer/agent-graph-panel.tsx -apps/desktop/src/renderer/app-shell-chrome-actions.tsx apps/desktop/src/renderer/app-shell-detail-panel.tsx apps/desktop/src/renderer/app-shell-overlays.tsx apps/desktop/src/renderer/app-shell.tsx @@ -149,6 +148,7 @@ apps/desktop/src/renderer/settings/subagent-settings-page.tsx apps/desktop/src/renderer/settings/tasks-settings-page.tsx apps/desktop/src/renderer/settings/usage-settings-page.tsx apps/desktop/src/renderer/settings/web-search-settings-page.tsx +apps/desktop/src/renderer/shell/topbar-actions.tsx apps/desktop/src/renderer/styles.css apps/desktop/src/renderer/styles/agent-graph.css apps/desktop/src/renderer/styles/astryx-mount.css