# InsightBlock Implementation Plan > **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:** Render Claude Code's Insight blocks as collapsible cards instead of ugly inline code elements. **Architecture:** Frontend-only text transform. A generic segment splitter in `src/lib/` accepts adapter-scoped regex patterns. Claude-specific patterns and UI live in `src/components/adapters/claude/`. MessageBubble splits text into segments and renders InsightBlocks for matched segments. No server changes. **Tech Stack:** React, ReactMarkdown, TypeScript, Tailwind CSS, lucide-react icons --- ### Task 1: Generic Text Segment Splitter **Files:** - Create: `src/lib/text-transforms.ts` - [ ] **Step 1: Create the text-transforms module** ```typescript // src/lib/text-transforms.ts export interface TextPattern { type: string; regex: RegExp; } export interface TextSegment { type: string; text: string; } /** * Split text into typed segments based on regex patterns. * Unmatched regions become { type: 'markdown' } segments. * Fast path: returns single markdown segment when no patterns match. */ export function splitTextSegments(text: string, patterns: TextPattern[]): TextSegment[] { if (!text || patterns.length === 0) return [{ type: 'markdown', text }]; // Collect all matches from all patterns with their positions const matches: { type: string; start: number; end: number; captured: string }[] = []; for (const pattern of patterns) { const re = new RegExp(pattern.regex.source, pattern.regex.flags); let m: RegExpExecArray | null; while ((m = re.exec(text)) !== null) { matches.push({ type: pattern.type, start: m.index, end: m.index + m[0].length, captured: m[1] ?? m[0], }); } } if (matches.length === 0) return [{ type: 'markdown', text }]; matches.sort((a, b) => a.start - b.start); const segments: TextSegment[] = []; let cursor = 0; for (const match of matches) { if (match.start < cursor) continue; if (match.start > cursor) { const before = text.slice(cursor, match.start).trim(); if (before) segments.push({ type: 'markdown', text: before }); } segments.push({ type: match.type, text: match.captured.trim() }); cursor = match.end; } if (cursor < text.length) { const after = text.slice(cursor).trim(); if (after) segments.push({ type: 'markdown', text: after }); } return segments; } ``` - [ ] **Step 2: Verify build passes** - [ ] **Step 3: Commit** --- ### Task 2: Claude Adapter Patterns **Files:** - Create: `src/components/adapters/claude/patterns.ts` - [ ] **Step 1: Create Claude patterns module** ```typescript // src/components/adapters/claude/patterns.ts import type { TextPattern } from '@/lib/text-transforms'; /** * Claude Code text patterns for special content rendering. * * Insight format: * `★ Insight ─────────────────────────────────────` * [content lines] * `─────────────────────────────────────────────────` */ export const CLAUDE_PATTERNS: TextPattern[] = [ { type: 'insight', regex: /`[★✦]?\s*Insight\s*[─\-]+`\n([\s\S]*?)\n`[─\-]+[.。]?`/g, }, ]; ``` - [ ] **Step 2: Verify build passes** - [ ] **Step 3: Commit** --- ### Task 3: InsightBlock Collapsible Component **Files:** - Create: `src/components/adapters/claude/InsightBlock.tsx` **Reference:** Follow `src/components/ToolCallCard.tsx` expand/collapse pattern (useState, ChevronDown/Up icons). - [ ] **Step 1: Create InsightBlock component** ```tsx // src/components/adapters/claude/InsightBlock.tsx import { useState } from 'react'; import { ChevronDown, ChevronUp } from 'lucide-react'; import ReactMarkdown from 'react-markdown'; import { cn } from '@/lib/utils'; export function InsightBlock({ text }: { text: string }) { const [expanded, setExpanded] = useState(false); const summary = text.split('\n').find(l => l.trim())?.trim() || 'Insight'; const truncated = summary.length > 80 ? summary.slice(0, 80) + '...' : summary; return (