- Introduced WorkerService class to handle HTTP requests and manage sessions. - Added endpoints for health check, session management, and data retrieval. - Integrated ChromaSync for background data synchronization. - Implemented SSE for real-time updates to connected clients. - Added error handling and logging throughout the service. - Cached Claude executable path for improved performance. - Included settings management for user configuration. - Established database interactions for session and observation management.
16 KiB
Plan: Display Complete Observation Data in Viewer UI
Current State Analysis
What's Currently Shown (5 fields)
- ✅ type - Displayed as chip/badge (e.g., "discovery", "bugfix")
- ✅ project - Shown in card header
- ✅ title - Main card title (shows "Untitled" if null)
- ✅ subtitle - Optional subheading
- ✅ id + created_at - Metadata line (e.g., "#1 • 2 hours ago")
What's Hidden (10+ fields)
- ❌ narrative - Detailed explanation text (MOST IMPORTANT)
- ❌ facts - JSON array of key facts (structured bullet points)
- ❌ concepts - JSON array of concept tags (e.g., "problem-solution", "gotcha")
- ❌ files_read - JSON array of file paths that were read
- ❌ files_modified - JSON array of file paths that were modified
- ❌ text - Legacy unstructured text field (deprecated but still populated)
- ❌ prompt_number - Which user prompt triggered this observation
- ❌ sdk_session_id - Session identifier
Database Schema (Actual Structure)
observations table:
- id (INTEGER PRIMARY KEY)
- sdk_session_id (TEXT)
- project (TEXT)
- type (TEXT: decision, bugfix, feature, refactor, discovery, change)
- created_at (TEXT ISO timestamp)
- created_at_epoch (INTEGER milliseconds)
- prompt_number (INTEGER nullable)
- title (TEXT nullable)
- subtitle (TEXT nullable)
- narrative (TEXT nullable) -- Rich detailed explanation
- text (TEXT nullable) -- Legacy field
- facts (TEXT nullable) -- JSON array of key facts
- concepts (TEXT nullable) -- JSON array of concept tags
- files_read (TEXT nullable) -- JSON array of file paths
- files_modified (TEXT nullable) -- JSON array of file paths
Issues Found
-
Type Definition Mismatch: Three different type definitions exist:
- Actual database schema (most complete)
worker-types.tsObservation interface (flattened, has wrong field names)viewer/types.tsObservation interface (minimal subset)
-
Data Loss: Rich fields are stored in DB but not transmitted to UI:
- narrative, facts, files_read, files_modified all missing from API
-
PaginationHelper Query Bug: Selects non-existent fields:
session_db_id(should besdk_session_id)claude_session_id(doesn't exist in observations table)files(should befiles_read+files_modified)
Proposed Implementation Plan
Phase 1: Fix Data Layer
1.1 Update Viewer Type Definitions
File: src/ui/viewer/types.ts
export interface Observation {
id: number;
sdk_session_id: string;
project: string;
type: string;
title: string | null;
subtitle: string | null;
narrative: string | null; // NEW - detailed explanation
text: string | null; // Legacy field
facts: string | null; // NEW - JSON array of key facts
concepts: string | null; // NEW - JSON array of concept tags
files_read: string | null; // NEW - JSON array of file paths
files_modified: string | null; // NEW - JSON array of file paths
prompt_number: number | null; // NEW - which prompt triggered this
created_at: string;
created_at_epoch: number;
}
1.2 Fix PaginationHelper SQL Query
File: src/services/worker/PaginationHelper.ts (around line 26)
Current (BROKEN):
const fields = 'id, session_db_id, claude_session_id, project, type, title, subtitle, text, concepts, files, prompt_number, created_at, created_at_epoch';
Fixed:
const fields = 'id, sdk_session_id, project, type, title, subtitle, narrative, text, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch';
1.3 Update Worker Service v2 Response Mapping
File: src/services/worker-service-v2.ts
Ensure the /api/observations endpoint properly maps all fields from database to response. May need to parse JSON fields (facts, concepts, files_read, files_modified) if they're stored as JSON strings.
Phase 2: Redesign UI Component
2.1 Update ObservationCard Component
File: src/ui/viewer/components/ObservationCard.tsx
New Structure:
┌─────────────────────────────────────────┐
│ [type badge] [project] │ ← Header (always visible)
├─────────────────────────────────────────┤
│ Title │ ← Always visible
│ Subtitle (if present) │ ← Always visible
│ #123 • 2 hours ago [▼ More]│ ← Metadata + Expand button
├─────────────────────────────────────────┤
│ │
│ ┌─ EXPANDED CONTENT (when opened) ───┐ │
│ │ │ │
│ │ 📝 Narrative │ │
│ │ ─────────────────────────────────── │ │
│ │ Detailed explanation text... │ │
│ │ │ │
│ │ 📌 Key Facts │ │
│ │ ─────────────────────────────────── │ │
│ │ • Fact 1 │ │
│ │ • Fact 2 │ │
│ │ • Fact 3 │ │
│ │ │ │
│ │ 🏷️ Concepts │ │
│ │ ─────────────────────────────────── │ │
│ │ [problem-solution] [discovery] │ │
│ │ │ │
│ │ 📁 Files │ │
│ │ ─────────────────────────────────── │ │
│ │ 📖 Read: │ │
│ │ src/hooks/save-hook.ts │ │
│ │ src/services/worker.ts │ │
│ │ ✏️ Modified: │ │
│ │ src/hooks/save-hook.ts │ │
│ │ │ │
│ │ 🔗 Session Info │ │
│ │ ─────────────────────────────────── │ │
│ │ Prompt #5 • Session: abc123... │ │
│ │ │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────┘
Component Logic:
const ObservationCard = ({ observation }) => {
const [isExpanded, setIsExpanded] = useState(false);
// Parse JSON fields
const facts = observation.facts ? JSON.parse(observation.facts) : [];
const concepts = observation.concepts ? JSON.parse(observation.concepts) : [];
const filesRead = observation.files_read ? JSON.parse(observation.files_read) : [];
const filesModified = observation.files_modified ? JSON.parse(observation.files_modified) : [];
return (
<div className={`card ${isExpanded ? 'card-expanded' : ''}`}>
{/* Header - always visible */}
<div className="card-header">
<span className={`card-type type-${observation.type}`}>
{observation.type}
</span>
<span className="card-project">{observation.project}</span>
</div>
{/* Title/Subtitle - always visible */}
<div className="card-title">{observation.title || 'Untitled'}</div>
{observation.subtitle && (
<div className="card-subtitle">{observation.subtitle}</div>
)}
{/* Metadata + Expand button - always visible */}
<div className="card-meta">
<span>#{observation.id} • {formatDate(observation.created_at_epoch)}</span>
<button
className="expand-toggle"
onClick={() => setIsExpanded(!isExpanded)}
>
{isExpanded ? '▲ Less' : '▼ More'}
</button>
</div>
{/* Expanded content - conditional */}
{isExpanded && (
<div className="card-expanded-content">
{/* Narrative Section */}
{observation.narrative && (
<div className="card-section">
<div className="section-header">📝 Narrative</div>
<div className="section-content narrative">
{observation.narrative}
</div>
</div>
)}
{/* Facts Section */}
{facts.length > 0 && (
<div className="card-section">
<div className="section-header">📌 Key Facts</div>
<ul className="section-content facts-list">
{facts.map((fact, i) => (
<li key={i}>{fact}</li>
))}
</ul>
</div>
)}
{/* Concepts Section */}
{concepts.length > 0 && (
<div className="card-section">
<div className="section-header">🏷️ Concepts</div>
<div className="section-content concepts">
{concepts.map((concept, i) => (
<span key={i} className="concept-tag">{concept}</span>
))}
</div>
</div>
)}
{/* Files Section */}
{(filesRead.length > 0 || filesModified.length > 0) && (
<div className="card-section">
<div className="section-header">📁 Files</div>
<div className="section-content files">
{filesRead.length > 0 && (
<div className="file-group">
<div className="file-group-label">📖 Read:</div>
{filesRead.map((file, i) => (
<div key={i} className="file-path">{file}</div>
))}
</div>
)}
{filesModified.length > 0 && (
<div className="file-group">
<div className="file-group-label">✏️ Modified:</div>
{filesModified.map((file, i) => (
<div key={i} className="file-path">{file}</div>
))}
</div>
)}
</div>
</div>
)}
{/* Session Info Section */}
<div className="card-section">
<div className="section-header">🔗 Session Info</div>
<div className="section-content session-info">
{observation.prompt_number && (
<span>Prompt #{observation.prompt_number}</span>
)}
{observation.sdk_session_id && (
<span className="session-id">
Session: {observation.sdk_session_id.substring(0, 8)}...
</span>
)}
</div>
</div>
</div>
)}
</div>
);
};
Phase 3: Style Enhancements
3.1 Update Styles
File: src/ui/viewer/styles.css
New CSS Classes Needed:
/* Expanded card state */
.card-expanded {
/* Maybe increase shadow or border when expanded */
}
/* Expand toggle button */
.expand-toggle {
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
font-size: 12px;
padding: 4px 8px;
border-radius: 4px;
}
.expand-toggle:hover {
background: var(--bg-secondary);
}
/* Expanded content container */
.card-expanded-content {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid var(--border-color);
animation: expandDown 0.2s ease-out;
}
@keyframes expandDown {
from {
opacity: 0;
transform: translateY(-8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Section styling */
.card-section {
margin-bottom: 16px;
}
.card-section:last-child {
margin-bottom: 0;
}
.section-header {
font-weight: 600;
font-size: 13px;
color: var(--text-primary);
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 6px;
}
.section-content {
padding-left: 20px;
color: var(--text-secondary);
font-size: 13px;
line-height: 1.6;
}
/* Narrative styling */
.narrative {
max-height: 300px;
overflow-y: auto;
white-space: pre-wrap;
word-wrap: break-word;
}
/* Facts list styling */
.facts-list {
list-style: disc;
margin: 0;
padding-left: 20px;
}
.facts-list li {
margin-bottom: 4px;
}
/* Concepts tags */
.concepts {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.concept-tag {
background: var(--accent-bg);
color: var(--accent-text);
padding: 4px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 500;
}
/* File paths */
.file-group {
margin-bottom: 8px;
}
.file-group:last-child {
margin-bottom: 0;
}
.file-group-label {
font-weight: 500;
margin-bottom: 4px;
color: var(--text-primary);
}
.file-path {
font-family: 'SF Mono', 'Monaco', 'Courier New', monospace;
font-size: 12px;
padding: 4px 8px;
background: var(--code-bg);
border-radius: 4px;
margin-bottom: 2px;
overflow-x: auto;
white-space: nowrap;
}
/* Session info */
.session-info {
display: flex;
gap: 16px;
font-size: 12px;
}
.session-id {
font-family: 'SF Mono', 'Monaco', 'Courier New', monospace;
color: var(--text-tertiary);
}
Implementation Steps (In Order)
-
Fix PaginationHelper query (src/services/worker/PaginationHelper.ts)
- Update SQL SELECT to use correct field names
- Test with
npm run worker:restart:v2
-
Update viewer type definitions (src/ui/viewer/types.ts)
- Add all missing fields to Observation interface
-
Verify worker service v2 mapping (src/services/worker-service-v2.ts)
- Ensure
/api/observationsreturns all fields - Test API response with curl or browser
- Ensure
-
Update ObservationCard component (src/ui/viewer/components/ObservationCard.tsx)
- Add expand/collapse state
- Add all new sections (narrative, facts, concepts, files, session)
- Add expand toggle button
-
Update styles (src/ui/viewer/styles.css)
- Add all new CSS classes for expanded content
- Add animations for smooth expand/collapse
- Style sections, lists, tags, file paths
-
Build and test
npm run build npm run sync-marketplace npm run worker:restart:v2 -
Manual testing
- Open http://localhost:37777
- Click expand button on observations
- Verify all fields display correctly
- Test light/dark mode
- Test with observations that have missing fields (graceful fallback)
Success Criteria
- All database fields are fetched in API query
- All fields are properly typed in TypeScript interfaces
- ObservationCard shows all data in expanded view
- Expand/collapse animations work smoothly
- File paths are formatted in monospace font
- Concepts display as tag pills
- Facts display as bulleted list
- Narrative text wraps properly with scroll for long content
- No console errors
- Works in both light and dark themes
Optional Enhancements (Future)
- Remember expanded state in localStorage (persist across page refresh)
- Keyboard shortcuts (Space to expand/collapse focused card)
- Click file paths to copy to clipboard
- Search/filter by concepts or files
- Syntax highlighting for code in narrative
- Link session_id to session detail view