This document records the next Web UI refactor plan. The goal is to make the Web client maintainable before adding GitHub automation, persistent AI teammate management, plugin management, and a future desktop shell.
Current pressure points:
apps/web/src/App.tsxowns application state, page routing, feature components, API orchestration, UI helpers, and pure formatting helpers.apps/web/src/styles.cssowns global layout, feature styles, form controls, and component-specific rules.- More features will make this shape harder to test and harder to reuse in desktop.
This is a behavior-preserving refactor. It is not a redesign.
- Keep the current visual direction and behavior unchanged.
- Split the Web UI into feature modules with explicit props and local helpers.
- Keep daemon API, routes, request bodies, and response shapes unchanged.
- Keep existing Playwright selectors and visible labels stable.
- Create reusable surfaces for the future desktop shell.
- Avoid introducing a router, global state manager, or new dependency in this slice.
apps/web/src/
app/
AppShell.tsx
view-types.ts
workspace-state.ts
features/
navigation/
workspace/
teammates/
runs/
providers/
media/
approvals/
settings/
shared/
components/
constants/
icons/
lib/
approvals.ts
artifacts.ts
channels.ts
formatting.ts
local-storage.ts
providers.ts
run-evidence.ts
runtime-state.ts
styles/
base.css
layout.css
forms.css
navigation.css
workspace.css
teammates.css
runs.css
providers.css
media.css
approvals.css
settings.css
styles.css should become the stable import surface for ordered CSS imports. Feature
styles are split after component extraction is stable. styles.css remains the only
import used by the Vite entrypoint.
| Feature | Owns | Does not own |
|---|---|---|
navigation |
Sidebar, view buttons, channel shortcuts, pinned/hidden teammate rows | Run/task/provider mutations |
workspace |
Main workspace surface, channel/task composer, first-run flow, right context panel | Provider setup internals |
teammates |
AI teammate wizard, template filters, workflow preview, inspector | Persistent teammate DB schema |
runs |
Run history, run detail, timeline, artifacts, runtime audit, repair hints | Provider CRUD |
providers |
Model Workbench, provider form, template grouping, provider readiness helpers | Secret value access |
media |
Media job list/create/run, media artifact rows, media approval surfacing | Generic run artifact logic |
approvals |
Approval inbox, approval card, payload summary, repair panel | Approval policy settings |
settings |
Approval policy page | Runtime execution |
Shared components should stay small and generic: badges, meta rows, avatar initials, empty states, field rows, and status chips.
Keep App.tsx or AppShell.tsx as the coordinator during the first slice.
Rules:
- Feature components receive explicit props and callbacks.
- Cross-feature actions stay in the coordinator, for example opening Model Workbench from a failed run or opening Approvals from workspace readiness.
- Pure helpers move to
src/libfirst. - Feature-local transient state can live inside feature components when it is not shared.
- Do not introduce Redux, Zustand, React Router, or another state/routing library in this refactor.
- Move pure constants, formatters, provider readiness helpers, approval helpers, artifact
helpers, and local-storage helpers out of
App.tsx. - Extract shared stateless components such as
StatusBadge,MetaRow,AvatarInitial, and simple icon helpers. - Extract
navigation. - Extract feature pages in this order:
teammates,providers,approvals,media,runs,workspace,settings. - Split CSS after component imports are stable.
- Keep class names and aria labels stable unless each affected test is updated in the same change.
Current slice:
-
App.tsxis now a thin entry component; the coordinator lives inapp/AppShell.tsx. -
View copy is centralized in
app/view-types.ts. -
Local storage, channel policy conversion, provider readiness/conversion, approval helpers, artifact helpers, event-data helpers, run evidence helpers, and tool constants live under
lib/. -
Generic UI pieces
MetaRow,StatusBadge,AvatarInitial, andRunRepairHintBannerlive undershared/components/. -
Sidebar navigation lives under
features/navigation/. -
AI teammate presentation now lives under
features/teammates/; local draft state, default skill pack wiring, sidebar coordination, and pin/hide/delete callbacks stay inAppShell. -
Model Workbench presentation now lives under
features/providers/; provider CRUD, check, batch check, repair handoff, and rerun coordination stay inAppShell. -
Approval inbox presentation now lives under
features/approvals/; approval refresh, resolution, and repair-run routing stay inAppShell.ApprovalCardis exported for run detail and inline pending approval surfaces. -
Media Jobs presentation now lives under
features/media/; media job load/create/run coordination stays inAppShell. -
ArtifactRowlives undershared/components/because run evidence and media jobs both render artifact links. -
Settings approval policy presentation now lives under
features/settings/; approval policy load/save, workspace path, loading, and error coordination stay inAppShell. -
Runs presentation now lives under
features/runs/; run loading, selection, rerun, approval resolution, provider repair, and task repair routing stay inAppShell. -
Workspace presentation now lives under
features/workspace/; channel/task coordination, run orchestration, provider selection, and repair handoffs stay inAppShell. -
styles.cssis now an ordered import surface forstyles/base.css,forms.css,navigation.css,workspace.css,runs.css,media.css,providers.css,settings.css,approvals.css,layout.css,teammates.css, andcomponents.css. The split preserves the previous cascade order;forms.cssis intentionally reserved for later form-control refinement. -
Goal 2 is complete: feature presentation has moved out of
AppShell, and CSS has a stable modular entry structure.
Follow-up design:
- Web AppShell Domain Modularization defines
the next architecture slice: page-level lazy loading, domain-owned state controllers,
a typed
AppEventRouter, concurrency rules, refresh/cache policy, stream lifecycle, and migration exit criteria. That follow-up keeps this document's original no-new-global-store constraint while addressing the remainingAppShellgrowth and large initial Web chunk warning. - Web API Facade Modularization defines the next
source-ownership slice for
apps/web/src/api.ts: keep the compatibility barrel, extract request builders and form payload adapters, then move endpoint wrappers into domain-specific API modules.
- No daemon API changes.
- No visual redesign.
- No new state manager.
- No new routing library.
- No contracts package extraction in this slice.
- No desktop implementation.
apps/web/e2e/app.spec.ts is the Web end-to-end regression suite. It runs with
Playwright against the real Vite Web client, while daemon calls to
http://127.0.0.1:17891/** are mocked through the suite's route handler. Treat it as a
browser-level Web contract test, not as a full daemon process integration test.
Do not split the file only to make the tree look cleaner. The current suite intentionally
shares MockState, daemon route handling, fixture builders, and cross-feature flows.
Splitting tests before those support boundaries are stable would create churn and raise
the risk of breaking selectors, mock daemon behavior, or setup order.
Refactor the e2e structure incrementally when there is a real maintenance trigger:
- a new feature would add a large block of unrelated scenarios to
app.spec.ts, - merge conflicts in the file become frequent,
- mock daemon changes repeatedly affect unrelated domains,
- a domain can move cleanly with its helpers and fixtures, such as channel, workspace, runtime, model workbench, settings, external work, or media jobs.
When the trigger exists, split support code before splitting scenarios:
apps/web/e2e/
app.spec.ts
support/
mock-daemon.ts
mock-state.ts
test-helpers.ts
Only after the shared support layer is stable should domain specs move out of
app.spec.ts:
apps/web/e2e/channel.spec.ts
apps/web/e2e/workspace.spec.ts
apps/web/e2e/runtime.spec.ts
apps/web/e2e/model-workbench.spec.ts
apps/web/e2e/settings.spec.ts
apps/web/e2e/external-work.spec.ts
apps/web/e2e/media-jobs.spec.ts
Each split must preserve visible labels, aria labels, CSS selectors used by tests, and mock daemon response semantics unless the same change intentionally updates the affected tests and product behavior.
Run after each meaningful extraction:
corepack pnpm --filter @ling-stack/web typecheck
corepack pnpm --filter @ling-stack/web build
corepack pnpm --filter @ling-stack/web exec playwright test
git diff --checkRegression coverage must preserve:
- Workspace readiness and repair hints.
- Channel runtime policy creation/edit visibility.
- Paused channel disabled run actions.
- Run detail state-transition timeline.
- Language switch persistence.
- AI teammate wizard and sidebar pin/hide behavior.
- Workspace path persistence and daemon default fallback.
- Model Workbench readiness and repair flow.
- Task provider preflight filtering.
- Approval inbox pending/expired behavior.
- Approval policy settings load/save behavior.
- Run artifacts and media job artifacts.
- Media cost approval flow.
- This refactor follows the existing client-style UI direction.
- The first success metric is behavior-preserving modularity, not smaller files at any cost.
- Web remains the reference client for the future desktop shell.