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
160 changes: 160 additions & 0 deletions ai_plans/2026-06-17_zoo-153-chat-oom-large-transcript.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# Port plan — Zoo PR #153 → `feature/zoo-153-chat-oom-large-transcript`

> **For the executor (read first).** Do the steps **in order**. This port lands
> as a verified-clean `git apply` of the upstream diff, **followed by a small
> post-apply rebrand** of two user-facing strings in `ClineProvider.ts`. Do
> **not** improvise or hand-edit anything else. If any `git apply --check`
> reports a conflict, **STOP and report** — do not force it or hand-merge. This
> repo is **Tumble Code**: never leave the strings "Roo" or "Zoo" in user-facing
> text.

---

## 0. Context (read once, write no code)

- **Upstream:** Zoo PR #153 — "[Fix] Chat window runs out of memory when transcript grows large" (commit `ed868c675`).
- **What it does, one paragraph:** When a chat transcript grows very large, the
Virtuoso virtual list in [ChatView.tsx](webview-ui/src/components/chat/ChatView.tsx)
pre-renders an enormous viewport buffer (`increaseViewportBy={{ top: 3_000, bottom: 1000 }}`),
forcing thousands of pixels of off-screen message rows to mount at once. On long
tasks this balloons memory until the webview crashes / greys out. This PR shrinks
the pre-render buffer to `{ top: 600, bottom: 800 }`, gives Virtuoso a
`defaultItemHeight` (180) so it can estimate scroll extents without mounting
every row, and a stable `computeItemKey` (`${ts}-${index}`) so rows are recycled
instead of re-created. It also adds lightweight diagnostics: when the webview
becomes hidden during an active task, `ClineProvider` logs the task id, message
count, stack depth and a timestamp to the output channel — a breadcrumb for
debugging the "grey screen" report.
- **Why we want it, with evidence in OUR code:**
[ChatView.tsx:1665](webview-ui/src/components/chat/ChatView.tsx#L1665) still
carries the original `increaseViewportBy={{ top: 3_000, bottom: 1000 }}`, with
no `defaultItemHeight` and no `computeItemKey` — so our build over-renders the
same way and is subject to the same OOM on long transcripts.
- **What we deliberately leave out (YAGNI):** nothing structural — all 4 files
apply cleanly. The only adaptation is the **rebrand** in step 4: the upstream
diagnostics string is Zoo-branded (`[Zoo Code]`, `support@zoocode.dev`) and must
be made neutral / Tumble.
- **Original authors — credit them.** (commit author is `roomote[bot]`, dropped;
`T <taltas@…>` deduped into `Toray Altas` — same email). Commit trailers:

```text
Co-authored-by: Toray Altas <6816042+taltas@users.noreply.github.com>
Co-authored-by: edelauna <54631123+edelauna@users.noreply.github.com>
Co-authored-by: Elliott de Launay <edelauna@gmail.com>
```

## 1. Preconditions — verify before touching anything

- [ ] Current branch is `feature/zoo-153-chat-oom-large-transcript`, created off `main`.
- [ ] Working tree clean (`git status --short` empty).
- [ ] The Zoo clone exists at `/home/krzych/Projekty/QUB-IT/Zoo-Code`.
- [ ] These 4 files exist: `webview-ui/src/components/chat/ChatView.tsx`,
`webview-ui/src/components/chat/__tests__/ChatView.spec.tsx`,
`src/core/webview/ClineProvider.ts`,
`src/core/webview/__tests__/ClineProvider.spec.ts`.

## 2. Regenerate the diff and confirm it applies (no code yet)

```bash
cd /home/krzych/Projekty/QUB-IT/Roo-Code
git -C /home/krzych/Projekty/QUB-IT/Zoo-Code show ed868c675 > /tmp/zoo-153.diff
git apply --check /tmp/zoo-153.diff && echo "APPLIES CLEANLY"
```

- **Expect:** `APPLIES CLEANLY`. If `git apply --check` prints any error, **STOP
and report** — the before-state has drifted and this plan is stale.

## 3. Apply the diff

```bash
cd /home/krzych/Projekty/QUB-IT/Roo-Code
git apply /tmp/zoo-153.diff
git status --short
```

- **Expect exactly these 4 files changed:**
`webview-ui/src/components/chat/ChatView.tsx`,
`webview-ui/src/components/chat/__tests__/ChatView.spec.tsx`,
`src/core/webview/ClineProvider.ts`,
`src/core/webview/__tests__/ClineProvider.spec.ts`.

## 4. Rebrand the two Zoo strings (MANDATORY — Tumble Code, no Zoo)

The applied `logWebviewHiddenDiagnostics` method in
[ClineProvider.ts](src/core/webview/ClineProvider.ts) contains Zoo branding. Make
exactly these two edits in that method, nothing else.

### Edit 1 — the log prefix

Replace:

```ts
this.log(
`[Zoo Code] Webview hidden during active task.\n` +
```

With:

```ts
this.log(
`[Tumble Code] Webview hidden during active task.\n` +
```

### Edit 2 — the support line (drop the Zoo support email)

Replace:

```ts
` timestamp: ${new Date().toISOString()}\n` +
`If the panel appears gray after this, share this log with support@zoocode.dev`,
```

With:

```ts
` timestamp: ${new Date().toISOString()}\n` +
`If the panel appears gray after this, include this log when reporting the issue.`,
```

- **Why this is safe:** the spec only asserts `expect.stringContaining("running-task")`
(the taskId) and `not.toHaveBeenCalled()` for the abort/abandoned cases — it
never references the branding or the support sentence. Confirmed in the §2 diff
of `ClineProvider.spec.ts`.

## 5. Out of scope — do NOT do these

- Do **not** hand-edit the Virtuoso tuning or "improve" the applied numbers.
- Do **not** re-add or re-wire: **TTS**, the **router / cloud provider**, **cloud
upsell** UI, or **Roo/Zoo branding**.
- Do **not** rename internal ids (those stay `Roo-Code`).
- Do **not** leave `Zoo`, `zoocode`, or `support@zoocode.dev` anywhere.

## 6. Verify — paste real output, don't claim success without it

```bash
cd /home/krzych/Projekty/QUB-IT/Roo-Code/src && npx vitest run core/webview/__tests__/ClineProvider.spec.ts
cd /home/krzych/Projekty/QUB-IT/Roo-Code/webview-ui && npx vitest run src/components/chat/__tests__/ChatView.spec.tsx
cd /home/krzych/Projekty/QUB-IT/Roo-Code/src && npx tsc --noEmit -p . 2>&1 | tail -5 # or: pnpm check-types
grep -rIn "Zoo\|zoocode" webview-ui/src/components/chat/ChatView.tsx src/core/webview/ClineProvider.ts || echo "NO ZOO STRINGS"
```

## 7. Acceptance criteria (binary — all must hold)

- [ ] `ClineProvider.spec.ts` (incl. the new `logWebviewHiddenDiagnostics` describe) green.
- [ ] `ChatView.spec.tsx` green.
- [ ] `check-types` clean.
- [ ] `git status` shows exactly the 4 expected files.
- [ ] `grep` confirms **no** `Zoo` / `zoocode` strings remain in the two production files.
- [ ] `ChatView.tsx` shows `increaseViewportBy={CHAT_VIEWPORT_BUFFER}` (not the old `3_000/1000`),
`defaultItemHeight={CHAT_DEFAULT_ITEM_HEIGHT}`, and `computeItemKey={computeMessageKey}`.

## 8. Record in the ledger

Already recorded by the orchestrator after the plan file is written. The commit
(done by the orchestrator) will carry:

```text
Co-authored-by: Toray Altas <6816042+taltas@users.noreply.github.com>
Co-authored-by: edelauna <54631123+edelauna@users.noreply.github.com>
Co-authored-by: Elliott de Launay <edelauna@gmail.com>
```
172 changes: 172 additions & 0 deletions ai_plans/2026-06-17_zoo-281-ripgrep-diagnostic-command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Port plan — Zoo PR #281 → `feature/zoo-281-ripgrep-diagnostic-command`

## §0 Context & credit

- **Upstream:** Zoo-Code PR #281 `feat(ripgrep): add Show Ripgrep Diagnostic
command` (squashed commit `d29520b5c`).
- **Authors (credit on commit):**
- `Co-authored-by: 0xMink <260166390+0xMink@users.noreply.github.com>`
- `Co-authored-by: edelauna <54631123+edelauna@users.noreply.github.com>`
- `Co-authored-by: Elliott de Launay <edelauna@gmail.com>`
- **Canonical source diff:** `git -C ../Zoo-Code show d29520b5c` (28 files).

## §1 What it does

Adds a user-triggerable VS Code command (`showRipgrepDiagnostic`) that runs the
same hybrid ripgrep-resolution logic our search path uses and writes a verbose
report to a dedicated output channel + the clipboard. The report covers three
steps:

1. `require("@vscode/ripgrep")` via a testable `loadRipgrep()` wrapper (surfaces
the `.asar → .asar.unpacked` rewrite and whether the file exists).
2. A path probe of every known `appRoot`-relative candidate path, marking each
`✓`/`✗`.
3. A `rg --version` spawn (5 s timeout) on the path `getBinPath()` selects —
directly catching the "file exists but spawn fails" failure mode.

Motivated by the #248 ripgrep-universal layout bug (already ported to our fork):
debugging it required a custom instrumented VSIX. With this command shipped,
users hitting ripgrep-resolution weirdness can paste the diagnostic into a bug
report instead.

The PR refactors `getBinPath` to share its candidate list with the diagnostic via
a new exported `ripgrepCandidatePaths(appRoot)` helper, and reintroduces the
`@vscode/ripgrep` esbuild `external` entry (so the `require()` resolves at runtime
against VS Code's own `node_modules` rather than being bundled).

## §2 Fork adaptations (the judgment calls)

This PR is Zoo-branded; our fork is **Tumble Code**. Decisions made:

1. **Command id — routed through `getCommand()`, never hardcoded.** Upstream
registers `zoo-code.showRipgrepDiagnostic`. Our `getCommand(id)` in
`src/utils/commands.ts` produces `` `${Package.name}.${id}` `` →
`tumble-code.showRipgrepDiagnostic`. `diagnostic.ts` calls
`getCommand("showRipgrepDiagnostic")`. `"showRipgrepDiagnostic"` added to the
`CommandId` union in `packages/types/src/vscode.ts` (matching the existing
blank-line-separated style, appended after `toggleAutoApprove`).

2. **`package.json` command contribution** uses literal command id
`tumble-code.showRipgrepDiagnostic` (matching siblings like
`tumble-code.plusButtonClicked`) with `"title":
"%command.showRipgrepDiagnostic.title%"` and `"category":
"%configuration.title%"`, inserted between the `acceptInput` and
`toggleAutoApprove` entries (mirroring the NLS insert position).

3. **NLS title VALUE — brand-neutral.** Inspected our existing command titles in
`src/package.nls.json`: they are brand-neutral ("New Task", "Fix Code", "Accept
Input/Suggestion"), NOT prefixed with "Tumble Code:". So the English value is
**"Show Ripgrep Diagnostic"** (no brand prefix), matching upstream's English
value exactly (upstream's en value also had no brand prefix; only the
diagnostic report text and toast carry the brand). For the 17 non-English
locales I mirrored upstream's per-locale localized string verbatim — none of
upstream's title values contained "Zoo"/"Roo", so no brand-token swap was
needed in any locale title. Inserted between `command.acceptInput.title` and
`command.toggleAutoApprove.title` in every file.

4. **Brand tokens INSIDE `diagnostic.ts` (user-facing report/channel/toast)**
swapped Zoo→Tumble:

- OutputChannel name: `"Zoo Code Ripgrep Diagnostic"` → `"Tumble Code Ripgrep
Diagnostic"`.
- Report header line: `"Zoo Code Ripgrep Diagnostic (…)"` → `"Tumble Code
Ripgrep Diagnostic (…)"`.
- Toast: `"Zoo Code: ripgrep diagnostic copied to clipboard."` → `"Tumble
Code: ripgrep diagnostic copied to clipboard."`.
The test expectations were adapted to the Tumble strings accordingly.

5. **`@vscode/ripgrep` dependency — NOT re-added to `package.json`.** Verified it
is absent from BOTH our `src/package.json` AND upstream's post-#281
`src/package.json` (`@vscode/ripgrep` is a VS Code internal package resolved at
runtime, not an npm devDep in either tree). Only the two things genuinely
needed for the `require()` to compile+resolve were added:

- `src/esbuild.mjs`: `"@vscode/ripgrep"` appended to the `external` array.
- `knip.json`: `"@vscode/ripgrep"` added to `ignore` so knip doesn't flag the
unlisted dependency.
(Upstream's commit message says "reintroduces the devDep", but the actual diff
never touched `package.json` deps — only `knip.json` + `esbuild.mjs`. We match
the real diff, not the prose.)

6. **`registerCommands.ts` adaptation.** Our fork's pre-state used
`Record<CommandId, any>` and had no separate diagnostic registration. Applied
upstream's intent: added the import, the
`context.subscriptions.push(registerRipgrepDiagnosticCommand())` call, the
`CommandCallback` alias, and changed the map type to
`Record<Exclude<CommandId, "showRipgrepDiagnostic">, CommandCallback>` so the
diagnostic's separate registration owns the OutputChannel lifecycle.

## §3 How our `index.ts` differed from upstream pre-state

Our fork already ported #248 (the `@vscode/ripgrep-universal` layout). So our
pre-#281 `getBinPath` was a 6-branch `checkPath(...)||...` chain that ALREADY
included the two `ripgrep-universal` candidates — upstream's pre-#281 had only the
4 classic candidates plus its own universal addition. Both converge on the same
6-candidate ordered list. The #281 refactor (extract `ripgrepCandidatePaths()`,
loop in `getBinPath`) applied cleanly on top of our 6-candidate version: the
extracted helper lists exactly our 6 candidates in the same order, and `getBinPath`
became a `for` loop returning the first existing path. No behavioral change to
resolution order.

