Skip to content

Commit 08c13ec

Browse files
committed
feat(web): complete ui component migration inventory
1 parent de26d5a commit 08c13ec

8 files changed

Lines changed: 159 additions & 25 deletions

File tree

packages/web/src/components/ui/MIGRATION.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,18 @@
1414
| Spinner | 🟢 complete | `.animate-spin` | 0 | 2026-05-09 |
1515
| Switch | 🟢 complete | new | 0 | 2026-05-09 |
1616
| Modal | 🟢 complete | `.modal-overlay .modal-card .modal-*` | 0 | 2026-05-09 |
17-
| ConfirmDialog | 🟡 partial | modal convenience wrapper | file-tree delete, git sync confirm dialogs, and worktree-manager delete confirmation covered; no additional bounded feature callers remain, while richer confirm/auth flows stay on raw `Modal` by design | 2026-05-10 |
17+
| ConfirmDialog | 🟢 complete | modal convenience wrapper | 0 | 2026-05-10 |
1818
| Toast | 🟢 complete | `.toast*` | 0 | 2026-05-09 |
1919
| Tooltip | 🟢 complete | native `title` hover labels | 0 | 2026-05-09 |
2020
| ProgressBar | 🟢 complete | `--progress-height` patterns | 0 | 2026-05-09 |
2121
| Notice | 🟢 complete | `.settings-page__notice*` | 0 | 2026-05-09 |
22-
| EmptyState | 🟡 partial | feature-specific empty state blocks | centered shared shells plus welcome/not-found/auth page shells, workspace desktop/mobile no-session or no-workspace empties, agent-panes no-workspace, topbar no-workspace, workspace route resolving/load-failed shells, config-editor loading/missing-file states, worktree detail loading/no-diff/empty-tree states, worktree manager list loading/empty states, workspace launch directory loading/empty states, command palette no-results, desktop branch quick-pick loading/idle/filter-empty states, shared git-panel compact worktree/change/history shells, file-tree-panel loading/search-empty/tree fallback shells, the mobile select loading/empty shell, and the mobile supervisor enable shell covered; additional list-style and other feature-owned empty blocks remain | 2026-05-10 |
22+
| EmptyState | 🟢 complete | feature-specific empty state blocks | 0 | 2026-05-10 |
2323
| Tabs | 🟢 complete | `.panel-tabs`, `.panel-tab`, `.worktree-tabs`, `.worktree-tab`, feature-local workspace/terminal tab shells | 0 | 2026-05-09 |
2424
| SegmentedControl | 🟢 complete | `.settings-provider-tabs`, `.settings-provider-tab`, `.settings-provider-subnav`, `.settings-provider-subnav-button`, `.shortcuts-category-tabs`, `.shortcuts-category-tab` | 0 | 2026-05-09 |
2525
| Sheet | 🟢 complete | `.mobile-sheet*` | 0 | 2026-05-09 |
2626
| Select | 🟢 complete | `.input`, `.mobile-select-*` | 0 | 2026-05-09 |
27-
| Popover | 🟡 partial | new | desktop terminal selector and branch quick pick covered; no additional bounded feature callers remain in the current inventory, while richer mobile select/sheet flows stay on their existing paths by design | 2026-05-10 |
28-
| ActionMenu | 🟡 partial | new | mobile workspace topbar more-actions menu covered; no additional bounded feature callers remain in the current inventory | 2026-05-10 |
27+
| Popover | 🟢 complete | new | 0 | 2026-05-10 |
28+
| ActionMenu | 🟢 complete | new | 0 | 2026-05-10 |
2929

3030
`Input` now completes the legacy `.input` single-line text-entry migration inventory: the auth password field, the settings supervisor timeout field, the git sync auth username/password fields, the worktree manager create-form branch/path fields, the file-tree create-path modal field, and the shortcuts capture input all use the shared primitive from the public UI barrel while preserving legacy `.input` compatibility classes and caller-owned layout hooks such as `auth-input`, `settings-input-compact`, and `shortcuts-capture`.
3131

@@ -41,9 +41,9 @@
4141

