Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
chore: specs
  • Loading branch information
adamdotdevin committed Feb 6, 2026
commit 3db4155639a169c8a322442000fe0d1d2ba6b55d
104 changes: 104 additions & 0 deletions specs/09-session-page-hot-paths.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
## Session hot paths

Reduce render work and duplication in `session.tsx`

---

### Summary

`packages/app/src/pages/session.tsx` mixes routing, commands, tab rendering, review panel wiring, terminal focus logic, and message scrolling. This spec targets hot-path performance + local code quality improvements that can ship together in one session-page-focused PR.

---

### Goals

- Render heavy file-tab content only for the active tab
- Deduplicate review-panel wiring used in desktop and mobile paths
- Centralize terminal-focus DOM logic into one helper
- Reduce churn in command registration setup

---

### Non-goals

- Scroll-spy rewrite (covered by `specs/04-scroll-spy-optimization.md`)
- Large routing/layout redesign
- Behavior changes to prompt submission or session history

---

### Parallel execution contract

This spec owns:

- `packages/app/src/pages/session.tsx`
- New files under `packages/app/src/pages/session/*` (if extracted)

This spec should not modify:

- `packages/app/src/context/*`
- `packages/app/src/components/prompt-input.tsx`
- `packages/app/src/components/file-tree.tsx`

---

### Implementation plan

1. Add shared helpers for repeated session-page actions

- Extract `openReviewFile(path)` helper to replace repeated inline `onViewFile` bodies.
- Extract `focusTerminalById(id)` helper and reuse in both:
- terminal active change effect
- terminal drag-end focus restoration

2. Deduplicate review panel construction

- Build a shared review props factory (or local render helper) so desktop/mobile paths do not duplicate comment wiring, `onViewFile`, and classes glue.
- Keep per-surface differences limited to layout classes and diff style.

3. Gate heavy file-tab rendering by active tab

- Keep tab trigger list rendered for all opened tabs.
- Render `Tabs.Content` body only for `activeTab()`, plus lightweight placeholders as needed.
- Ensure per-tab scroll state restore still works when reactivating a tab.

4. Reduce command registry reallocation

- 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.

---

### Acceptance criteria

- File tab bodies are not all mounted at once for large open-tab sets.
- `onViewFile` review behavior is defined in one shared helper.
- Terminal focus query/dispatch logic lives in one function and is reused.
- `command.register` no longer contains one monolithic inline array with repeated inline handlers for shared actions.
- Session UX remains unchanged for:
- opening files from review
- drag-reordering terminal tabs
- keyboard command execution

---

### Validation plan