Confirmed `src/services/ripgrep/diagnostic.ts` and
`src/services/ripgrep/internal/loadRipgrep.ts` did NOT pre-exist (new files).

## §4 Out of scope — do NOT do these

- No TTS / router / cloud / Roo/Zoo branding reintroduced.
- No `package.json` dependency addition (§2.5).
- Internal ids stay (`@roo-code/types`, `Package.name` resolves to `tumble-code`).

## §5 Files changed (execution checklist)

### Types

- `packages/types/src/vscode.ts`: add `"showRipgrepDiagnostic"` to `commandIds`.

### New ripgrep modules

- `src/services/ripgrep/internal/loadRipgrep.ts` (NEW): `loadRipgrep()` require
wrapper + `LoadRipgrepResult` type.
- `src/services/ripgrep/diagnostic.ts` (NEW): `trySpawnRipgrep`,
`getRipgrepDiagnostic` (data fn), `registerRipgrepDiagnosticCommand` (cmd
wrapper) — Tumble-branded strings.
- `src/services/ripgrep/index.ts`: extract `ripgrepCandidatePaths()` (exported),
rewrite `getBinPath` as a loop over it.

### Wiring

- `src/activate/registerCommands.ts`: import + register the diagnostic command;
exclude `showRipgrepDiagnostic` from `getCommandsMap`; `CommandCallback` alias.
- `src/package.json`: `contributes.commands` entry for
`tumble-code.showRipgrepDiagnostic`.
- `src/package.nls.json` + 17 locale files: `command.showRipgrepDiagnostic.title`.
- `src/esbuild.mjs`: `@vscode/ripgrep` external.
- `knip.json`: `@vscode/ripgrep` ignore.