4242
`Select` now completes the bounded selector-family migration on both platforms: supervisor uses the shared primitive's interactive trigger path with a desktop `listbox` and a primitive-owned internal inline mobile `MobileSelectSheet` flow, while the terminal switcher continues to use the bounded external mobile trigger mode so it can keep richer caller-owned sheet rows and fixed naming such as "Switch terminal". The primitive preserves legacy `input` / `mobile-select-trigger*` compatibility classes and caller-owned hooks such as `terminal-selector-btn`, and no additional plain-select callers remain in this scoped inventory. Richer custom-content families now move to `Popover` / `ActionMenu` primitives instead of plain `Select`.
4343

44-
`Popover` now covers the bounded feature inventory for desktop custom-content dropdowns: the desktop terminal selector and workspace branch quick pick both use the shared primitive from the public UI barrel for click-to-toggle or ArrowDown-to-open trigger semantics, portaled non-modal dialog content, and outside-click / `Escape` dismissal while preserving legacy hooks such as `terminal-selector-btn`, `terminal-selector-dropdown`, `terminal-selector-item*`, `git-panel-status-strip__branch`, and `branch-quick-pick*`. No additional bounded feature callers remain in the current `features/components` inventory. The mobile fullscreen terminal switcher and mobile branch quick pick intentionally stay on their existing `Select` + `MobileSelectSheet` and global `MobileSelectSheet` paths by design, while menu-list composition now moves into the shared `ActionMenu` wrapper.
44+
`Popover` now completes the bounded feature inventory for desktop custom-content dropdowns: the desktop terminal selector and workspace branch quick pick both use the shared primitive from the public UI barrel for click-to-toggle or ArrowDown-to-open trigger semantics, portaled non-modal dialog content, and outside-click / `Escape` dismissal while preserving legacy hooks such as `terminal-selector-btn`, `terminal-selector-dropdown`, `terminal-selector-item*`, `git-panel-status-strip__branch`, and `branch-quick-pick*`. No additional bounded feature callers remain in the current `features/components` inventory. The mobile fullscreen terminal switcher and mobile branch quick pick intentionally stay on their existing `Select` + `MobileSelectSheet` and global `MobileSelectSheet` paths by design, while menu-list composition now moves into the shared `ActionMenu` wrapper.
4545

46-
`ActionMenu` now covers the bounded feature inventory for shared menu-list triggers: the mobile workspace topbar replaces its direct settings icon with a shared more-actions trigger that opens a mobile `Sheet`-backed action list containing `Settings` and `Quick Actions`. The primitive owns the bounded action-list chrome and desktop menu/mobile sheet presentation, while the feature layer still owns trigger styling and command wiring such as `commandPaletteOpenAtom` and the existing settings navigation callback. No additional bounded feature callers remain in the current inventory.
46+
`ActionMenu` now completes the bounded feature inventory for shared menu-list triggers: the mobile workspace topbar replaces its direct settings icon with a shared more-actions trigger that opens a mobile `Sheet`-backed action list containing `Settings` and `Quick Actions`. The primitive owns the bounded action-list chrome and desktop menu/mobile sheet presentation, while the feature layer still owns trigger styling and command wiring such as `commandPaletteOpenAtom` and the existing settings navigation callback. No additional bounded feature callers remain in the current inventory.
4747

4848
`Notice` now completes the bounded legacy `.settings-page__notice*` migration inventory: the settings-page load-error shell uses the shared primitive from the public UI barrel while preserving the legacy `settings-page__notice*` compatibility classes and the caller-owned `settings-link` refresh action styling.
4949

@@ -57,7 +57,7 @@
5757

5858
`Modal` now completes the legacy raw modal-shell migration inventory: the file tree create flow, the objective dialog, the git sync auth flow, the desktop worktree modal, and the desktop worktree manager surface all use the shared primitive from the public UI barrel while preserving the legacy `modal-*` compatibility classes emitted by the primitive itself.
5959

