chore: refactoring and tests, splitting up files (#12495)

This commit is contained in:
Adam
2026-02-06 10:02:31 -06:00
committed by GitHub
parent a4bc883595
commit 2c58dd6203
117 changed files with 9457 additions and 5827 deletions

View File

@@ -0,0 +1,113 @@
## Session page decomposition
Split `pages/session.tsx` into focused modules without behavior changes.
---
### Summary
`packages/app/src/pages/session.tsx` is still a large (~3,655 LOC) route coordinator. Recent refactoring already extracted `packages/app/src/pages/session/helpers.ts` and `packages/app/src/pages/session/scroll-spy.ts`, but review-panel wiring, message timeline orchestration, file-tab rendering, and terminal coordination remain tightly coupled. This spec continues the decomposition from that updated baseline.
---
### Goals
- Reduce complexity in `packages/app/src/pages/session.tsx`.
- Isolate major concerns into dedicated modules under `packages/app/src/pages/session/`.
- Keep behavior and route/API contracts unchanged.
- Preserve current keyboard, scroll, hash, and review interactions.
---
### Non-goals
- No redesign of session UX.
- No changes to SDK contracts.
- No refactor of `context/global-sync.tsx`, `context/file.tsx`, or `components/prompt-input.tsx` in this workstream.
---
### Parallel ownership (important)
This workstream owns:
- `packages/app/src/pages/session.tsx`
- New files under `packages/app/src/pages/session/**`
This workstream must not edit:
- `packages/app/src/pages/layout.tsx` (owned by spec 10)
- `packages/app/src/components/prompt-input.tsx` (owned by spec 11)
- `packages/app/src/context/global-sync.tsx` (owned by spec 12)
- `packages/app/src/context/file.tsx` (owned by spec 13)
---
### Current state
- File size: ~3,655 LOC.
- Existing extracted modules:
- `packages/app/src/pages/session/helpers.ts` (terminal focus and shared handlers)
- `packages/app/src/pages/session/scroll-spy.ts` (message visibility + active-section tracking)
- High effect density (`createEffect`) and local-state density (`createStore` + `createSignal`) remain in `session.tsx`.
- Remaining interleaved responsibilities:
- review panel state + scrolling integration
- message timeline + hash navigation wiring
- file tab renderers + per-tab scroll sync
- terminal panel and tab coordination
---
### Proposed module split
Build on the existing `packages/app/src/pages/session/` directory and keep current extracted helpers in place. Add modules such as:
- `review-panel.tsx` - review tab rendering and focused diff logic.
- `message-timeline.tsx` - session turn rendering and active message tracking UI wiring.
- `file-tabs.tsx` - file tab content rendering, file scroll persistence, and line-comment overlays.
- `terminal-panel.tsx` - terminal tabs and focus behavior.
- `use-session-page-state.ts` - page-level derived state and imperative handlers.
`packages/app/src/pages/session.tsx` remains the route entry and orchestrator only.
---
### Phased steps
1. Keep `helpers.ts` and `scroll-spy.ts` as baseline; extract any additional pure helpers first (no behavior changes).
2. Extract review panel subtree and related handlers.
3. Extract file-tab subtree and scroll synchronization logic.
4. Extract terminal panel subtree.
5. Move page-level state/effects into `use-session-page-state.ts`.
6. Reduce `session.tsx` to composition and routing glue.
---
### Acceptance criteria
- `packages/app/src/pages/session.tsx` is reduced substantially (target: under 1,400 LOC).
- No user-facing behavior changes in session, review, file tabs, or terminal tabs.
- Event listeners and observers are still correctly cleaned up.
- New modules have clear prop boundaries and minimal hidden coupling.
---
### Validation plan
- Typecheck: `bun run typecheck` (from `packages/app`).
- Targeted e2e checks:
- `e2e/session/session.spec.ts`
- `e2e/files/file-viewer.spec.ts`
- `e2e/terminal/terminal.spec.ts`
- Manual checks:
- message hash navigation
- review diff focus + open-file action
- terminal tab create/reorder/focus behavior
---
### Handoff notes
- Keep module interfaces narrow and data-oriented.
- Prefer extracting code unchanged before doing any cleanup refactors.
- If a helper is useful to other specs, place it under `pages/session/` for now; cross-spec shared utilities can be unified later.

View File

@@ -68,7 +68,7 @@ This spec should not modify:
- Move large command-array construction into smaller memoized blocks:
- stable command definitions
- dynamic state fields (`disabled`, titles) as narrow computed closures
- Keep command IDs, keybinds, and behavior identical.
- Keep command IDs, keybinds, and behavior identical.
---

View File

@@ -0,0 +1,109 @@
## Layout page decomposition
Split `pages/layout.tsx` into composable layout modules with stable behavior.
---
### Summary
`packages/app/src/pages/layout.tsx` is a 3,000+ line coordinator for sidebar navigation, project/workspace controls, deep-link handling, dialogs, drag/drop overlays, and global shell interactions. This spec decomposes it into focused modules to improve maintainability and reduce merge risk for future features.
---
### Goals
- Break up `packages/app/src/pages/layout.tsx` into smaller units.
- Separate rendering concerns from orchestration/state concerns.
- Keep existing URL/navigation semantics and sidebar behavior.
- Preserve all current command and dialog entry points.
---
### Non-goals
- No major UX redesign of the sidebar or project/workspace UI.
- No changes to server/global-sync contracts.
- No refactor of `pages/session.tsx` in this workstream.
---
### Parallel ownership (important)
This workstream owns:
- `packages/app/src/pages/layout.tsx`
- New files under `packages/app/src/pages/layout/**`
This workstream must not edit:
- `packages/app/src/pages/session.tsx` (spec 09)
- `packages/app/src/components/prompt-input.tsx` (spec 11)
- `packages/app/src/context/global-sync.tsx` (spec 12)
---
### Current state
- File size: ~3,004 LOC.
- Contains mixed concerns:
- app-shell rendering
- sidebar/project/workspace UI + drag/drop
- deep-link handling and startup flows
- workspace reset/delete actions and toasts
---
### Proposed module split
Create `packages/app/src/pages/layout/` modules such as:
- `use-layout-page-state.ts` - orchestration state and handlers.
- `sidebar-panel.tsx` - sidebar shell and root interactions.
- `project-item.tsx` - project-level row and actions.
- `workspace-item.tsx` - workspace row, sessions list, and workspace actions.
- `deep-links.ts` - deep-link parsing/draining/handler utilities.
Keep `packages/app/src/pages/layout.tsx` as route-level composition and provider wiring.
---
### Phased steps
1. Extract pure helpers first (deep-link parse, shared label helpers, small utility functions).
2. Extract workspace subtree and action handlers.
3. Extract project subtree and menu actions.
4. Extract sidebar shell and drag overlay components.
5. Move orchestration logic into `use-layout-page-state.ts`.
6. Reduce `layout.tsx` to composition-only entry.
---
### Acceptance criteria
- `packages/app/src/pages/layout.tsx` is significantly smaller (target: under 1,200 LOC).
- Behavior parity for:
- project open/close/rename
- workspace expand/collapse/reset/delete
- deep-link handling
- drag/drop ordering
- No regressions in keyboard navigation and dialog actions.
---
### Validation plan
- Typecheck: `bun run typecheck` (from `packages/app`).
- Targeted e2e checks:
- `e2e/sidebar/sidebar.spec.ts`
- `e2e/projects/workspaces.spec.ts`
- `e2e/projects/project-edit.spec.ts`
- `e2e/app/navigation.spec.ts`
- Manual check: deep-link open-project flow still opens and navigates correctly.
---
### Handoff notes
- Keep action handlers close to their domain module.
- Do not merge in behavior cleanups during extraction; preserve semantics first.
- If shared components are needed, add them under `pages/layout/` for now to avoid cross-spec conflicts.

View File

@@ -0,0 +1,121 @@
## Prompt input and optimistic-state consolidation
Decompose prompt-input and unify optimistic message mutations.
---
### Summary
`packages/app/src/components/prompt-input.tsx` has already been partially decomposed and is now ~1,391 LOC. Editor DOM helpers, attachments, history, and submit flow were extracted into `packages/app/src/components/prompt-input/*.ts`, but optimistic mutation ownership and some UI/controller responsibilities are still split across call sites. This spec continues from that refactored baseline.
---
### Goals
- Split `prompt-input.tsx` into modular UI + controller pieces.
- Centralize optimistic message add/remove behavior behind sync-context APIs.
- Remove unsafe cast path around optimistic parts (`as unknown as Part[]`).
- Keep existing prompt UX and submission semantics unchanged.
---
### Non-goals
- No redesign of prompt input visuals.
- No changes to session protocol or backend APIs.
- No changes to unrelated page modules (`pages/session.tsx`, `pages/layout.tsx`).
---
### Parallel ownership (important)
This workstream owns:
- `packages/app/src/components/prompt-input.tsx`
- New files under `packages/app/src/components/prompt-input/**`
- `packages/app/src/context/sync.tsx` (optimistic API surface only)
This workstream must not edit:
- `packages/app/src/pages/session.tsx` (spec 09)
- `packages/app/src/pages/layout.tsx` (spec 10)
- `packages/app/src/context/global-sync.tsx` (spec 12)
- `packages/app/src/context/file.tsx` (spec 13)
---
### Current state
- File size: ~1,391 LOC for `prompt-input.tsx`.
- Existing extracted modules:
- `prompt-input/editor-dom.ts`
- `prompt-input/attachments.ts`
- `prompt-input/history.ts`
- `prompt-input/submit.ts`
- Optimistic mutation and request-part casting still need consolidation (including remaining `as unknown as Part[]` in submit path).
- Remaining concerns still tightly coupled in `prompt-input.tsx`:
- slash/mention UI rendering and keyboard orchestration
- context pill interactions and focus behavior
- composition glue across history/attachments/submit
---
### Proposed structure
Build on the existing `packages/app/src/components/prompt-input/` modules by adding/further splitting modules such as:
- `use-prompt-composer.ts` - state machine for submit/abort/history.
- `build-request-parts.ts` - typed request-part construction.
- `slash-popover.tsx` - slash command list rendering.
- `context-items.tsx` - context pills and interactions.
Keep existing lower-level modules (`attachments.ts`, `editor-dom.ts`, `history.ts`, `submit.ts`) and narrow their responsibilities where needed.
Add sync-level optimistic APIs (in `context/sync.tsx` or `context/sync-optimistic.ts`):
- `session.optimistic.add(...)`
- `session.optimistic.remove(...)`
Prompt input should call these APIs instead of directly mutating message/part stores.
---
### Phased steps
1. Extract typed request-part builder (likely from `prompt-input/submit.ts`) to remove ad hoc casting.
2. Introduce sync optimistic APIs with current behavior.
3. Replace remaining direct `produce(...)` optimistic mutations with optimistic APIs.
4. Extract remaining UI subtrees (slash popover, context items, toolbar controls).
5. Extract controller hook and keep route component as composition shell.
---
### Acceptance criteria
- Optimistic update logic exists in one place only.
- `prompt-input.tsx` is significantly smaller (target: under 1,200 LOC).
- Prompt submit/abort/history behavior remains unchanged.
- No `as unknown as Part[]` in optimistic request construction path.
---
### Validation plan
- Typecheck: `bun run typecheck` (from `packages/app`).
- Targeted e2e checks:
- `e2e/prompt/prompt.spec.ts`
- `e2e/prompt/context.spec.ts`
- `e2e/prompt/prompt-slash-open.spec.ts`
- `e2e/prompt/prompt-mention.spec.ts`
- Manual check:
- submit with file/image/context attachments
- abort in-flight turn
- history up/down restore behavior
---
### Handoff notes
- Preserve sequence semantics around optimistic insert, worktree wait, send, and rollback.
- Keep sync optimistic API data-oriented and reusable by future callers.
- Do not mix this with broader sync/global-sync refactors in the same diff.

View File

@@ -0,0 +1,105 @@
## Global sync domain split
Refactor `context/global-sync.tsx` into domain modules while preserving behavior.
---
### Summary
`packages/app/src/context/global-sync.tsx` is a large multi-domain module (1,000+ LOC) that currently owns queue scheduling, bootstrap, child store creation, persistence bridges, session trimming, and event reduction. This workstream splits it into clear domains without changing runtime behavior.
---
### Goals
- Decompose global sync internals into maintainable modules.
- Keep `useGlobalSync()` public API unchanged.
- Isolate pure logic (session trimming, ordering, grouping) from side effects.
- Keep event handling deterministic and easier to test.
---
### Non-goals
- No protocol/API changes to server events.
- No behavior changes in session ordering, trimming, or cache semantics.
- No changes to page-level UI logic.
---
### Parallel ownership (important)
This workstream owns:
- `packages/app/src/context/global-sync.tsx`
- New files under `packages/app/src/context/global-sync/**`
This workstream must not edit:
- `packages/app/src/context/file.tsx` (spec 13)
- `packages/app/src/components/prompt-input.tsx` (spec 11)
- `packages/app/src/pages/session.tsx` and `packages/app/src/pages/layout.tsx` (specs 09/10)
---
### Current state
- Single large module with many responsibilities.
- Event reducer is embedded in component lifecycle code.
- Queue/scheduler, bootstrap, and child-store lifecycle are tightly interwoven.
---
### Proposed module split
Create `packages/app/src/context/global-sync/` modules like:
- `types.ts` - shared types.
- `queue.ts` - refresh queue and drain scheduler.
- `child-store.ts` - child store creation, persistence wiring, cache maps.
- `session-trim.ts` - pure session sorting/trimming helpers.
- `bootstrap.ts` - global and per-directory bootstrap flows.
- `event-reducer.ts` - event handlers for SDK event stream.
Keep `global-sync.tsx` as provider/composition entry point.
---
### Phased steps
1. Extract pure helpers (`cmp`, session trim/recent logic) first.
2. Extract queue/drain scheduler.
3. Extract child-store creation and persisted cache wiring.
4. Extract bootstrap flows.
5. Extract event reducer and wire into existing listener.
6. Keep API surface stable and documented.
---
### Acceptance criteria
- Public API of `useGlobalSync()` remains backward compatible.
- `global-sync.tsx` is substantially reduced (target: under 500 LOC).
- Event handling logic is isolated and easier to trace.
- No behavior regressions in project/session/provider sync.
---
### Validation plan
- Typecheck: `bun run typecheck` (from `packages/app`).
- Targeted e2e checks:
- `e2e/app/session.spec.ts`
- `e2e/sidebar/sidebar-session-links.spec.ts`
- `e2e/projects/projects-switch.spec.ts`
- Manual checks:
- switching directories/projects still hydrates child stores correctly
- session list/pagination behavior remains stable
---
### Handoff notes
- Favor function extraction with unchanged code first.
- Keep event handler ordering explicit; avoid implicit fallthrough behaviors.
- Add focused tests only for extracted pure helpers if practical, but avoid broad test-suite changes here.

View File

@@ -0,0 +1,111 @@
## File context domain split
Refactor `context/file.tsx` into focused modules with unchanged API.
---
### Summary
`packages/app/src/context/file.tsx` still combines path normalization, file-content caching/eviction, file-tree loading, watcher event handling, and file-view persistence orchestration. Recent refactoring extracted generic scoped-cache primitives to `packages/app/src/utils/scoped-cache.ts`, but most file-domain behavior remains in one module. This spec separates those concerns while preserving the existing `useFile()` interface.
---
### Goals
- Keep `useFile()` API stable for all callers.
- Extract independent domains into dedicated modules.
- Improve readability and lower risk for future file-tree/perf changes.
- Preserve current caching and watcher semantics.
---
### Non-goals
- No redesign of file tree UI.
- No change to backend file APIs.
- No simultaneous refactor of `components/file-tree.tsx` in this workstream.
---
### Parallel ownership (important)
This workstream owns:
- `packages/app/src/context/file.tsx`
- New files under `packages/app/src/context/file/**`
- `packages/app/src/utils/scoped-cache.ts` (only when required for file-view cache extraction)
This workstream must not edit:
- `packages/app/src/context/global-sync.tsx` (spec 12)
- `packages/app/src/pages/session.tsx` (spec 09)
- `packages/app/src/components/prompt-input.tsx` (spec 11)
---
### Current state
- File size: ~751 LOC.
- `packages/app/src/utils/scoped-cache.ts` now exists as a shared cache primitive used by file view persistence.
- Multiple domains in one module:
- path normalization/parsing
- LRU content memory management
- tree node/directory state management
- event-driven watcher invalidation
- per-session view cache bootstrapping
---
### Proposed module split
Create `packages/app/src/context/file/` modules such as:
- `path.ts` - normalize/strip helpers.
- `content-cache.ts` - content LRU + byte caps.
- `view-cache.ts` - per-session file view persistence cache (building on `createScopedCache`).
- `tree-store.ts` - directory/node store and list/expand/collapse actions.
- `watcher.ts` - watcher event handling and invalidation routines.
`file.tsx` remains the provider entry that composes these modules.
---
### Phased steps
1. Extract path helper functions with no behavior changes.
2. Extract content cache and eviction logic.
3. Extract file-specific view-cache loading/pruning logic on top of `createScopedCache`.
4. Extract tree-store list/refresh/toggle actions.
5. Extract watcher update handler and wire cleanup.
6. Keep `useFile()` return shape unchanged.
---
### Acceptance criteria
- `useFile()` API remains backward compatible.
- `context/file.tsx` is reduced significantly (target: under 350 LOC).
- Tree loading/refresh and content eviction behavior remain unchanged.
- Watcher-driven reload behavior still works for changed/added/deleted files.
---
### Validation plan
- Typecheck: `bun run typecheck` (from `packages/app`).
- Targeted e2e checks:
- `e2e/files/file-tree.spec.ts`
- `e2e/files/file-viewer.spec.ts`
- `e2e/files/file-open.spec.ts`
- Manual checks:
- directory expand/collapse and refresh
- large file navigation and cache reuse
- watcher-driven updates in active file tabs
---
### Handoff notes
- Keep tree/data stores colocated with their mutation helpers.
- Avoid changing persisted key names or cache key shapes in this pass.
- Save broader API cleanups for a follow-up once modules are stable.

View File

@@ -0,0 +1,108 @@
## Server health and row dedupe
Unify server health checks and deduplicate server-row UI logic.
---
### Summary
Server health logic is duplicated across multiple files, and server row rendering/truncation logic is repeated in both the status popover and server dialog. This creates drift risk and inconsistent behavior. This spec centralizes health checks and row rendering while preserving existing UX.
---
### Goals
- Introduce one shared server-health checker.
- Use consistent timeout and error semantics in all server health call sites.
- Deduplicate repeated server row truncation/tooltip behavior.
- Keep current polling interval and status semantics unless explicitly changed.
---
### Non-goals
- No redesign of the status popover or server dialog.
- No changes to server persistence model.
- No broad refactor of unrelated status tabs (MCP/LSP/plugins).
---
### Parallel ownership (important)
This workstream owns:
- `packages/app/src/components/dialog-select-server.tsx`
- `packages/app/src/components/status-popover.tsx`
- `packages/app/src/context/server.tsx`
- New files under `packages/app/src/components/server/**` and/or `packages/app/src/utils/server-health.ts`
This workstream must not edit:
- `packages/app/src/components/terminal.tsx` (spec 15)
- `packages/app/src/pages/session.tsx` and `packages/app/src/pages/layout.tsx` (specs 09/10)
---
### Current state
- Duplicate `checkHealth` implementation in:
- `components/dialog-select-server.tsx`
- `components/status-popover.tsx`
- Similar health check logic in `context/server.tsx`.
- Duplicate row truncation + resize listener logic in status and dialog server lists.
---
### Proposed approach
1. Add shared health utility:
- `checkServerHealth(url, fetch, opts)`
- one timeout strategy
- one return shape: `{ healthy: boolean, version?: string }`
2. Add shared server row primitive:
- common rendering for status dot, truncated name/version handling, tooltip content
- optional action slots for per-screen controls
3. Adopt utility and row primitive in both consumers.
---
### Phased steps
1. Create `utils/server-health.ts` and migrate all health call sites.
2. Create shared row component (`components/server/server-row.tsx`).
3. Replace duplicated row logic in server dialog and status popover.
4. Confirm polling and active/default server behavior still match existing UX.
---
### Acceptance criteria
- Exactly one app-level server health check implementation remains.
- Server row truncation/tooltip behavior is shared, not duplicated.
- No regressions when switching active/default server.
- Existing status dot semantics are preserved.
---
### Validation plan
- Typecheck: `bun run typecheck` (from `packages/app`).
- Targeted e2e checks:
- `e2e/status/status-popover.spec.ts`
- `e2e/app/server-default.spec.ts`
- Manual checks:
- add/edit/remove server
- blocked unhealthy server behavior
- default server toggles and persistence
---
### Handoff notes
- Keep shared server row API minimal and composable.
- Avoid introducing new global state for this refactor.
- Prefer deterministic helper behavior over UI-specific branching inside the utility.

View File

@@ -0,0 +1,106 @@
## Runtime adapter type safety
Reduce unsafe casts at browser and third-party integration boundaries.
---
### Summary
Several integration points rely on `as any` or `unknown as` casts (terminal internals, speech recognition, add-on internals, generic trigger props). This spec introduces typed adapters and narrow interfaces to improve maintainability and make type errors actionable.
---
### Goals
- Remove or significantly reduce unsafe casts in scoped files.
- Introduce explicit adapter interfaces around unstable third-party APIs.
- Preserve behavior with no UX changes.
- Improve maintainability of terminal and speech integrations.
---
### Non-goals
- No server health dedupe work (owned by spec 14).
- No large architectural changes to terminal or speech subsystems.
- No changes to business logic semantics.
---
### Parallel ownership (important)
This workstream owns:
- `packages/app/src/components/terminal.tsx`
- `packages/app/src/utils/speech.ts`
- `packages/app/src/addons/serialize.ts`
- `packages/app/src/components/dialog-select-model.tsx`
- New utility files under `packages/app/src/utils/**` related to adapter typing
This workstream must not edit:
- `components/dialog-select-server.tsx`, `components/status-popover.tsx`, `context/server.tsx` (spec 14)
- `components/prompt-input.tsx` (spec 11)
---
### Current state
- Explicit `as any` appears in `serialize.ts` and `speech.ts`.
- Multiple `unknown as` casts in `terminal.tsx` for option/disposable access.
- Generic trigger props in `dialog-select-model.tsx` use `as any` spread.
---
### Proposed approach
1. Add narrow adapter types for third-party internals:
- terminal option setter/disposable handles
- speech recognition constructor on `window`
- serialize addon internal terminal buffer access
2. Introduce tiny helper guards/utilities:
- `isDisposable(value): value is { dispose(): void }`
- `hasSetOption(value): value is { setOption(...): void }`
3. Replace broad casts with adapter functions and runtime checks.
---
### Phased steps
1. Refactor terminal helpers (`setOption`, disposal cleanups) to typed guards.
2. Refactor speech recognition window access to typed constructor lookup.
3. Replace `serialize.ts` `as any` internals with explicit local interface.
4. Remove `dialog-select-model.tsx` `as any` trigger props cast via stricter generic typing.
---
### Acceptance criteria
- No `as any` remains in the scoped files (or document unavoidable cases inline).
- `unknown as` usage in scoped files is minimized and justified.
- Typecheck passes with no new suppression comments.
- Runtime behavior remains unchanged.
---
### Validation plan
- Typecheck: `bun run typecheck` (from `packages/app`).
- Targeted e2e checks:
- `e2e/terminal/terminal.spec.ts`
- `e2e/models/model-picker.spec.ts`
- Manual checks:
- terminal open/connect/resize/cleanup
- speech start/stop and interim/final behavior
---
### Handoff notes
- Prefer small typed wrapper functions over inline complex narrowing.
- Keep adapter names explicit and local to their integration point.
- If a cast cannot be removed safely, add a short comment describing why.

View File

@@ -0,0 +1,107 @@
## i18n hardening and parity
Strengthen locale correctness and remove remaining hardcoded copy.
---
### Summary
The app has broad translation coverage but still has maintainability gaps: locale dictionaries are typed as `Partial`, some non-English dictionaries contain English values for specific keys, and a few user-facing strings are still hardcoded in components/pages. This spec hardens i18n guarantees and cleans up remaining drift.
---
### Goals
- Enforce stricter dictionary key parity across all app locales.
- Remove known English fallback strings from non-English locale files.
- Localize remaining hardcoded user-facing strings in scoped files.
- Keep existing localization architecture (`useLanguage().t(...)`) intact.
---
### Non-goals
- No translation quality rewrite for all strings.
- No locale expansion beyond existing languages.
- No changes to non-user-facing log/diagnostic strings.
---
### Parallel ownership (important)
This workstream owns:
- `packages/app/src/context/language.tsx`
- `packages/app/src/i18n/*.ts`
- `packages/app/src/components/dialog-custom-provider.tsx`
- `packages/app/src/pages/directory-layout.tsx`
This workstream must not edit:
- `pages/session.tsx`, `pages/layout.tsx`, `components/prompt-input.tsx`
- server/terminal integration files owned by specs 14/15
---
### Current state
- Locale files are large and manually maintained.
- Non-English locales are typed with `Partial<Record<Keys, string>>`, which allows silent missing keys.
- Known untranslated strings exist for keys like:
- `command.session.previous.unseen`
- `command.session.next.unseen`
- Some user-facing strings remain hardcoded in scoped files.
---
### Proposed approach
1. Tighten locale typing:
- Move from `Partial<Record<Keys, string>>` to stricter parity enforcement.
- Keep `en.ts` as source-of-truth key set.
2. Fix known untranslated key values in non-English dictionaries.
3. Localize scoped hardcoded strings by adding translation keys and using `language.t(...)`.
---
### Phased steps
1. Add/adjust shared locale typing pattern for parity safety.
2. Update all locale files to satisfy stricter typing.
3. Translate known English carry-over keys in non-English dictionaries.
4. Replace hardcoded copy in:
- `components/dialog-custom-provider.tsx`
- `pages/directory-layout.tsx`
5. Run typecheck and parity checks.
---
### Acceptance criteria
- Locale files enforce full key parity against `en` (compile-time).
- No known English carry-over values remain for the targeted keys in non-English locales.
- Scoped hardcoded user-facing strings are replaced with translation keys.
- Typecheck passes.
---
### Validation plan
- Typecheck: `bun run typecheck` (from `packages/app`).
- Grep sanity checks:
- targeted keys no longer English in non-English locales
- scoped files no longer contain hardcoded user-facing copy
- Manual spot checks in at least 2 locales (for example: `de`, `zh`).
---
### Handoff notes
- Keep key naming consistent with existing conventions.
- Avoid broad copy changes outside scoped files to reduce review surface.
- If translation wording is uncertain, keep it simple and literal for now; quality passes can follow.

View File

@@ -0,0 +1,101 @@
## Unit test foundation
Establish reliable unit coverage for core app logic.
---
### Summary
`packages/app` is still e2e-first, but recent refactoring added a first wave of active source-unit tests (session helpers/scroll spy, prompt-input modules, file-tree, comments/layout/terminal/file context, and scoped-cache). This spec focuses on turning that momentum into a stable, explicit unit-test baseline in CI/local and unblocking the remaining skipped legacy suites.
---
### Goals
- Add a clear unit-test command for app source tests.
- Unskip and stabilize existing skipped unit tests.
- Add fast tests for high-value pure logic.
- Keep unit suite independent of full e2e environment.
---
### Non-goals
- No replacement of e2e tests.
- No broad product-code refactors unless required to make logic testable.
- No flaky browser-automation tests added here.
---
### Parallel ownership (important)
This workstream owns:
- `packages/app/package.json` (test scripts only)
- `packages/app/happydom.ts` (if harness tweaks are needed)
- `packages/app/src/**/*.test.ts`
- `packages/app/src/**/*.test.tsx`
This workstream should avoid editing product code files owned by other specs, unless a tiny testability export is strictly required.
---
### Current state
- Active unit coverage now exists across several `src/**/*.test.*` files (including context, pages/session, components/prompt-input, and utils).
- Remaining skipped legacy suites:
- `src/context/layout-scroll.test.ts` (`test.skip`)
- `src/addons/serialize.test.ts` (`describe.skip`)
- `package.json` scripts still focus on Playwright e2e and do not expose a dedicated `test:unit` entrypoint.
---
### Proposed approach
1. Add dedicated unit-test script(s), for example:
- `test:unit` using Bun test + happydom preload where needed.
2. Unskip and stabilize remaining skipped legacy tests:
- make `layout-scroll.test.ts` deterministic
- enable a reliable subset of `serialize.test.ts` (or split smoke vs heavy integration cases)
3. Add/expand fast unit tests for high-value pure logic not yet covered:
- keybind parsing/formatting/matching (`context/command.tsx` exports)
- worktree state machine (`utils/worktree.ts`)
---
### Phased steps
1. Wire `test:unit` in `package.json`.
2. Make existing skipped tests runnable and stable.
3. Add at least 2 new unit test files for core pure logic.
4. Ensure unit suite can run standalone without Playwright server setup.
---
### Acceptance criteria
- `bun run test:unit` exists and passes locally.
- No full-file `describe.skip`/`test.skip` remains in `packages/app/src/**/*.test.*` (unless documented as intentionally quarantined with reason).
- Unit suite includes meaningful assertions for keybind + worktree logic.
- Runtime for unit suite remains fast (target: under 15 seconds locally, excluding first install).
---
### Validation plan
- Run: `bun run test:unit`.
- Run: `bun run typecheck`.
- Verify unit tests can execute without starting full app/backend servers.
---
### Handoff notes
- Keep tests implementation-focused, not duplicated business logic.
- Avoid mocks where practical; prefer real small-scope code paths.
- If integration-heavy serialize cases remain flaky, separate them into a clearly named non-default test target.

View File

@@ -0,0 +1,51 @@
## Parallel workstream map
Use this as the assignment sheet for running multiple agents at once.
---
### Workstreams
1. `specs/09-session-page-decomposition.md`
2. `specs/10-layout-page-decomposition.md`
3. `specs/11-prompt-input-and-optimistic-state.md`
4. `specs/12-global-sync-domain-split.md`
5. `specs/13-file-context-domain-split.md`
6. `specs/14-server-health-and-row-dedupe.md`
7. `specs/15-runtime-adapter-type-safety.md`
8. `specs/16-i18n-hardening-and-parity.md`
9. `specs/17-unit-test-foundation.md`
---
### File-ownership matrix
| Spec | Primary ownership | Avoid editing |
| ---- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| 09 | `pages/session.tsx`, `pages/session/**` | `pages/layout.tsx`, `components/prompt-input.tsx`, `context/global-sync.tsx`, `context/file.tsx` |
| 10 | `pages/layout.tsx`, `pages/layout/**` | `pages/session.tsx`, `components/prompt-input.tsx`, `context/global-sync.tsx` |
| 11 | `components/prompt-input.tsx`, `components/prompt-input/**`, `context/sync.tsx` (optimistic API only) | `pages/session.tsx`, `pages/layout.tsx`, `context/global-sync.tsx`, `context/file.tsx` |
| 12 | `context/global-sync.tsx`, `context/global-sync/**` | `context/file.tsx`, `components/prompt-input.tsx`, page files |
| 13 | `context/file.tsx`, `context/file/**`, `utils/scoped-cache.ts` (only when file-view cache extraction needs it) | `context/global-sync.tsx`, `components/prompt-input.tsx`, page files |
| 14 | `components/dialog-select-server.tsx`, `components/status-popover.tsx`, `context/server.tsx`, shared server utility/component | terminal/speech/serialize files |
| 15 | `components/terminal.tsx`, `utils/speech.ts`, `addons/serialize.ts`, `components/dialog-select-model.tsx`, adapter utilities | server status/dialog/context files |
| 16 | `context/language.tsx`, `i18n/*.ts`, `components/dialog-custom-provider.tsx`, `pages/directory-layout.tsx` | major page/context refactors |
| 17 | `package.json` (test scripts), `happydom.ts`, `src/**/*.test.*` | product code files in other specs unless strictly needed |
---
### Recommended execution order (if all start together)
- Start all 9 in parallel.
- Merge low-conflict streams first: 12, 13, 14, 15, 16, 17.
- Then merge 09, 10, 11 (largest diff sizes and highest rebase probability).
---
### Integration checkpoint
After all streams merge, run a full verification pass in `packages/app`:
- `bun run typecheck`
- `bun run test:unit` (from spec 17)
- targeted e2e smoke for session/layout/prompt/server/terminal flows