- Manual:
- Open 12+ file tabs, switch quickly, verify active tab restore and no blank states.
- Open review panel (desktop and mobile), use "view file" from diffs, verify same behavior as before.
- Drag terminal tab, ensure terminal input focus is restored.
- Run key commands: `mod+p`, `mod+w`, `mod+shift+r`, `ctrl+``.
- Perf sanity:
- Compare CPU usage while switching tabs with many opened files before/after.

---

### Risks and mitigations

- Risk: unmounted tab content loses transient editor state.
- Mitigation: keep persisted scroll/selection restore path intact and verify reactivation behavior.
- Risk: command refactor subtly changes command ordering.
- Mitigation: keep IDs and registration order stable, diff against current command list in dev.
99 changes: 99 additions & 0 deletions specs/10-file-content-eviction-accounting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
## File cache accounting

Make file-content eviction bookkeeping O(1)

---

### Summary

`packages/app/src/context/file.tsx` currently recomputes total cached bytes by reducing the entire LRU map inside the eviction loop. This creates avoidable overhead on large file sets. We will switch to incremental byte accounting while keeping LRU behavior unchanged.

---

### Goals

- Remove repeated full-map reductions from eviction path
- Maintain accurate total byte tracking incrementally
- Preserve existing eviction semantics (entry count + byte cap)

---

### Non-goals

- Changing cache limits
- Changing file loading API behavior
- Introducing cross-session shared caches

---

### Parallel execution contract

This spec owns:

- `packages/app/src/context/file.tsx`
- Optional tests in `packages/app/src/context/*file*.test.ts`

This spec should not modify:

- `packages/app/src/pages/session.tsx`
- `packages/app/src/components/file-tree.tsx`

---

### Implementation plan

1. Introduce incremental byte counters

- Add module-level `contentBytesTotal`.
- Add helper(s):
- `setContentBytes(path, nextBytes)`
- `removeContentBytes(path)`
- `resetContentBytes()`

2. Refactor LRU touch/update path

- Keep `contentLru` as LRU order map.
- Update byte total only when a path is inserted/updated/removed.
- Ensure replacing existing byte value updates total correctly.

3. Refactor eviction loop

- Use `contentBytesTotal` in loop condition instead of `Array.from(...).reduce(...)`.
- On eviction, remove from both `contentLru` and byte counter.

4. Keep scope reset correct

- On directory scope change, clear inflight maps + `contentLru` + byte counter.

---

### Acceptance criteria

- `evictContent` performs no full-map reduction per iteration.
- Total bytes remain accurate after:
- loading file A
- loading file B
- force-reloading file A with a different size
- evicting entries
- scope reset
- Existing caps (`MAX_FILE_CONTENT_ENTRIES`, `MAX_FILE_CONTENT_BYTES`) continue to enforce correctly.

---

### Validation plan

- Manual:
- Open many files with mixed sizes and verify old files still evict as before.
- Switch directory scope and verify cache clears safely.
- Optional unit coverage:
- size counter updates on overwrite + delete.
- eviction condition uses count and bytes as expected.

---

### Risks and mitigations

- Risk: byte counter drifts from map contents.
- Mitigation: route all updates through centralized helpers.
- Risk: stale bytes retained on early returns.
- Mitigation: assert cleanup paths in `finally`/scope reset still execute.
92 changes: 92 additions & 0 deletions specs/11-layout-view-tabs-reactivity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
## Layout reactivity

Reduce per-call reactive overhead in `useLayout`

---

### Summary

`packages/app/src/context/layout.tsx` creates reactive effects inside `view(sessionKey)` and `tabs(sessionKey)` each time these helpers are called. Multiple consumers for the same key can accumulate duplicate watchers. This spec simplifies the API internals so calls stay lightweight while preserving behavior.

---

### Goals

- Remove avoidable per-call `createEffect` allocations in `view()` and `tabs()`
- Preserve scroll seeding, pruning, and touch semantics
- Keep external `useLayout` API stable

---

### Non-goals

- Persistence schema migration
- Session tab behavior redesign
- New layout features

---

### Parallel execution contract

This spec owns:

- `packages/app/src/context/layout.tsx`
- `packages/app/src/context/layout-scroll.test.ts` (if updates needed)

This spec should not modify:

- `packages/app/src/pages/session.tsx`
- `packages/app/src/components/session/*`

---

### Implementation plan

1. Consolidate key-touch logic

- Introduce shared internal helper, e.g. `ensureSessionKey(key)` that performs:
- `touch(key)`
- `scroll.seed(key)`

2. Remove per-call effects in `view()` / `tabs()`

- Replace internal `createEffect(on(key, ...))` usage with lazy key reads inside accessors/memos.
- Ensure reads still invoke `ensureSessionKey` at safe points.

3. Keep return API stable

- Preserve current method names and behavior:
- `view(...).scroll`, `setScroll`, `terminal`, `reviewPanel`, `review`
- `tabs(...).active`, `all`, `open`, `close`, `move`, etc.

4. Verify pruning behavior

- Ensure session-key pruning still runs when key set grows and active key changes.

---

### Acceptance criteria

- `view()` and `tabs()` no longer instantiate per-call key-change effects.
- Existing callers do not require API changes.
- Scroll restore and tab persistence still work across session navigation.
- No regressions in handoff/pending-message behavior.

---

### Validation plan

- Manual:
- Navigate across multiple sessions; verify tabs + review open state + scroll positions restore.
- Toggle terminal/review panels and confirm persisted state remains consistent.
- Tests:
- Update/add targeted tests for key seeding/pruning if behavior changed.

---

### Risks and mitigations

- Risk: subtle key-touch ordering changes affect prune timing.
- Mitigation: keep `touch` and `seed` coupled through one helper and verify prune boundaries.
- Risk: removing effects misses updates for dynamic accessor keys.
- Mitigation: ensure every public accessor path reads current key and calls helper.
96 changes: 96 additions & 0 deletions specs/12-session-context-metrics-shared.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
## Context metrics shared

Unify duplicate session usage calculations

---

### Summary

`session-context-tab.tsx` and `session-context-usage.tsx` both compute overlapping session metrics (cost, last assistant token totals, provider/model context usage). This creates duplicate loops and raises drift risk. We will centralize shared calculations in one helper module and have both components consume it.

---

### Goals

- Compute shared session usage metrics in one place
- Remove duplicate loops for cost and latest-token context usage
- Keep UI output unchanged in both components

---

### Non-goals

- Rewriting the detailed context breakdown estimator logic
- Changing translations or labels
- Moving metrics into backend API responses

---

### Parallel execution contract

This spec owns:

- `packages/app/src/components/session/session-context-tab.tsx`
- `packages/app/src/components/session-context-usage.tsx`
- New helper in `packages/app/src/components/session/*` or `packages/app/src/utils/*`

This spec should not modify:

- `packages/app/src/pages/session.tsx`
- `packages/app/src/context/sync.tsx`

---

### Implementation plan

1. Add shared metrics helper

- Create helper for raw metrics from message list + provider map, e.g.:
- `totalCost`
- `lastAssistantWithTokens`
- `tokenTotal`
- `tokenUsagePercent`
- provider/model labels
- Return raw numeric values; keep locale formatting in consumers.

2. Add memoization guard

- Use reference-based memoization (e.g. by message-array identity) inside helper or component-level memo to avoid duplicate recalculation on unchanged arrays.

3. Migrate both components

- Replace duplicated loops in:
- `session-context-tab.tsx`
- `session-context-usage.tsx`
- Keep existing UI structure and i18n keys unchanged.

---

### Acceptance criteria

- Shared cost + token calculations are defined in one module.
- Both components read from the shared helper.
- Rendered values remain identical for:
- total cost
- token totals
- usage percentage
- provider/model fallback labels

---

### Validation plan

- Manual:
- Open session context tab and compare values with header/context indicator tooltip.
- Verify values update correctly while new assistant messages stream in.
- Regression:
- locale change still formats numbers/currency correctly.

---

### Risks and mitigations

- Risk: helper changes semantic edge cases (no provider, no model, missing token fields).
- Mitigation: preserve existing fallback behavior (`"—"`, null percent).
- Risk: memoization over-caches stale values.
- Mitigation: key cache by message-array reference and dependent IDs only.
Loading