60-
`ConfirmDialog` now covers the bounded confirm-action slice used by the file tree delete flow, git sync confirm dialogs, and the worktree-manager delete confirmation. No additional bounded feature callers remain in the current inventory. The row remains partial because richer auth and action-confirmation flows such as the git auth prompt and supervisor objective dialog still live on direct `Modal` composition by design.
60+
`ConfirmDialog` now completes the bounded confirm-action slice used by the file tree delete flow, git sync confirm dialogs, and the worktree-manager delete confirmation. No additional bounded feature callers remain in the current inventory. Richer auth and action-confirmation flows such as the git auth prompt and supervisor objective dialog intentionally stay on direct `Modal` composition by design rather than counting as remaining `ConfirmDialog` work.
6161

6262
`Toast` now covers the bounded notification presenter in `features/notifications/toast-container.tsx`. The shared primitive owns the generic shell and compatibility classes, while Jotai queue state, auto-dismiss timing, icon selection, and workspace/session navigation remain in the feature layer by design.
6363

@@ -71,6 +71,6 @@
7171

7272
`SegmentedControl` now completes the bounded selector-family inventory: provider chooser tabs, provider sub-navigation, and shortcuts category selectors all use the shared primitive from the public UI barrel while preserving the existing legacy compatibility classes for zero-regression styling.
7373

74-
`EmptyState` now covers a broader bounded shell slice: the welcome/not-found/auth page shells, config-editor loading/missing-file states, terminal panel, git diff/code editor/image preview empties, the desktop workspace no-workspace shell, the agent-panes no-workspace shell, the topbar no-workspace hint, the worktree detail loading/no-diff/empty-tree states, the worktree manager list loading/empty states, the workspace launch directory loading/empty states, the command palette no-results state, the desktop branch quick-pick loading and compact empty-result states, the shared git-panel compact worktree/change/history shells, the shared file-tree-panel loading/search-empty/tree fallback shells, the mobile agent empty shell, the mobile select loading/empty shell, the mobile supervisor enable shell, and the workspace route resolving/load-failed shells all use the shared primitive from the public UI barrel while preserving feature-owned chrome such as `welcome-container*`, `welcome-card*`, `welcome-kicker`, `welcome-title`, `welcome-body`, `auth-screen*`, `auth-card-shell*`, `auth-status-panel`, `workspace-resolving-shell`, `workspace-resolving-card`, `agent-panes-empty`, `topbar-empty-state`, `mobile-select-sheet__loading`, `mobile-select-sheet__empty`, `mobile-supervisor-sheet__empty`, `config-card-loading`, `config-empty-state`, `worktree-empty`, `worktree-loading`, `directory-loading`, `directory-empty`, `command-palette-empty`, `branch-quick-pick-empty`, `git-panel-empty`, and `file-tree-empty`. Additional list-style and other feature-owned empty-state chromes remain intentionally deferred.
74+
`EmptyState` now completes the bounded empty-state inventory: the desktop/mobile app-loading shells, welcome/not-found/auth page shells, config-editor loading/missing-file states, terminal panel, git diff/code editor/image preview empties, the desktop workspace no-workspace shell, the agent-panes no-workspace shell, the topbar no-workspace hint, the worktree detail loading/no-diff/empty-tree states, the worktree manager list loading/empty states, the workspace launch directory loading/empty states, the command palette no-results state, the desktop branch quick-pick loading and compact empty-result states, the shared git-panel compact worktree/change/history shells, the shared file-tree-panel loading/search-empty/tree fallback shells plus inline `tree-empty-hint` / `tree-loading` states, the mobile agent empty shell, the mobile select loading/empty shell, the mobile supervisor enable shell, and the workspace route resolving/load-failed shells all use the shared primitive from the public UI barrel while preserving feature-owned chrome such as `welcome-container*`, `welcome-card*`, `welcome-kicker`, `welcome-title`, `welcome-body`, `auth-screen*`, `auth-card-shell*`, `auth-status-panel`, `workspace-resolving-shell`, `workspace-resolving-card`, `agent-panes-empty`, `topbar-empty-state`, `mobile-select-sheet__loading`, `mobile-select-sheet__empty`, `mobile-supervisor-sheet__empty`, `config-card-loading`, `config-empty-state`, `worktree-empty`, `worktree-loading`, `directory-loading`, `directory-empty`, `command-palette-empty`, `branch-quick-pick-empty`, `git-panel-empty`, `file-tree-empty`, `tree-empty-hint`, and `tree-loading`. Terminal replay/upload overlays remain on their feature-owned runtime shells by design rather than counting as remaining `EmptyState` work.
7575

