Skip to content

Commit c58c538

Browse files
committed
refactor: extend shared empty state adoption
1 parent b634a94 commit c58c538

14 files changed

Lines changed: 193 additions & 29 deletions

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; richer confirm/auth flows remain on raw `Modal` | 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 |
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 workspace desktop/mobile no-session or no-workspace empties, welcome/not-found page shells, agent-panes no-workspace, topbar no-workspace, workspace route resolving/load-failed shells, worktree detail no-diff and empty-tree states, worktree manager list-empty, workspace launch directory-empty, 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 empty shell, and the mobile supervisor enable shell covered; additional list-style and other feature-owned empty blocks remain | 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 |
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; broader custom-content dropdown families remain | 2026-05-09 |
28-
| ActionMenu | 🟡 partial | new | mobile workspace topbar more-actions menu covered; broader desktop/topbar/command action families remain | 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 |
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 a bounded desktop custom-content dropdown batch: 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*`. The mobile fullscreen terminal switcher and mobile branch quick pick remain intentionally on their existing `Select` + `MobileSelectSheet` and global `MobileSelectSheet` paths, while menu-list composition now moves into the new shared `ActionMenu` wrapper.
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.
4545

46-
`ActionMenu` now covers a bounded menu-list slice: 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.
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.
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. The row remains partial because richer auth and action-confirmation flows still live on direct `Modal` composition by design.
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.
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: config editor, terminal panel, git diff/code editor/image preview empties, the desktop workspace no-workspace shell, the welcome and not-found page shells, the agent-panes no-workspace shell, the topbar no-workspace hint, the worktree detail no-diff and empty-tree states, the worktree manager list-empty state, the workspace launch directory-empty state, 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 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-status-panel`, `workspace-resolving-shell`, `workspace-resolving-card`, `agent-panes-empty`, `topbar-empty-state`, `mobile-select-sheet__empty`, `mobile-supervisor-sheet__empty`, `worktree-empty`, `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 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.
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: 3 additions & 3 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 slice covers the mobile workspace topbar more-actions menu |
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 |
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 used by bounded destructive flows |
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` |
1616
| EmptyState | 1 | `src/components/ui/index.ts` named export only | Shared centered empty-state shell with `title / description / icon / action` slots |
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 slice covers desktop anchored custom-content dropdown families; menu-list composition now lives in `ActionMenu` |
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` |
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/auth/index.test.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe("LoginPage", () => {
2828
it("renders the shared card layout while auth status is loading", async () => {
2929
globalThis.fetch = vi.fn(() => new Promise(() => {})) as typeof fetch;
3030

31-
render(
31+
const { container } = render(
3232
<Provider>
3333
<LoginPage />
3434
</Provider>
@@ -38,6 +38,8 @@ describe("LoginPage", () => {
3838
expect(document.querySelector(".welcome-card")).toBeTruthy();
3939
expect(document.querySelector(".auth-form")).toBeTruthy();
4040
expect(document.querySelector(".auth-status-panel")).toBeTruthy();
41+
expect(container.querySelector(".auth-card-shell > .auth-status-panel")).toBeTruthy();
42+
expect(container.querySelector(".auth-card-shell > .auth-form")).toBeTruthy();
4143
expect(screen.getByRole("button")).toBeDisabled();
4244
expect(screen.getAllByText("连接中").length).toBeGreaterThan(0);
4345
});

packages/web/src/features/auth/index.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,18 @@ import { useAtom, useAtomValue } from "jotai";
22
import { useEffect, useState } from "react";
33
import { authenticatedAtom, localeAtom } from "../../atoms/app-ui";
44
import { authEnabledAtom } from "../../atoms/connection";
5-
import { Button, Input } from "../../components/ui";
5+
import { Button, EmptyState, Input } from "../../components/ui";
66
import { useViewport } from "../../hooks/use-viewport";
77
import { formatDate, useTranslation } from "../../lib/i18n";
88

9+
const authEmptyStateStyle = {
10+
minHeight: "auto",
11+
padding: 0,
12+
gap: "var(--sp-5)",
13+
alignItems: "stretch",
14+
textAlign: "left" as const,
15+
};
16+
917
export function LoginPage() {
1018
const t = useTranslation();
1119
const [, setAuthenticated] = useAtom(authenticatedAtom);
@@ -143,9 +151,16 @@ export function LoginPage() {
143151
.filter(Boolean)
144152
.join(" ")}
145153
>
146-
<div className="welcome-kicker">CODER STUDIO</div>
147-
<h1 className="welcome-title">{t("app.name")}</h1>
148-
<p className="welcome-body auth-card-desc">{description}</p>
154+
<EmptyState
155+
style={authEmptyStateStyle}
156+
title={
157+
<div>
158+
<div className="welcome-kicker">CODER STUDIO</div>
159+
<h1 className="welcome-title">{t("app.name")}</h1>
160+
</div>
161+
}
162+
description={<p className="welcome-body auth-card-desc">{description}</p>}
163+
/>
149164
<div className={statusPanelClassName}>
150165
<div className="auth-status-eyebrow">{t("auth.status_title")}</div>
151166
<p className="auth-status-detail">{error ?? statusDetail}</p>

packages/web/src/features/mobile-select/components/mobile-select-sheet.test.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ describe("MobileSelectSheet", () => {
588588
expect(onCreate).not.toHaveBeenCalled();
589589
});
590590

591-
it("renders loading state separately from the empty state", () => {
591+
it("renders the loading state through the shared primitive while preserving the feature shell hook", () => {
592592
renderWithEnglishLocale(
593593
<MobileSelectSheet
594594
title="Branch"
@@ -599,7 +599,12 @@ describe("MobileSelectSheet", () => {
599599
/>
600600
);
601601

602+
const loadingState = document.querySelector(".mobile-select-sheet__loading");
603+
602604
expect(screen.getByRole("status")).toHaveTextContent("Loading...");
603-
expect(screen.queryByText("No results found")).not.toBeInTheDocument();
605+
expect(loadingState).not.toBeNull();
606+
expect(loadingState).toHaveAttribute("role", "status");
607+
expect(loadingState).toHaveTextContent("Loading...");
608+
expect(document.querySelector(".mobile-select-sheet__empty")).toBeNull();
604609
});
605610
});

0 commit comments

Comments
 (0)