|
| 1 | +# Thinking Component Truncation Design |
| 2 | + |
| 3 | +## Problem |
| 4 | + |
| 5 | +The `Thinking` component in `src/ui/Messages.tsx` displays all reasoning/thinking text from the LLM without any truncation. When the model produces long reasoning content, it takes up excessive screen space and distracts from the actual response. |
| 6 | + |
| 7 | +## Solution |
| 8 | + |
| 9 | +Add truncation logic to the `Thinking` component: default to showing at most 2 lines, with a hint to press `ctrl+o` (transcript mode) to see the full content. |
| 10 | + |
| 11 | +## Design |
| 12 | + |
| 13 | +### What Changes |
| 14 | + |
| 15 | +**File**: `src/ui/Messages.tsx` — `Thinking` component only. |
| 16 | + |
| 17 | +**Imports**: `useAppStore` is already imported in the file; `useMemo` is already imported from React. |
| 18 | + |
| 19 | +### Current Implementation |
| 20 | + |
| 21 | +```tsx |
| 22 | +function Thinking({ text }: { text: string }) { |
| 23 | + return ( |
| 24 | + <Box flexDirection="column" marginTop={SPACING.MESSAGE_MARGIN_TOP}> |
| 25 | + <Text bold color="gray">thinking</Text> |
| 26 | + <Text color="gray" italic>{text}</Text> |
| 27 | + </Box> |
| 28 | + ); |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +### New Implementation |
| 33 | + |
| 34 | +1. Read `transcriptMode` from `useAppStore()` |
| 35 | +2. Use `useMemo` with dependencies `[text, transcriptMode]` to compute truncation: |
| 36 | + - Split `text` by `\n` into `lines` |
| 37 | + - If `transcriptMode || lines.length <= 2`, show all content |
| 38 | + - Otherwise, show `lines.slice(0, 2).join('\n')` and compute `hiddenCount = lines.length - 2` |
| 39 | +3. When truncated, render a separate `<Text>` hint element below the content |
| 40 | +4. Truncated text maintains existing `color="gray" italic` styling |
| 41 | + |
| 42 | +### Truncation Hint Style |
| 43 | + |
| 44 | +Rendered as a separate `<Text>` element (not counted as part of the 2 visible lines). Reuses the same style as `ExpandableOutput`: |
| 45 | +```tsx |
| 46 | +<Text color="gray" dimColor> |
| 47 | + ... {hiddenCount} more line{hiddenCount === 1 ? '' : 's'} hidden (Press ctrl+o to expand) ... |
| 48 | +</Text> |
| 49 | +``` |
| 50 | + |
| 51 | +### Edge Cases |
| 52 | + |
| 53 | +- **Empty or whitespace-only text**: Render nothing (return early or render empty) |
| 54 | +- **Trailing newlines**: `"line1\nline2\n".split('\n')` produces `["line1", "line2", ""]`. The empty trailing element is included in the count — this is acceptable since `ExpandableOutput` follows the same behavior |
| 55 | +- **Exactly 2 lines**: Show full content, no hint |
| 56 | +- **Single line**: Show full content, no hint |
| 57 | + |
| 58 | +### Data Flow |
| 59 | + |
| 60 | +``` |
| 61 | +text (string) |
| 62 | + → useMemo([text, transcriptMode]) |
| 63 | + → split('\n') → lines[] |
| 64 | + → transcriptMode || lines.length <= 2 ? { displayText: text, shouldTruncate: false } |
| 65 | + : { displayText: lines.slice(0, 2).join('\n'), hiddenCount, shouldTruncate: true } |
| 66 | + → render <Text color="gray" italic>{displayText}</Text> |
| 67 | + → if shouldTruncate → render hint <Text> below |
| 68 | +``` |
| 69 | + |
| 70 | +### What Does NOT Change |
| 71 | + |
| 72 | +- `ExpandableOutput` component — no modifications |
| 73 | +- `ctrl+o` / `transcriptMode` toggle logic in `App.tsx` — no modifications |
| 74 | +- `DiffViewer` component — no modifications |
| 75 | +- No new files created |
| 76 | + |
| 77 | +### Constants |
| 78 | + |
| 79 | +- Max lines value `2` is used inline within the `useMemo` computation (consistent with how `ExpandableOutput` uses its `maxLines` prop default) |
| 80 | + |
| 81 | +## Testing |
| 82 | + |
| 83 | +- Verify thinking content with 0 or 1 line renders fully without hint |
| 84 | +- Verify thinking content with exactly 2 lines renders fully without hint |
| 85 | +- Verify thinking content with 3+ lines shows only 2 lines + hint |
| 86 | +- Verify the hint text matches: `... N more lines hidden (Press ctrl+o to expand) ...` |
| 87 | +- Verify pressing `ctrl+o` (transcript mode) shows full thinking content |
| 88 | +- Verify pressing `ctrl+o` again (or Escape) returns to truncated view |
| 89 | +- Verify empty text renders nothing or an empty box |
0 commit comments