### Tests

- `src/services/ripgrep/__tests__/diagnostic.spec.ts` (NEW): 21 data-fn +
registration tests, expectations adapted to `tumble-code.showRipgrepDiagnostic`
and the Tumble channel/toast strings.
- `src/activate/__tests__/registerCommands.spec.ts`: mock the diagnostic module,
add `commands.registerCommand` to the vscode mock, add a `registerCommands`
describe verifying the disposable lands in `context.subscriptions`.

## §6 Verification (binary acceptance)

- `cd src && npx vitest run services/ripgrep/__tests__/diagnostic.spec.ts
activate/__tests__/registerCommands.spec.ts` → 25/25 GREEN.
- `pnpm --filter @roo-code/types check-types` → clean.
- `cd src && pnpm check-types` (`tsc --noEmit`) → clean.
- No new "Zoo"/"Roo" user-facing strings introduced.

## §7 Record in ledger

```bash
node .claude/skills/zoo-port/scripts/zoo-prs.mjs record \
--pr 281 --status ported \
--branch feature/zoo-281-ripgrep-diagnostic-command \
--plan ai_plans/2026-06-17_zoo-281-ripgrep-diagnostic-command.md
```

Commit (only if asked) with the three `Co-authored-by:` trailers from §0.
Loading
Loading