# Sparse Mode Fixes — Multi-Model Jury Findings

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Fix critical bugs and edge cases found by 4-model jury (Gemini, GPT, Qwen, background Gemini) in sparse render mode.

**Architecture:** Three targeted fixes to existing functions — no new files, no architectural changes. Truncation order swap in server.ts, input guards in stripMarkdown, cleanup of renderSummaryBadge.

**Tech Stack:** TypeScript, raw ANSI escape codes

---

### Task 1: Truncate Raw Content BEFORE Rendering (Critical)

**Why:** All 4 models flagged this. `truncateSmart` runs after `renderOutput`, which adds ANSI escape codes. If truncation lands mid-escape (`\x1b[1`), it corrupts terminal output and bleeds colors. Truncating the raw text first, then applying ANSI badges, guarantees clean output.

**Files:**
- Modify: `src/server.ts:262-272`
- Test: Manual — build and verify with long content

- [ ] **Step 1: Swap truncation order in server.ts**

Change from:
```typescript
const model = inferModelFromTool(tool.name) || undefined;
let rendered = renderOutput(result, { model, summary: tool.name });
const MAX_RESPONSE_CHARS = 25000;
if (rendered.length > MAX_RESPONSE_CHARS) {
  rendered = truncateSmart(rendered, MAX_RESPONSE_CHARS);
}
return { type: "text" as const, text: rendered };
```

To:
```typescript
const model = inferModelFromTool(tool.name) || undefined;
// Truncate raw content BEFORE adding ANSI — prevents mid-escape corruption
let raw = result;
const MAX_RAW_CHARS = 24000;
if (raw.length > MAX_RAW_CHARS) {
  raw = truncateSmart(raw, MAX_RAW_CHARS);
}
const rendered = renderOutput(raw, { model, summary: tool.name });
return { type: "text" as const, text: rendered };
```

Note: Use 24K (not 25K) to leave ~1K headroom for badge ANSI codes.

- [ ] **Step 2: Build and verify**

Run: `npm run build`

- [ ] **Step 3: Commit**

```bash
git add src/server.ts
git commit -m "fix: truncate raw content before ANSI rendering to prevent escape corruption"
```

---

### Task 2: Show Summary Badge Even Without Model

**Why:** GPT flagged that tools returning `null` from `inferModelFromTool` (e.g., `think`, `focus`, `nextThought`) lose their summary badge entirely. The tool name should still appear.

**Files:**
- Modify: `src/utils/ansi-renderer.ts:240-253` (sparse case)

- [ ] **Step 1: Update sparse case to show summary independently**

Change from:
```typescript
case 'sparse': {
  if (options.model) {
    const badge = renderModelBadge(options.model);
    if (options.summary) {
      output = badge + renderSummaryBadge(options.summary, options.model) + '\n';
    } else {
      output = badge + '\n';
    }
  }
  output += stripMarkdown(content, { boldHeaders: true });
  break;
}
```

To:
```typescript
case 'sparse': {
  const parts: string[] = [];
  if (options.model) {
    parts.push(renderModelBadge(options.model));
  }
  if (options.summary) {
    parts.push(renderSummaryBadge(options.summary));
  }
  if (parts.length) output += parts.join('') + '\n';
  output += stripMarkdown(content, { boldHeaders: true });
  break;
}
```

- [ ] **Step 2: Remove unused `model` param from renderSummaryBadge**

In `src/utils/ansi-styles.ts`, change:
```typescript
export function renderSummaryBadge(summary: string, model?: string): string {
```
To:
```typescript
export function renderSummaryBadge(summary: string): string {
```

- [ ] **Step 3: Update import in ansi-renderer.ts if needed**

Verify the call site no longer passes `model` to `renderSummaryBadge`.

- [ ] **Step 4: Build and test**

Run: `npm run build && node -e "
import { renderOutput } from './dist/src/utils/ansi-renderer.js';
// Test: no model, has summary
console.log(renderOutput('Hello', { summary: 'think', mode: 'sparse' }));
// Test: model + summary
console.log(renderOutput('Hello', { model: 'grok', summary: 'grok_search', mode: 'sparse' }));
// Test: no model, no summary
console.log(renderOutput('Hello', { mode: 'sparse' }));
"`

- [ ] **Step 5: Commit**

```bash
git add src/utils/ansi-renderer.ts src/utils/ansi-styles.ts
git commit -m "fix(sparse): show summary badge even when model is null"
```

---

### Task 3: Add Input Guards to stripMarkdown

**Why:** GPT and Qwen flagged that empty/null input and missing early return can cause unnecessary regex processing.

**Files:**
- Modify: `src/utils/ansi-renderer.ts:557` (stripMarkdown function body)
- Modify: `src/utils/__tests__/strip-markdown.test.ts` (add empty input test)

- [ ] **Step 1: Add test for empty input**

Add to the test file:
```typescript
it('returns empty string for empty/null input', () => {
  expect(stripMarkdown('')).toBe('');
  expect(stripMarkdown('   ')).toBe('');
});
```

- [ ] **Step 2: Add early return to stripMarkdown**

Add at the start of the function body (after destructuring options):
```typescript
if (!md || !md.trim()) return '';
```

- [ ] **Step 3: Run tests**

Run: `npx vitest run src/utils/__tests__/strip-markdown.test.ts`

- [ ] **Step 4: Commit**

```bash
git add src/utils/ansi-renderer.ts src/utils/__tests__/strip-markdown.test.ts
git commit -m "fix: add empty input guard to stripMarkdown"
```

---

### Summary of What We're NOT Fixing

| Finding | Decision | Reason |
|---------|----------|--------|
| Replace regex with markdown parser | Skip | Overkill — model output is ~95% simple markdown |
| `@@CODE` placeholder collision | Skip | Probability near zero in real model output |
| Code block with nested backticks | Skip | Models rarely output nested fences |
| Link with `)` in URL | Skip | Models rarely output such URLs |
| Windows `\r\n` line endings | Skip | MCP runs on Unix; models output `\n` |
| ANSI enters LLM context | Accept | 1M context window; ~1.2x overhead acceptable |
| Decouple UI from LLM payload | Skip | MCP architecture doesn't support split rendering |
