Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
107 changes: 107 additions & 0 deletions .claude/sessions/2026-04-06.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,110 @@ Full plan at `.claude/plans/architecture-refactor.md`. Key points:
- Existing `ConversationHistory.spec.ts` tests become the spec for `Conversation`
- Rewrite tests to follow convention, then migrate implementation
- `AgentRun` receives `Conversation`; `AnthropicAgent` creates both


---

## Session continuation 3 (same day, later still)

This continuation picks up from compact. The previous session had just planned step 1a as the next move. We worked through steps 1a → 3a over several PRs, with one architecture plan revision along the way.

---

### 1. Step 1a — `Conversation` / `ConversationStore` split (PR #183)

Already completed before context compacted, but recorded here for continuity. Split `packages/claude-sdk/src/private/ConversationHistory.ts` into:
- `Conversation.ts` — pure data: stores `HistoryItem[]`, merge logic, compaction, no file I/O
- `ConversationStore.ts` — I/O wrapper: reads/writes `history.jsonl`, holds a `Conversation` instance

`AgentRun` receives a `Conversation`; `AnthropicAgent` creates both `ConversationStore` and extracts the inner `Conversation` to pass down. Tests migrated to `Conversation.spec.ts` following the one-expect-per-it convention.

---

### 2. Five banana pillars restoration (PR #185)

Philosophy document `.claude/five-banana-pillars.md` was lost at some point after commit `b67b1f6`. Content recovered from git history and restored. No code changes.

---

### 3. Step 1b — Replay conversation history into TUI on startup (PR #186)

New file: `apps/claude-sdk-cli/src/replayHistory.ts` — `replayHistory(messages, emit)` converts `BetaMessageParam[]` into the `StreamEvent` shapes that `AppLayout` already handles, replaying them into the conversation view so prior turns are visible when the CLI starts.

`runAgent.ts` calls `replayHistory` after constructing the agent and before entering the input loop. The history is read via `agent.getHistory()`.

Effect: starting the CLI mid-conversation now shows the previous exchange in the TUI rather than a blank screen.

---

### 4. Step 2 — `RequestBuilder` extraction from `AgentRun` (PR #187)

New file: `packages/claude-sdk/src/private/RequestBuilder.ts` — pure function `buildRequest(conversation, options)` that assembles the Anthropic API request body from current history and run options. No I/O, no side effects.

`AgentRun` now delegates request construction to `RequestBuilder`. The split makes request logic independently testable and separates "what to send" from "how to run the loop".

---

### 5. Architecture plan revision — MVVM without data bindings (PR #188)

The original plan used a component-based approach. After reading the code more carefully, this was revised to a cleaner MVVM model with three explicit layers:

- **State** — pure data and transitions (`EditorState`, `ConversationState`, etc.). No rendering, no I/O.
- **Renderer** — pure functions `(state, cols) → string[]`. Know column width but not row position. Testable with plain string assertions.
- **ScreenCoordinator** — owns the physical screen. Handles events (keypress, stream data, resize). After each event: updates relevant state, calls all renderers, allocates rows, assembles and writes output. Single trigger for re-renders.

Key properties this design gives us:
- Reflow lands cleanly: state untouched on resize, renderers re-called with new `cols`, coordinator re-allocates rows
- State machines testable without any ANSI knowledge
- Renderers testable with plain string assertions
- Cross-component layout constraints (tool widget expands → conversation shrinks) live in coordinator — one place
- Pull-based and explicit: no data bindings, no reactive subscriptions

The distinction between Renderer and Coordinator: Renderer knows *what* is on screen (content, colours, wrapping) and takes `cols`. Coordinator knows *where* it goes (row position, cursor placement) and takes both `cols` and `rows`.

Plan file updated at `.claude/plans/architecture-refactor.md`.

---

### 6. Step 3a — `EditorState` extracted from `AppLayout` (PR #189, pending merge)

New file: `apps/claude-sdk-cli/src/EditorState.ts`

```typescript
export class EditorState {
#lines: string[] = [''];
#cursorLine = 0;
#cursorCol = 0;

public get lines(): string[] // live internal array — intentionally mutable until 3b
public get cursorLine(): number
public set cursorLine(n: number)
public get cursorCol(): number
public set cursorCol(n: number)
public get text(): string // lines.join('\n')
public reset(): void // lines=[''], cursor at 0,0
}
```

`AppLayout` changes:
- `#editorLines`, `#cursorLine`, `#cursorCol` fields removed
- `#editorState = new EditorState()` added
- Two reset sites (`completeTurn`, `waitForInput`) replaced with `this.#editorState.reset()`
- All field accesses updated to go through `#editorState`

Key handling and rendering still live in `AppLayout` — behaviour is identical. The `lines` getter intentionally returns the live internal array because `AppLayout` still mutates it via index assignment and `splice`. This is documented as temporary until step 3b.

BananaBot approved. PR #189 open, auto-merge set to squash.

---

### State at compact

- Branch: `feature/editor-state`, one commit ahead of main (`c020b84`)
- PR #189 open, approved, awaiting merge
- **Next: step 3b** — move key handling into `EditorState.handleKey(key: KeyAction)`
- `handleKey` returns `boolean` (true = handled, false = AppLayout handles it)
- `#wordStartLeft` / `#wordEndRight` helpers move into `EditorState`
- `ctrl+enter` stays in `AppLayout` (involves attachments and `editorResolve`, not pure state)
- AppLayout key dispatch becomes: delegate to `handleKey`, schedule render, then handle `ctrl+enter` separately
- Tests: pure state machine → valuable unit tests for backspace, enter, ctrl+left, ctrl+backspace, delete, up/down clamping
Loading
Loading