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

4.9 KiB

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:

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:

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

+ 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