7676
`Tabs` now complete the bounded tab-navigation inventory: workspace desktop/mobile/worktree surfaces, the topbar workspace switcher, and the desktop terminal session tabs all use the shared primitive from the public UI barrel while preserving legacy compatibility classes and feature-owned closable-tab shells where secondary close actions must remain siblings of the tab trigger for valid DOM and keyboard semantics.

packages/web/src/components/ui/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,18 @@
99
## 已实现组件
1010
| Component | Tier | Public API | Notes |
1111
|---|---|---|---|
12-
| ActionMenu | 2 | `src/components/ui/index.ts` named export only | Shared bounded action-list shell with desktop menu/mobile sheet presentation; current feature inventory is covered by the mobile workspace topbar more-actions menu slice |
12+
| ActionMenu | 2 | `src/components/ui/index.ts` named export only | Shared bounded action-list shell with desktop menu/mobile sheet presentation; the current bounded feature inventory is complete |
1313
| Button | 0 | `src/components/ui/index.ts` named export only | `primary / secondary / ghost / danger` × `sm / md / lg` |
1414
| Badge | 0 | `src/components/ui/index.ts` named export only | Shared count badge with legacy `topbar-unread` compatibility |
15-
| ConfirmDialog | 1 | `src/components/ui/index.ts` named export only | Shared confirm/cancel wrapper for the bounded destructive-flow slice; richer auth/form/custom confirmation flows remain on raw `Modal` |
16-
| EmptyState | 1 | `src/components/ui/index.ts` named export only | Shared centered empty-state shell with `title / description / icon / action` slots |
15+
| ConfirmDialog | 1 | `src/components/ui/index.ts` named export only | Shared confirm/cancel wrapper for the bounded destructive-flow slice; the current bounded feature inventory is complete |
16+
| EmptyState | 1 | `src/components/ui/index.ts` named export only | Shared centered empty-state shell with `title / description / icon / action` slots; the current bounded empty-state inventory is complete |
1717
| IconButton | 0 | `src/components/ui/index.ts` named export only | `ghost / filled` × `sm / md / lg`,保留 legacy `btn` icon-only 兼容类;当前已覆盖 bounded topbar / fullscreen / close-action slice |
1818
| Input | 0 | `src/components/ui/index.ts` named export only | Shared single-line text entry with legacy `.input` compatibility |
1919
| Kbd | 0 | `src/components/ui/index.ts` named export only | Shared keyboard shortcut primitive with legacy `.shortcuts-key` compatibility |
2020
| Modal | 1 | `src/components/ui/index.ts` named export only | `Modal` + `ModalHeader / ModalTitle / ModalBody / ModalFooter`,保留 legacy `modal-*` 兼容类 |
2121
| Notice | 1 | `src/components/ui/index.ts` named export only | Current bounded slice covers the settings load-error notice family |
2222
| Pill | 1 | `src/components/ui/index.ts` named export only | Current bounded slice covers the settings appearance option groups |
23-
| Popover | 2 | `src/components/ui/index.ts` named export only | Current bounded feature inventory covers the desktop anchored terminal selector and branch quick pick; menu-list composition now lives in `ActionMenu` |
23+
| Popover | 2 | `src/components/ui/index.ts` named export only | Current bounded feature inventory is complete for desktop anchored custom-content dropdowns; menu-list composition lives in `ActionMenu` |
2424
| ProgressBar | 1 | `src/components/ui/index.ts` named export only | Shared bounded linear progress shell with optional ARIA progress semantics |
2525
| SegmentedControl | 1 | `src/components/ui/index.ts` named export only | Shared compact selector used by provider chooser/subnav and shortcuts categories |
2626
| Select | 2 | `src/components/ui/index.ts` named export only | Current bounded slice now completes the scoped selector family for supervisor and terminal mobile-sheet trigger flows |

