Files
clawtap/docs/superpowers/specs/2026-03-23-insight-block-design.md
T
kuannnn 42861ea7fa feat: ClawTap v0.1.0 — initial release
Multi-adapter mobile UI for AI coding assistants.
Supports Claude Code, Codex CLI, and Gemini CLI through one interface.

Features:
- Real-time bidirectional sync via tmux + WebSocket
- Cross-AI review (send one AI's output to another for review)
- Multi-review tabs with minimize/expand
- Push notifications (PWA) with smart session-aware filtering
- Three-channel event system (hooks, file watcher, pane monitor)
- Voice input, image paste, draft persistence
- Terminal-native design (JetBrains Mono, dark theme, pixel art claw)
- CLI with --adapter flag on every command
- Zero-overhead fire-and-forget hooks
2026-03-26 10:40:26 +08:00

137 lines
4.9 KiB
Markdown

# InsightBlock — Adapter-Specific Content Rendering
## Problem
Claude Code produces "Insight" blocks in its markdown responses:
```
`★ Insight ─────────────────────────────────────`
[educational content]
`─────────────────────────────────────────────────`
```
These currently render as ugly inline `<code>` elements in ReactMarkdown. Need a dedicated, collapsible UI component — while keeping the architecture extensible for other adapters (Gemini, Codex) that may have their own text patterns.
## Design
### Approach: Frontend Text Transform with Adapter-Scoped Patterns
- **No server changes.** The Insight text flows through the existing pipeline as `{ type: 'text' }` content blocks.
- **Regex patterns** defined per-adapter in adapter-scoped files.
- **Generic splitter** in `src/lib/` accepts patterns as parameters — no coupling to any specific adapter.
- **Collapsible card UI** matching ToolCallCard's expand/collapse pattern.
### File Structure
```
src/
├── lib/
│ └── text-transforms.ts # Generic: splitTextSegments(text, patterns)
├── components/
│ ├── adapters/
│ │ └── claude/
│ │ ├── InsightBlock.tsx # Collapsible insight card
│ │ └── patterns.ts # INSIGHT_RE regex + segment type
│ ├── MessageBubble.tsx # Modified: split → map → render
│ └── ...
```
**Dependency direction:**
```
MessageBubble → text-transforms (generic)
MessageBubble → adapters/claude/patterns (adapter-specific)
MessageBubble → adapters/claude/InsightBlock (adapter-specific)
```
Generic code never imports adapter-specific code. MessageBubble is the composition root.
### `src/components/adapters/claude/patterns.ts`
Exports Claude-specific text patterns:
```typescript
import type { TextPattern } from '@/lib/text-transforms';
export const CLAUDE_PATTERNS: TextPattern[] = [
{
type: 'insight',
regex: /`[★✦]?\s*Insight[─\-\s]*`\n([\s\S]*?)\n`[─\-]+[.。]?`/g,
},
];
```
### `src/lib/text-transforms.ts`
Generic segment splitter — adapter-agnostic:
```typescript
export interface TextPattern {
type: string;
regex: RegExp;
}
export type TextSegment = {
type: string; // 'markdown' | 'insight' | future types
text: string;
};
export function splitTextSegments(text: string, patterns: TextPattern[]): TextSegment[] {
// Fast path: no patterns or no text → return as-is
// For each pattern, find all matches and record their positions
// Split text into alternating markdown/matched segments
// Streaming safety: unmatched opening fence → treat as plain markdown
}
```
### `src/components/adapters/claude/InsightBlock.tsx`
Collapsible card — Style C from brainstorming:
- **Collapsed (default):** `★` icon + "Insight" label + first-line summary (truncated) + `▼` chevron
- **Expanded:** Full content rendered via ReactMarkdown with prose styling
- **Visual:** `bg-surface/30 border border-border/50 rounded-lg` — subtle card with accent star
### `src/components/MessageBubble.tsx` Changes
```diff
+ import { splitTextSegments } from '@/lib/text-transforms';
+ import { CLAUDE_PATTERNS } from './adapters/claude/patterns';
+ import { InsightBlock } from './adapters/claude/InsightBlock';
// In assistant message render:
const textContent = content.filter(...).map(...).join('');
+ const segments = splitTextSegments(textContent, CLAUDE_PATTERNS);
- <ReactMarkdown ...>{textContent}</ReactMarkdown>
+ {segments.map((seg, i) =>
+ seg.type === 'insight'
+ ? <InsightBlock key={i} text={seg.text} />
+ : <ReactMarkdown key={i} components={markdownComponents}>{seg.text}</ReactMarkdown>
+ )}
```
## Extensibility
When Gemini adds "Analysis" blocks:
1. `src/components/adapters/gemini/patterns.ts` — export `GEMINI_PATTERNS`
2. `src/components/adapters/gemini/AnalysisBlock.tsx` — new component
3. `MessageBubble.tsx` — merge patterns: `[...CLAUDE_PATTERNS, ...GEMINI_PATTERNS]`
4. Add one more segment type case in the render map
No server changes. No refactoring. Explicit additions only.
## Edge Cases
- **Streaming:** Partial insight (opening fence without closing) → `splitTextSegments` treats as plain markdown. InsightBlock only renders when both fences are present.
- **No insights:** Fast path — `splitTextSegments` returns `[{ type: 'markdown', text }]`, single ReactMarkdown render. No performance regression.
- **Multiple insights:** Each becomes its own InsightBlock in the segments array, with markdown segments between them.
- **Nested markdown in insight body:** ReactMarkdown inside InsightBlock handles bullet points, code, links.
## Not Changing
- Server pipeline (TranscriptParser, session-manager, IAdapter)
- ChatView.tsx (Insight is text-layer, not content-block-layer)
- useChat.ts
- WS protocol