For each message in the list:1. Always include INTERRUPTION messages2. Always include USER messages → AND look backward to find the last AGENT_TEXT before this user message (provides context for why the user sent their next prompt)3. Always include CODE CHANGES (edit, write, delete tools)┌─────────────────────────────────────────────────────────────────────┐│ page.tsx ││ ┌─────────────────────────────────────────────────────────────┐ ││ │ useMessageFilters(messages, filter) │ ││ │ → returns { highlightedIndices, counts, isHighlighted } │ ││ └─────────────────────────────────────────────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────────────┐ ││ │ <MessageList filter={filter} /> │ ││ │ → calls useMessageFilters internally │ ││ │ → uses isHighlighted(msg, index) to decide: │ ││ │ • Show message directly (highlighted) │ ││ │ • Collapse into accordion (not highlighted) │ ││ └─────────────────────────────────────────────────────────────┘ │└─────────────────────────────────────────────────────────────────────┘const { icon: Icon, label, iconClassName } = MESSAGE_TYPES[kind];const Icon = getIcon(kind);const label = getLabel(kind);export function getTypeConfig(id: string) { return MESSAGE_TYPES[id] ?? MESSAGE_TYPES.tool_call; // fallback for unknown tools}// Without helper, every call site does this:const label = variant === 'short' ? config.shortLabel : config.label;
// With helper:const label = getLabel(kind, 'short');const config = MESSAGE_TYPES[kind] ?? MESSAGE_TYPES.tool_call;const { icon: Icon, label, iconClassName } = config;Object.values(MESSAGE_TYPES) .filter(t => t.category === 'message_kind') .map(({ id, icon: Icon, label }) => ...)const config = MESSAGE_TYPES[toolType];if (!config) { // Already handled by DefaultToolMessage / Wrench icon}const knownToolTypes = toolTypes.filter(t => MESSAGE_TYPES[t]);raw tool name → normalizeToolName() → canonical ID → MESSAGE_TYPES[id].icon/label/etcraw tool name → normalizeToolName() → canonical ID → MESSAGE_TYPES[id].icon/label/orderconst { icon, label } = getMessageType(normalizedToolName);# Filter Refactoring
**Goal:** Eliminate duplication in message type metadata and filtering logic by creating centralized registries, then simplify components to consume from these single sources of truth.
---
## Phase 1: Message Type Registry (Foundation)
**Objective:** Create a single source of truth for ALL message type metadata.
### Tasks
- [ ] Create `lib/message-type-registry.ts` with unified config: ```typescript type MessageTypeConfig = { id: MessageKind; label: string; // Filter UI: "Prompts" shortLabel: string; // Compact UI: "User" icon: LucideIcon; iconClassName?: string; // e.g., "scale-x-[-1]" urlValue: string; // URL param value order: number; // Display sort order }; export const MESSAGE_TYPE_REGISTRY: Record<MessageKind, MessageTypeConfig>; export const FILTERABLE_TYPES: MessageKind[];export function buildFilterUrl( filters: MessageFilter[], toolTypes: string[], baseParams: URLSearchParams): string;
export function parseFiltersFromUrl( params: URLSearchParams): { filters: MessageFilter[]; toolTypes: string[] };
export function createFilterToggler( currentFilters: MessageFilter[], filter: MessageFilter): MessageFilter[];
---
This plan ensures:
1. **Each phase is orthogonal** - Phase 1-2 are pure additions, Phase 3 is pure deletion, Phases 4-7 each touch one component2. **Each phase is verifiable in isolation** - Clear acceptance criteria for each3. **Phases are small and reversible** - If one breaks something, easy to revert4. **Foundation first** - Registries created before components are refactored to use them
Would you like me to switch to Agent mode so I can update the project markdown file with this revised plan?┌─────────────────────────────────────────────────────────────┐│ message-type-registry.ts (Message Kinds) ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││ │user_message │ │ agent_text │ │agent_thinking│ ││ └─────────────┘ └─────────────┘ └─────────────┘ ││ ││ ┌─────────────────────────────────────────────────────┐ ││ │ tool_call │ ││ │ ┌──────────────────────────────────────────────┐ │ ││ │ │ tool-utils.ts (Tool Types) │ │ ││ │ │ read, edit, write, grep, terminal_command… │ │ ││ │ └──────────────────────────────────────────────┘ │ ││ └─────────────────────────────────────────────────────┘ │└─────────────────────────────────────────────────────────────┘// message-type-registry.ts
import { getToolTypeIcon, getToolTypeDisplayName } from './tool-utils';
export const MESSAGE_TYPE_REGISTRY = { user_message: { id: 'user_message', label: 'Prompts', shortLabel: 'User', icon: MessageSquare, iconClassName: 'scale-x-[-1]', // ... }, agent_thinking: { id: 'agent_thinking', label: 'Thinking', shortLabel: 'Thinking', icon: Brain, // ... }, tool_call: { id: 'tool_call', label: 'Tool Calls', shortLabel: 'Tool', icon: Wrench, // Generic fallback // For specific tool icons, components call tool-utils.ts directly // ... }, // ...};
// Helper that bridges both levelsexport function getIconForMessage(message: Message): LucideIcon { const kind = getMessageKind(message); if (kind === 'tool_call') { // Delegate to tool-utils for specific tool icon const toolName = getToolNameFromMessage(message); return getToolTypeIcon(normalizeToolName(toolName) || ''); } return MESSAGE_TYPE_REGISTRY[kind].icon;}// lib/message-type-registry.ts
type MessageTypeConfig = { id: string; label: string; // Full label: "Read", "Prompts", "Thinking" shortLabel: string; // Compact: "Read", "User", "Thinking" icon: LucideIcon; iconClassName?: string; urlValue: string; // URL param value for filtering order: number; // Display sort order isFilterable: boolean; // Shows in filter UI category?: 'message_kind' | 'tool_type'; // What level this belongs to parentKind?: string; // For tools: 'tool_call'};
export const MESSAGE_TYPES: Record<string, MessageTypeConfig> = { // ═══════════════════════════════════════════════════════ // MESSAGE KINDS (top-level filter categories) // ═══════════════════════════════════════════════════════ user_message: { id: 'user_message', label: 'Prompts', shortLabel: 'User', icon: MessageSquare, iconClassName: 'scale-x-[-1]', urlValue: 'user_message', order: 1, isFilterable: true, category: 'message_kind', }, agent_text: { id: 'agent_text', label: 'Agent Response', shortLabel: 'Assistant', icon: MessageSquare, urlValue: 'agent_text', order: 2, isFilterable: true, category: 'message_kind', }, agent_thinking: { id: 'agent_thinking', label: 'Thinking', shortLabel: 'Thinking', icon: Brain, urlValue: 'agent_thinking', order: 3, isFilterable: true, category: 'message_kind', }, tool_call: { id: 'tool_call', label: 'Tool Calls', shortLabel: 'Tool', icon: Wrench, urlValue: 'tool_call', order: 4, isFilterable: true, category: 'message_kind', },
// ═══════════════════════════════════════════════════════ // TOOL TYPES (sub-filters under tool_call) // ═══════════════════════════════════════════════════════ read: { id: 'read', label: 'Read', shortLabel: 'Read', icon: BookOpen, urlValue: 'read', order: 10, isFilterable: true, category: 'tool_type', parentKind: 'tool_call', }, edit: { id: 'edit', label: 'Edit', shortLabel: 'Edit', icon: Pen, urlValue: 'edit', order: 11, isFilterable: true, category: 'tool_type', parentKind: 'tool_call', }, write: { id: 'write', label: 'Write', shortLabel: 'Write', icon: FilePlus, urlValue: 'write', order: 12, isFilterable: true, category: 'tool_type', parentKind: 'tool_call', }, // ... delete, grep, glob, terminal_command, web_search, etc.};// lib/tool-utils.ts (simplified)
/** * Maps raw tool names from various agents to canonical IDs * that exist in MESSAGE_TYPES registry */const TOOL_NAME_ALIASES: Record<string, string> = { bash: 'terminal_command', shell: 'terminal_command', strreplace: 'edit', readfile: 'read', // ... all the aliases};
export function normalizeToolName(rawName: string | undefined): string | undefined { if (!rawName) return undefined; const normalized = rawName.toLowerCase().replace(/[-_\s]/g, ''); return TOOL_NAME_ALIASES[normalized] || rawName;}// lib/message-type-registry.ts
export function getTypeConfig(id: string): MessageTypeConfig { return MESSAGE_TYPES[id] ?? MESSAGE_TYPES.tool_call; // fallback}
export function getIcon(id: string): LucideIcon { return getTypeConfig(id).icon;}
export function getLabel(id: string, variant: 'full' | 'short' = 'full'): string { const config = getTypeConfig(id); return variant === 'short' ? config.shortLabel : config.label;}
export function getFilterableKinds(): MessageTypeConfig[] { return Object.values(MESSAGE_TYPES) .filter(t => t.isFilterable && t.category === 'message_kind') .sort((a, b) => a.order - b.order);}
export function getToolTypes(): MessageTypeConfig[] { return Object.values(MESSAGE_TYPES) .filter(t => t.category === 'tool_type') .sort((a, b) => a.order - b.order);}type MessageTypeConfig = { id: string; label: string; shortLabel: string; icon: LucideIcon; iconClassName?: string; urlValue: string; order: number; category: 'message_kind' | 'tool_type'; parentKind?: string; // Only for tool_type: 'tool_call'};