packages/web/src/features/workspace/views/shared/file-tree-panel.test.tsx

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,49 @@ describe("FileTreePanel", () => {
708708
</Provider>
709709
);
710710

711-
expect(screen.getByText("file.empty_directory")).toBeInTheDocument();
711+
const emptyDirectoryState = screen.getByText("file.empty_directory");
712+
713+
expect(emptyDirectoryState.tagName).toBe("P");
714+
expect(emptyDirectoryState.closest(".tree-empty-hint")).toBeTruthy();
715+
});
716+
717+
it("uses translated loading copy for expanded folders while children are still loading", async () => {
718+
const sendCommand = vi.fn().mockImplementation(
719+
() =>
720+
new Promise(() => {
721+
// Keep the directory request pending so the inline loading state remains visible.
722+
})
723+
);
724+
const store = createStore();
725+
store.set(wsClientAtom, { sendCommand } as never);
726+
store.set(
727+
fileTreeAtomFamily("ws-test"),
728+
new Map([
729+
[
730+
".",
731+
[
732+
{
733+
path: "docs",
734+
name: "docs",
735+
kind: "dir",
736+
},
737+
],
738+
],
739+
])
740+
);
741+
742+
render(
743+
<Provider store={store}>
744+
<FileTreePanel workspaceId="ws-test" />
745+
</Provider>
746+
);
747+
748+
fireEvent.click(screen.getByText("docs"));
749+
750+
const loadingState = await screen.findByText("common.loading");
751+
752+
expect(loadingState.tagName).toBe("P");
753+
expect(loadingState.closest(".tree-loading")).toBeTruthy();
712754
});
713755

714756
it("filters loaded files by fuzzy filename search", async () => {

packages/web/src/features/workspace/views/shared/file-tree-panel.tsx

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,22 @@ const fileTreeEmptyStateTitleStyle = {
5252
fontWeight: "var(--font-normal)",
5353
};
5454

55+
const fileTreeInlineStateStyle = {
56+
display: "block",
57+
width: "auto",
58+
minHeight: "auto",
59+
padding: 0,
60+
gap: 0,
61+
textAlign: "left" as const,
62+
};
63+
64+
const fileTreeInlineStateTitleStyle = {
65+
margin: 0,
66+
color: "inherit",
67+
fontSize: "inherit",
68+
fontWeight: "inherit",
69+
};
70+
5571
function FileTreeEmptyState({ title }: { title: string }) {
5672
return (
5773
<EmptyState
@@ -62,6 +78,22 @@ function FileTreeEmptyState({ title }: { title: string }) {
6278
);
6379
}
6480

81+
function FileTreeInlineState({
82+
className,
83+
title,
84+
}: {
85+
className: "tree-empty-hint" | "tree-loading";
86+
title: string;
87+
}) {
88+
return (
89+
<EmptyState
90+
className={className}
91+
style={fileTreeInlineStateStyle}
92+
title={<p style={fileTreeInlineStateTitleStyle}>{title}</p>}
93+
/>
94+
);
95+
}
96+
6597
interface FileTreePanelProps {
6698
workspaceId: string;
6799
refreshToken?: number;
@@ -444,13 +476,13 @@ const FileTreeNode: FC<FileTreeNodeProps> = ({
444476
/>
445477
))}
446478
{node.children.length === 0 && !isLoadingDir && (
447-
<div className="tree-empty-hint">{t("file.empty_directory")}</div>
479+
<FileTreeInlineState className="tree-empty-hint" title={t("file.empty_directory")} />
448480
)}
449481
</div>
450482
)}
451483

452484
{isFolder && isExpanded && !node.children && isLoadingDir === node.path && (
453-
<div className="tree-loading">{t("common.loading")}</div>
485+
<FileTreeInlineState className="tree-loading" title={t("common.loading")} />
454486
)}
455487
</>
456488
);

packages/web/src/shells/desktop-shell.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ describe("DesktopShell auth gating", () => {
7777
renderShell(store);
7878

7979
expect(screen.getByText("正在连接工作区...")).toBeInTheDocument();
80+
expect(document.querySelector(".app-loading-shell")).toBeTruthy();
81+
expect(screen.getByText("CODER STUDIO").closest(".app-loading-card")).toBeTruthy();
8082
expect(screen.queryByText("LoginPage")).not.toBeInTheDocument();
8183
});
8284

0 commit comments

Comments
 (0)