coaia-visualizer 1.4.3 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +9 -0
- package/Dockerfile.app +50 -0
- package/Dockerfile.test +24 -0
- package/LIVE_MODE_DESIGN.md +435 -0
- package/MCP_TESTING_COMPLETE.md +302 -0
- package/MCP_TESTING_IMPLEMENTATION_SUMMARY.md +317 -0
- package/MCP_TESTING_SETUP.md +268 -0
- package/NAMING.md +218 -0
- package/QUICK_START_MCP_TESTING.md +236 -0
- package/WS__issue_8__coaia-visualizer__260207.code-workspace +45 -0
- package/app/api/audio/[filename]/route.ts +37 -0
- package/app/api/charts/[id]/route.ts +48 -35
- package/app/api/watch/route.ts +42 -0
- package/app/page.tsx +13 -0
- package/cli.ts +56 -3
- package/components/live-indicator.tsx +14 -0
- package/direct-test.sh +180 -0
- package/dist/cli.js +52 -3
- package/docker-compose.test.yml +69 -0
- package/hooks/use-live-polling.ts +45 -0
- package/jgwill.coaia-visualizer-8--496dca71-d476-4ac9-ba9f-376add118dd8--260208.txt +2612 -0
- package/mcp/Dockerfile +21 -0
- package/mcp/README.md +25 -6
- package/mcp/src/api-client.ts +15 -3
- package/mcp/src/index.ts +17 -2
- package/mcp/src/tools/index.ts +21 -1
- package/mcp-config.json +14 -0
- package/package.json +1 -1
- package/run-mcp-tests.sh +99 -0
- package/test-data/test-master.jsonl +11 -0
- package/test-run.log +101 -0
- package/test-scripts/README.md +239 -0
- package/test-scripts/run-all-tests.sh +38 -0
- package/test-scripts/test-01-basic-operations.sh +87 -0
- package/test-scripts/test-02-telescope-creation.sh +91 -0
- package/test-scripts/test-03-navigation.sh +87 -0
- package/validate-mcp.sh +136 -0
package/.dockerignore
ADDED
package/Dockerfile.app
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Build stage
|
|
2
|
+
FROM node:20-alpine AS builder
|
|
3
|
+
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
|
|
6
|
+
# Copy package files
|
|
7
|
+
COPY package.json pnpm-lock.yaml ./
|
|
8
|
+
|
|
9
|
+
# Install pnpm and dependencies
|
|
10
|
+
RUN npm install -g pnpm@latest && \
|
|
11
|
+
pnpm install --frozen-lockfile
|
|
12
|
+
|
|
13
|
+
# Copy application code
|
|
14
|
+
COPY . .
|
|
15
|
+
|
|
16
|
+
# Build Next.js application
|
|
17
|
+
RUN pnpm build
|
|
18
|
+
|
|
19
|
+
# Production stage
|
|
20
|
+
FROM node:20-alpine AS runner
|
|
21
|
+
|
|
22
|
+
WORKDIR /app
|
|
23
|
+
|
|
24
|
+
# Install pnpm
|
|
25
|
+
RUN npm install -g pnpm@latest
|
|
26
|
+
|
|
27
|
+
# Copy package files
|
|
28
|
+
COPY package.json pnpm-lock.yaml ./
|
|
29
|
+
|
|
30
|
+
# Install production dependencies only, skip prepare hook
|
|
31
|
+
RUN pnpm install --prod --frozen-lockfile --ignore-scripts
|
|
32
|
+
|
|
33
|
+
# Copy built application from builder
|
|
34
|
+
COPY --from=builder /app/.next ./.next
|
|
35
|
+
COPY --from=builder /app/public ./public
|
|
36
|
+
COPY --from=builder /app/next.config.mjs ./
|
|
37
|
+
COPY --from=builder /app/app ./app
|
|
38
|
+
COPY --from=builder /app/components ./components
|
|
39
|
+
COPY --from=builder /app/lib ./lib
|
|
40
|
+
COPY --from=builder /app/dist ./dist
|
|
41
|
+
|
|
42
|
+
# Create data directories
|
|
43
|
+
RUN mkdir -p /app/samples /app/test-data
|
|
44
|
+
|
|
45
|
+
ENV NODE_ENV=production
|
|
46
|
+
ENV PORT=4321
|
|
47
|
+
|
|
48
|
+
EXPOSE 4321
|
|
49
|
+
|
|
50
|
+
CMD ["pnpm", "start"]
|
package/Dockerfile.test
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
FROM node:20-alpine
|
|
2
|
+
|
|
3
|
+
WORKDIR /test
|
|
4
|
+
|
|
5
|
+
# Install system dependencies
|
|
6
|
+
RUN apk add --no-cache \
|
|
7
|
+
curl \
|
|
8
|
+
jq \
|
|
9
|
+
bash \
|
|
10
|
+
git \
|
|
11
|
+
python3 \
|
|
12
|
+
py3-pip
|
|
13
|
+
|
|
14
|
+
# Create directories
|
|
15
|
+
RUN mkdir -p /test-scripts /test-results /test-data
|
|
16
|
+
|
|
17
|
+
# Copy test scripts
|
|
18
|
+
COPY test-scripts /test-scripts
|
|
19
|
+
|
|
20
|
+
# Make scripts executable
|
|
21
|
+
RUN chmod +x /test-scripts/*.sh
|
|
22
|
+
|
|
23
|
+
# Default command
|
|
24
|
+
CMD ["/bin/bash"]
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
# 🎬 COAIA Visualizer: Live Narrative Observer Mode
|
|
2
|
+
|
|
3
|
+
## Vision
|
|
4
|
+
|
|
5
|
+
Transform coaia-visualizer into a **real-time narrative observatory** that watches structural tension charts being created, displays them as they emerge, and creates an immersive audio-visual experience.
|
|
6
|
+
|
|
7
|
+
## Current State
|
|
8
|
+
|
|
9
|
+
- ✅ Next.js app with JSONL parsing
|
|
10
|
+
- ✅ Narrative beat display components
|
|
11
|
+
- ✅ Manual reload button
|
|
12
|
+
- ✅ Chart editing capabilities
|
|
13
|
+
- ✅ CLI launcher with memory-path flag
|
|
14
|
+
|
|
15
|
+
## Target State
|
|
16
|
+
|
|
17
|
+
- 🎯 Live monitoring mode with auto-refresh
|
|
18
|
+
- 🎯 Audio file integration with auto-play
|
|
19
|
+
- 🎯 Timeline visualization
|
|
20
|
+
- 🎯 Real-time beat appearance animations
|
|
21
|
+
- 🎯 Observer dashboard for multiple sessions
|
|
22
|
+
|
|
23
|
+
## Architecture
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
┌──────────────────────────────────────────────────┐
|
|
27
|
+
│ Session: Creating Narrative Beats │
|
|
28
|
+
│ ├─ stc_observer.sh --read │
|
|
29
|
+
│ │ ├─ Watches: _claude_PreToolUse.jsonl │
|
|
30
|
+
│ │ ├─ Processes: MCP tool calls │
|
|
31
|
+
│ │ ├─ Generates: audio/{timestamp}.mp3/.txt │
|
|
32
|
+
│ │ └─ Appends: charts_*.coaia-narrative.jsonl │
|
|
33
|
+
│ └─ Outputs to: workspace/.coaia/*.jsonl │
|
|
34
|
+
└──────────────────────────────────────────────────┘
|
|
35
|
+
↓ (polling every 2s)
|
|
36
|
+
┌──────────────────────────────────────────────────┐
|
|
37
|
+
│ coaia-visualizer --live │
|
|
38
|
+
│ ├─ Polls JSONL for changes │
|
|
39
|
+
│ ├─ Detects new beats (via beatCount) │
|
|
40
|
+
│ ├─ Auto-scrolls to latest │
|
|
41
|
+
│ ├─ Plays audio/{timestamp}.mp3 (auto-play) │
|
|
42
|
+
│ ├─ Shows timeline with act progression │
|
|
43
|
+
│ └─ Live indicator (pulse animation) │
|
|
44
|
+
└──────────────────────────────────────────────────┘
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Implementation Plan
|
|
48
|
+
|
|
49
|
+
### Phase 1: Live Monitoring Core
|
|
50
|
+
|
|
51
|
+
#### 1.1. CLI Enhancement
|
|
52
|
+
**File:** `cli.ts`
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// New flags
|
|
56
|
+
--live Enable live monitoring mode
|
|
57
|
+
--poll-interval MS Polling interval (default: 2000ms)
|
|
58
|
+
--auto-play Auto-play audio for new beats
|
|
59
|
+
--audio-dir PATH Audio directory (default: ./audio)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Environment Variables:**
|
|
63
|
+
```bash
|
|
64
|
+
COAIAV_LIVE=true
|
|
65
|
+
COAIAV_POLL_INTERVAL=2000
|
|
66
|
+
COAIAV_AUTO_PLAY=true
|
|
67
|
+
COAIAV_AUDIO_DIR=./audio
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### 1.2. Watch API Route
|
|
71
|
+
**File:** `app/api/watch/route.ts`
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
export async function GET() {
|
|
75
|
+
const memoryPath = process.env.COAIAV_MEMORY_PATH;
|
|
76
|
+
const stats = await fs.stat(memoryPath);
|
|
77
|
+
const content = await fs.readFile(memoryPath, 'utf-8');
|
|
78
|
+
const lines = content.trim().split('\n');
|
|
79
|
+
|
|
80
|
+
// Count beats
|
|
81
|
+
const beatCount = lines.filter(line => {
|
|
82
|
+
const record = JSON.parse(line);
|
|
83
|
+
return record.type === 'entity' &&
|
|
84
|
+
record.metadata?.type === 'narrative_beat';
|
|
85
|
+
}).length;
|
|
86
|
+
|
|
87
|
+
return NextResponse.json({
|
|
88
|
+
lastModified: stats.mtime.getTime(),
|
|
89
|
+
beatCount,
|
|
90
|
+
fileSize: stats.size
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### 1.3. Audio API Route
|
|
96
|
+
**File:** `app/api/audio/[timestamp]/route.ts`
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
export async function GET(
|
|
100
|
+
request: NextRequest,
|
|
101
|
+
{ params }: { params: { timestamp: string } }
|
|
102
|
+
) {
|
|
103
|
+
const audioDir = process.env.COAIAV_AUDIO_DIR || './audio';
|
|
104
|
+
const audioPath = path.join(audioDir, `${params.timestamp}.mp3`);
|
|
105
|
+
|
|
106
|
+
const file = await fs.readFile(audioPath);
|
|
107
|
+
return new NextResponse(file, {
|
|
108
|
+
headers: {
|
|
109
|
+
'Content-Type': 'audio/mpeg',
|
|
110
|
+
'Content-Length': file.length.toString()
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Phase 2: Live Mode UI
|
|
117
|
+
|
|
118
|
+
#### 2.1. Live Monitor Hook
|
|
119
|
+
**File:** `hooks/use-live-monitor.ts`
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
export function useLiveMonitor(options: {
|
|
123
|
+
enabled: boolean;
|
|
124
|
+
pollInterval: number;
|
|
125
|
+
onNewBeat: (beat: EntityRecord) => void;
|
|
126
|
+
}) {
|
|
127
|
+
const [lastBeatCount, setLastBeatCount] = useState(0);
|
|
128
|
+
const [isLive, setIsLive] = useState(false);
|
|
129
|
+
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (!options.enabled) return;
|
|
132
|
+
|
|
133
|
+
const interval = setInterval(async () => {
|
|
134
|
+
const response = await fetch('/api/watch');
|
|
135
|
+
const { beatCount } = await response.json();
|
|
136
|
+
|
|
137
|
+
if (beatCount > lastBeatCount) {
|
|
138
|
+
setIsLive(true);
|
|
139
|
+
// Trigger reload and notify about new beats
|
|
140
|
+
const newBeatsCount = beatCount - lastBeatCount;
|
|
141
|
+
// Reload data and call onNewBeat for each new beat
|
|
142
|
+
setLastBeatCount(beatCount);
|
|
143
|
+
}
|
|
144
|
+
}, options.pollInterval);
|
|
145
|
+
|
|
146
|
+
return () => clearInterval(interval);
|
|
147
|
+
}, [options.enabled, options.pollInterval, lastBeatCount]);
|
|
148
|
+
|
|
149
|
+
return { isLive };
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### 2.2. Audio Player Component
|
|
154
|
+
**File:** `components/audio-player.tsx`
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
export function AudioPlayer({
|
|
158
|
+
audioFiles,
|
|
159
|
+
autoPlay = false,
|
|
160
|
+
currentBeatIndex
|
|
161
|
+
}: AudioPlayerProps) {
|
|
162
|
+
const audioRef = useRef<HTMLAudioElement>(null);
|
|
163
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
164
|
+
const [currentTrack, setCurrentTrack] = useState(0);
|
|
165
|
+
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (autoPlay && audioRef.current && currentTrack < audioFiles.length) {
|
|
168
|
+
audioRef.current.play();
|
|
169
|
+
}
|
|
170
|
+
}, [currentTrack, autoPlay]);
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<div className="audio-player">
|
|
174
|
+
<audio
|
|
175
|
+
ref={audioRef}
|
|
176
|
+
src={`/api/audio/${audioFiles[currentTrack]}`}
|
|
177
|
+
onEnded={() => setCurrentTrack(prev => prev + 1)}
|
|
178
|
+
onPlay={() => setIsPlaying(true)}
|
|
179
|
+
onPause={() => setIsPlaying(false)}
|
|
180
|
+
/>
|
|
181
|
+
{/* Playback controls */}
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### 2.3. Timeline Component
|
|
188
|
+
**File:** `components/narrative-timeline.tsx`
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
export function NarrativeTimeline({ beats }: { beats: EntityRecord[] }) {
|
|
192
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
193
|
+
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
// Auto-scroll to latest beat
|
|
196
|
+
if (containerRef.current) {
|
|
197
|
+
containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
|
198
|
+
}
|
|
199
|
+
}, [beats.length]);
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<div ref={containerRef} className="timeline-container">
|
|
203
|
+
{beats.map((beat, idx) => (
|
|
204
|
+
<TimelineBeat
|
|
205
|
+
key={beat.name}
|
|
206
|
+
beat={beat}
|
|
207
|
+
index={idx}
|
|
208
|
+
isLatest={idx === beats.length - 1}
|
|
209
|
+
/>
|
|
210
|
+
))}
|
|
211
|
+
</div>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
#### 2.4. Live Indicator
|
|
217
|
+
**File:** `components/live-indicator.tsx`
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
export function LiveIndicator({ isLive }: { isLive: boolean }) {
|
|
221
|
+
return (
|
|
222
|
+
<div className="flex items-center gap-2">
|
|
223
|
+
<div className={`w-2 h-2 rounded-full ${
|
|
224
|
+
isLive ? 'bg-red-500 animate-pulse' : 'bg-gray-400'
|
|
225
|
+
}`} />
|
|
226
|
+
<span className="text-sm font-medium">
|
|
227
|
+
{isLive ? 'LIVE' : 'OFFLINE'}
|
|
228
|
+
</span>
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Phase 3: Enhanced Page Component
|
|
235
|
+
|
|
236
|
+
#### 3.1. Live Mode Page
|
|
237
|
+
**File:** `app/live/page.tsx`
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
"use client"
|
|
241
|
+
|
|
242
|
+
export default function LivePage() {
|
|
243
|
+
const [beats, setBeats] = useState<EntityRecord[]>([]);
|
|
244
|
+
const [audioQueue, setAudioQueue] = useState<string[]>([]);
|
|
245
|
+
const isLiveMode = process.env.NEXT_PUBLIC_LIVE_MODE === 'true';
|
|
246
|
+
const pollInterval = parseInt(process.env.NEXT_PUBLIC_POLL_INTERVAL || '2000');
|
|
247
|
+
|
|
248
|
+
const { isLive } = useLiveMonitor({
|
|
249
|
+
enabled: isLiveMode,
|
|
250
|
+
pollInterval,
|
|
251
|
+
onNewBeat: (beat) => {
|
|
252
|
+
setBeats(prev => [...prev, beat]);
|
|
253
|
+
// Extract audio timestamp from beat metadata
|
|
254
|
+
const timestamp = beat.metadata.timestamp;
|
|
255
|
+
if (timestamp) {
|
|
256
|
+
setAudioQueue(prev => [...prev, timestamp]);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<div className="min-h-screen bg-background">
|
|
263
|
+
<header className="border-b sticky top-0 z-10 bg-card">
|
|
264
|
+
<div className="container mx-auto px-4 py-4">
|
|
265
|
+
<div className="flex items-center justify-between">
|
|
266
|
+
<h1 className="text-2xl font-bold">
|
|
267
|
+
🎬 Live Narrative Observer
|
|
268
|
+
</h1>
|
|
269
|
+
<LiveIndicator isLive={isLive} />
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
</header>
|
|
273
|
+
|
|
274
|
+
<main className="container mx-auto px-4 py-8">
|
|
275
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
276
|
+
{/* Timeline */}
|
|
277
|
+
<div className="lg:col-span-1">
|
|
278
|
+
<NarrativeTimeline beats={beats} />
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
{/* Beat Details */}
|
|
282
|
+
<div className="lg:col-span-2">
|
|
283
|
+
<NarrativeBeats beats={beats} />
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
</main>
|
|
287
|
+
|
|
288
|
+
{/* Audio Player */}
|
|
289
|
+
<AudioPlayer
|
|
290
|
+
audioFiles={audioQueue}
|
|
291
|
+
autoPlay={true}
|
|
292
|
+
currentBeatIndex={beats.length - 1}
|
|
293
|
+
/>
|
|
294
|
+
</div>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Usage Examples
|
|
300
|
+
|
|
301
|
+
### Example 1: Basic Live Mode
|
|
302
|
+
```bash
|
|
303
|
+
# Terminal 1: Start observer with audio
|
|
304
|
+
cd /a/src/_sessiondata/55158baa-dea7-45c8-9941-540433cfe551
|
|
305
|
+
./scripts/stc_observer.sh --read
|
|
306
|
+
|
|
307
|
+
# Terminal 2: Start visualizer in live mode
|
|
308
|
+
cd /src/coaia-visualizer
|
|
309
|
+
npx coaia-visualizer --live \
|
|
310
|
+
--memory-path /home/mia/workspace/stcmastery-copilot-acp-55158baa-dea7-45c8-9941-540433cfe551--2602072108/.coaia/stcmastery-copilot-acp-55158baa-dea7-45c8-9941-540433cfe551--2602072108.jsonl \
|
|
311
|
+
--audio-dir /a/src/_sessiondata/55158baa-dea7-45c8-9941-540433cfe551/audio \
|
|
312
|
+
--auto-play
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Example 2: With Environment Variables
|
|
316
|
+
```bash
|
|
317
|
+
export COAIAV_LIVE=true
|
|
318
|
+
export COAIAV_POLL_INTERVAL=3000
|
|
319
|
+
export COAIAV_AUTO_PLAY=true
|
|
320
|
+
export COAIAN_MF=/home/mia/workspace/stcmastery-copilot-acp-55158baa-dea7-45c8-9941-540433cfe551--2602072108/.coaia/stcmastery-copilot-acp-55158baa-dea7-45c8-9941-540433cfe551--2602072108.jsonl
|
|
321
|
+
export COAIAV_AUDIO_DIR=/a/src/_sessiondata/55158baa-dea7-45c8-9941-540433cfe551/audio
|
|
322
|
+
|
|
323
|
+
npx coaia-visualizer
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Example 3: Launch Script
|
|
327
|
+
**File:** `/src/coaia-visualizer/launch-live.sh`
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
#!/bin/bash
|
|
331
|
+
|
|
332
|
+
SESSION_ID="55158baa-dea7-45c8-9941-540433cfe551"
|
|
333
|
+
SESSION_DIR="/a/src/_sessiondata/${SESSION_ID}"
|
|
334
|
+
WORKSPACE_DIR="/home/mia/workspace/stcmastery-copilot-acp-${SESSION_ID}--2602072108"
|
|
335
|
+
|
|
336
|
+
# Start observer in background
|
|
337
|
+
cd "$SESSION_DIR"
|
|
338
|
+
./scripts/stc_observer.sh --read &
|
|
339
|
+
OBSERVER_PID=$!
|
|
340
|
+
|
|
341
|
+
# Start visualizer in live mode
|
|
342
|
+
cd /src/coaia-visualizer
|
|
343
|
+
COAIAV_LIVE=true \
|
|
344
|
+
COAIAV_AUTO_PLAY=true \
|
|
345
|
+
COAIAN_MF="${WORKSPACE_DIR}/.coaia/stcmastery-copilot-acp-${SESSION_ID}--2602072108.jsonl" \
|
|
346
|
+
COAIAV_AUDIO_DIR="${SESSION_DIR}/audio" \
|
|
347
|
+
npx coaia-visualizer
|
|
348
|
+
|
|
349
|
+
# Cleanup
|
|
350
|
+
kill $OBSERVER_PID
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Features Roadmap
|
|
354
|
+
|
|
355
|
+
### Phase 1 (Core) ✨
|
|
356
|
+
- [ ] File polling mechanism
|
|
357
|
+
- [ ] Live mode flag in CLI
|
|
358
|
+
- [ ] Auto-reload on JSONL changes
|
|
359
|
+
- [ ] Audio player component
|
|
360
|
+
- [ ] Auto-play integration
|
|
361
|
+
|
|
362
|
+
### Phase 2 (Enhanced) 🎨
|
|
363
|
+
- [ ] Timeline visualization
|
|
364
|
+
- [ ] Beat appearance animations
|
|
365
|
+
- [ ] Auto-scroll to latest
|
|
366
|
+
- [ ] Live indicator with pulse
|
|
367
|
+
- [ ] Playback controls
|
|
368
|
+
|
|
369
|
+
### Phase 3 (Advanced) 🚀
|
|
370
|
+
- [ ] Multi-session observer (watch multiple sessions)
|
|
371
|
+
- [ ] WebSocket real-time updates (no polling)
|
|
372
|
+
- [ ] Audio waveform visualization
|
|
373
|
+
- [ ] Beat search/filter
|
|
374
|
+
- [ ] Export timeline as video
|
|
375
|
+
|
|
376
|
+
### Phase 4 (Immersive) 🌌
|
|
377
|
+
- [ ] 3D narrative space visualization
|
|
378
|
+
- [ ] VR/AR mode for narrative exploration
|
|
379
|
+
- [ ] Collaborative real-time viewing
|
|
380
|
+
- [ ] AI-generated beat summaries
|
|
381
|
+
- [ ] Narrative pattern recognition
|
|
382
|
+
|
|
383
|
+
## Technical Considerations
|
|
384
|
+
|
|
385
|
+
### Performance
|
|
386
|
+
- Polling every 2s is acceptable for local files
|
|
387
|
+
- Consider debouncing rapid consecutive changes
|
|
388
|
+
- Lazy-load audio files (stream, don't buffer all)
|
|
389
|
+
|
|
390
|
+
### Audio Synchronization
|
|
391
|
+
- Queue management: FIFO for auto-play
|
|
392
|
+
- Handle concurrent audio generation
|
|
393
|
+
- Graceful degradation if audio missing
|
|
394
|
+
|
|
395
|
+
### State Management
|
|
396
|
+
- Use React Context for live mode state
|
|
397
|
+
- Local storage for user preferences (volume, auto-play)
|
|
398
|
+
- Session storage for current playback position
|
|
399
|
+
|
|
400
|
+
### Accessibility
|
|
401
|
+
- Keyboard controls for audio (space = play/pause)
|
|
402
|
+
- Screen reader announcements for new beats
|
|
403
|
+
- Visual indicators for audio playback state
|
|
404
|
+
|
|
405
|
+
## Integration Points
|
|
406
|
+
|
|
407
|
+
### With stc_observer.sh
|
|
408
|
+
- Observer writes to JSONL
|
|
409
|
+
- Generates timestamped audio files
|
|
410
|
+
- Visualizer reads both sources
|
|
411
|
+
|
|
412
|
+
### With coaia-narrative MCP
|
|
413
|
+
- Shared JSONL format
|
|
414
|
+
- Compatible with existing tools
|
|
415
|
+
- Can edit via visualizer, view via MCP
|
|
416
|
+
|
|
417
|
+
### With Session Management
|
|
418
|
+
- Multiple session directories
|
|
419
|
+
- Session selector in UI
|
|
420
|
+
- Parallel session monitoring
|
|
421
|
+
|
|
422
|
+
## Error Handling
|
|
423
|
+
|
|
424
|
+
- JSONL parse errors → Show error banner, keep previous state
|
|
425
|
+
- Audio file not found → Silent skip, visual indicator
|
|
426
|
+
- Network/file system errors → Retry with exponential backoff
|
|
427
|
+
- Malformed beat data → Log, skip, continue
|
|
428
|
+
|
|
429
|
+
## Success Metrics
|
|
430
|
+
|
|
431
|
+
- ✅ Zero manual reloads needed
|
|
432
|
+
- ✅ Audio plays within 3s of beat creation
|
|
433
|
+
- ✅ Smooth animations at 60fps
|
|
434
|
+
- ✅ No memory leaks during extended monitoring
|
|
435
|
+
- ✅ Intuitive UX for narrative flow comprehension
|