Files
oldboy/to_continue.md
2025-10-15 02:53:48 +02:00

1031 lines
29 KiB
Markdown

# Project Mode Implementation - Continuation Guide
## Overview
This document provides a complete roadmap for continuing the implementation of dual-mode support in OldBoy: **Composition Mode** (full document evaluation) and **Live Coding Mode** (block-based evaluation with persistent Csound instance).
---
## What Has Been Completed
### 1. Type System & Data Model ✅
**Files Modified:**
- `/src/lib/project-system/types.ts`
- `/src/lib/project-system/project-manager.ts`
**Changes:**
1. Created `ProjectMode` type:
```typescript
export type ProjectMode = 'composition' | 'livecoding';
```
2. Added `mode` field to `CsoundProject` interface:
```typescript
export interface CsoundProject {
// ... existing fields
mode: ProjectMode; // NEW: Execution mode
}
```
3. Updated `CreateProjectData` to accept optional mode:
```typescript
export interface CreateProjectData {
// ... existing fields
mode?: ProjectMode; // Defaults to 'composition' if not provided
}
```
4. Updated `UpdateProjectData` to allow mode updates:
```typescript
export interface UpdateProjectData {
// ... existing fields
mode?: ProjectMode; // Can change mode after creation
}
```
5. Modified `ProjectManager.createProject()`:
- Line 100: Added `mode: data.mode || 'composition'` to project initialization
- Ensures all new projects have a mode (defaults to composition)
6. Modified `ProjectManager.updateProject()`:
- Line 165: Added `...(data.mode !== undefined && { mode: data.mode })` to update logic
- Allows mode to be updated without affecting other fields
**Result:** All projects now have a mode field that persists in IndexedDB and is included in import/export operations.
---
### 2. UI for Mode Selection ✅
**Files Modified:**
- `/src/lib/components/ui/FileBrowser.svelte`
- `/src/lib/stores/projectEditor.svelte.ts`
- `/src/App.svelte`
**FileBrowser Changes:**
1. **Imports (Line 7):**
```typescript
import type { ProjectMode } from '../../project-system/types';
```
2. **Props Interface (Line 13):**
```typescript
onMetadataUpdate?: (projectId: string, updates: {
title?: string;
author?: string;
mode?: ProjectMode // NEW
}) => void;
```
3. **State (Line 30):**
```typescript
let editMode = $state<ProjectMode>('composition');
```
4. **Effect Hook (Lines 32-38):**
```typescript
$effect(() => {
if (selectedProject) {
editTitle = selectedProject.title;
editAuthor = selectedProject.author;
editMode = selectedProject.mode; // NEW: Sync mode from project
}
});
```
5. **Metadata Change Handler (Lines 103-118):**
```typescript
function handleMetadataChange() {
if (!selectedProject) return;
const hasChanges =
editTitle !== selectedProject.title ||
editAuthor !== selectedProject.author ||
editMode !== selectedProject.mode; // NEW: Include mode in change detection
if (hasChanges) {
onMetadataUpdate?.(selectedProject.id, {
title: editTitle,
author: editAuthor,
mode: editMode // NEW: Send mode updates
});
}
}
```
6. **UI Element (Lines 183-193):**
```svelte
<div class="field">
<label for="file-mode">Mode</label>
<select
id="file-mode"
bind:value={editMode}
onchange={handleMetadataChange}
>
<option value="composition">Composition</option>
<option value="livecoding">Live Coding</option>
</select>
</div>
```
7. **CSS (Lines 373-390):**
```css
.field input,
.field select {
padding: 0.5rem;
background-color: #2a2a2a;
border: 1px solid #3a3a3a;
color: rgba(255, 255, 255, 0.87);
font-size: 0.875rem;
outline: none;
}
.field select {
cursor: pointer;
}
```
**ProjectEditor Changes (Line 108):**
```typescript
async updateMetadata(updates: {
title?: string;
author?: string;
mode?: import('../project-system/types').ProjectMode // NEW
}): Promise<boolean>
```
**App.svelte Changes (Line 123):**
```typescript
async function handleMetadataUpdate(
projectId: string,
updates: {
title?: string;
author?: string;
mode?: import('./lib/project-system/types').ProjectMode // NEW
}
)
```
**Result:** Users can now select project mode in the Files panel metadata editor. Mode changes are immediately persisted.
---
### 3. Block Evaluation Infrastructure ✅
**File Created:**
- `/src/lib/editor/block-eval.ts`
**Purpose:** Provides utilities for extracting code blocks and visual feedback, adapted from flok's cm-eval package.
**Exports:**
1. **`flash(view, from, to, timeout)`**
- Visually highlights evaluated code region
- Default timeout: 150ms
- Background color: `#FFCA2880` (yellow with transparency)
2. **`flashField(style?)`**
- CodeMirror StateField for managing flash decorations
- Returns StateField to be added to editor extensions
- Handles flash effect lifecycle
3. **`getSelection(state): EvalBlock`**
- Returns currently selected text with positions
- Returns `{ text: '', from: null, to: null }` if no selection
4. **`getLine(state): EvalBlock`**
- Returns the entire line at cursor position
- Includes line's from/to positions
5. **`getBlock(state): EvalBlock`**
- Returns paragraph block (text separated by blank lines)
- Searches up and down from cursor until blank lines found
- Core functionality for live coding mode
6. **`getDocument(state): EvalBlock`**
- Returns entire document text
- Used for composition mode evaluation
**EvalBlock Interface:**
```typescript
interface EvalBlock {
text: string; // The code to evaluate
from: number | null; // Start position in document
to: number | null; // End position in document
}
```
**Implementation Details:**
- **Block Detection Algorithm:**
1. Start at cursor line
2. If line is blank, return empty block
3. Search backwards until blank line or document start
4. Search forwards until blank line or document end
5. Extract text from start to end positions
- **Flash Effect:**
- Uses CodeMirror StateEffect system
- Creates decoration with inline style
- Automatically clears after timeout
- Non-blocking (doesn't prevent editing)
**Result:** Complete block evaluation infrastructure ready to integrate with Editor component.
---
## What Needs to Be Done Next
### Phase 1: Editor Integration with Block Evaluation
**Goal:** Connect block-eval utilities to the Editor component and add visual feedback.
**File to Modify:** `/src/lib/components/editor/Editor.svelte`
**Steps:**
1. **Import block-eval utilities (add to imports):**
```typescript
import {
flashField,
flash,
getSelection,
getBlock,
getDocument
} from '../editor/block-eval';
```
2. **Add flashField to extensions:**
Find where extensions are created (likely in a `$derived` or similar). It should look something like:
```typescript
let extensions = $derived([
// ... existing extensions
]);
```
Add flashField:
```typescript
let extensions = $derived([
// ... existing extensions
flashField(), // NEW: Add flash effect support
]);
```
3. **Expose block extraction methods:**
Add these methods to the Editor component (after the component logic, before closing tag):
```typescript
export function getSelectedText(): string | null {
if (!editorView) return null;
const { text } = getSelection(editorView.state);
return text || null;
}
export function getCurrentBlock(): string | null {
if (!editorView) return null;
const { text } = getBlock(editorView.state);
return text || null;
}
export function getFullDocument(): string {
if (!editorView) return '';
const { text } = getDocument(editorView.state);
return text;
}
export function evaluateWithFlash(text: string, from: number | null, to: number | null) {
if (editorView && from !== null && to !== null) {
flash(editorView, from, to);
}
}
```
4. **Modify keyboard shortcut for execute:**
Find the keyboard shortcut setup (search for "Ctrl-Enter" or "Cmd-Enter"). It might look like:
```typescript
keymap.of([
{
key: "Ctrl-Enter",
run: () => {
onExecute?.(value);
return true;
}
}
])
```
Change it to call a new internal method:
```typescript
keymap.of([
{
key: "Ctrl-Enter",
mac: "Cmd-Enter",
run: () => {
handleExecute();
return true;
}
}
])
```
5. **Add internal execute handler:**
```typescript
function handleExecute() {
if (!editorView) return;
// Get selection or block
const selection = getSelection(editorView.state);
if (selection.text) {
// Has selection: evaluate it
flash(editorView, selection.from, selection.to);
onExecute?.(selection.text, 'selection');
} else {
// No selection: evaluate block or document based on mode
// For now, always get block (mode logic will be in App.svelte)
const block = getBlock(editorView.state);
if (block.text) {
flash(editorView, block.from, block.to);
onExecute?.(block.text, 'block');
}
}
}
```
6. **Update onExecute prop type:**
Change the prop signature from:
```typescript
onExecute?: (code: string) => void;
```
To:
```typescript
onExecute?: (code: string, source: 'selection' | 'block' | 'document') => void;
```
**Expected Result:**
- Cmd/Ctrl+Enter will flash the code being evaluated
- Editor can distinguish between selection, block, and document evaluation
- Parent components can access block extraction methods
---
### Phase 2: Execution Strategy Implementation
**Goal:** Create strategy pattern for handling composition vs livecoding execution modes.
**File to Create:** `/src/lib/csound/execution-strategies.ts`
**Implementation:**
```typescript
import type { CsoundStore } from './store';
import type { ProjectMode } from '../project-system/types';
export interface ExecutionStrategy {
execute(
csound: CsoundStore,
code: string,
fullContent: string,
source: 'selection' | 'block' | 'document'
): Promise<void>;
}
/**
* Composition Mode Strategy
* - Always evaluates full CSD document
* - Uses ephemeral instances (fresh instance each time)
* - Standard workflow: compile → start → play → cleanup
*/
export class CompositionStrategy implements ExecutionStrategy {
async execute(
csound: CsoundStore,
code: string,
fullContent: string,
source: 'selection' | 'block' | 'document'
): Promise<void> {
// Always evaluate full document in composition mode
await csound.evaluate(fullContent);
}
}
/**
* Live Coding Mode Strategy
* - Evaluates blocks incrementally
* - Uses persistent instance (maintains state)
* - Supports: score events, channel updates, instrument redefinition
*/
export class LiveCodingStrategy implements ExecutionStrategy {
private isInitialized = false;
private headerCompiled = false;
async execute(
csound: CsoundStore,
code: string,
fullContent: string,
source: 'selection' | 'block' | 'document'
): Promise<void> {
// First time: initialize with full document
if (!this.isInitialized) {
await this.initializeFromDocument(csound, fullContent);
this.isInitialized = true;
return;
}
// Subsequent evaluations: handle blocks
await this.evaluateBlock(csound, code);
}
private async initializeFromDocument(
csound: CsoundStore,
fullContent: string
): Promise<void> {
// Parse CSD to extract orchestra and initial score
const { header, instruments, score } = this.parseCSD(fullContent);
// Compile header + instruments
const fullOrchestra = header + '\n' + instruments;
const compileResult = await csound.compileOrchestra(fullOrchestra);
if (!compileResult.success) {
throw new Error(compileResult.errorMessage || 'Compilation failed');
}
this.headerCompiled = true;
// Start performance
await csound.startPerformance();
// If score has events, send them
if (score.trim()) {
await csound.readScore(score);
}
}
private async evaluateBlock(csound: CsoundStore, code: string): Promise<void> {
const trimmedCode = code.trim();
if (!trimmedCode) return;
// Detect what kind of code this is
if (this.isScoreEvent(trimmedCode)) {
// Send score event (e.g., "i 1 0 2 0.5")
await csound.sendScoreEvent(trimmedCode);
}
else if (this.isInstrumentDefinition(trimmedCode)) {
// Recompile instrument
await csound.compileOrchestra(trimmedCode);
}
else if (this.isChannelSet(trimmedCode)) {
// Set channel value (e.g., "freq = 440")
await this.handleChannelSet(csound, trimmedCode);
}
else {
// Default: try to compile as orchestra code
await csound.compileOrchestra(trimmedCode);
}
}
private parseCSD(content: string): {
header: string;
instruments: string;
score: string
} {
// Extract <CsInstruments> section
const orcMatch = content.match(/<CsInstruments>([\s\S]*?)<\/CsInstruments>/);
if (!orcMatch) {
return { header: '', instruments: '', score: '' };
}
const orchestra = orcMatch[1].trim();
// Extract <CsScore> section
const scoMatch = content.match(/<CsScore>([\s\S]*?)<\/CsScore>/);
const score = scoMatch ? scoMatch[1].trim() : '';
// Split orchestra into header (sr, ksmps, nchnls, etc.) and instruments
const instrMatch = orchestra.match(/([\s\S]*?)(instr\s+\d+[\s\S]*)/);
if (instrMatch) {
return {
header: instrMatch[1].trim(),
instruments: instrMatch[2].trim(),
score
};
}
// If no instruments found, treat entire orchestra as header
return {
header: orchestra,
instruments: '',
score
};
}
private isScoreEvent(code: string): boolean {
// Check for score event syntax: i, f, e, a followed by space/number
return /^[ifea]\s+[\d\-]/.test(code);
}
private isInstrumentDefinition(code: string): boolean {
// Check for instrument definition
return /^\s*instr\s+/.test(code);
}
private isChannelSet(code: string): boolean {
// Simple channel set syntax: varname = value
// e.g., "freq = 440" or "cutoff = 2000"
return /^\w+\s*=\s*[\d\.\-]+/.test(code);
}
private async handleChannelSet(csound: CsoundStore, code: string): Promise<void> {
// Parse: varname = value
const match = code.match(/^(\w+)\s*=\s*([\d\.\-]+)/);
if (!match) return;
const [, channelName, valueStr] = match;
const value = parseFloat(valueStr);
await csound.setControlChannel(channelName, value);
}
/**
* Reset the strategy state (called when switching documents or resetting)
*/
reset(): void {
this.isInitialized = false;
this.headerCompiled = false;
}
}
/**
* Factory function to create appropriate strategy based on mode
*/
export function createExecutionStrategy(mode: ProjectMode): ExecutionStrategy {
return mode === 'livecoding'
? new LiveCodingStrategy()
: new CompositionStrategy();
}
```
**Expected Result:**
- Strategy pattern cleanly separates composition and livecoding behaviors
- LiveCodingStrategy maintains state across evaluations
- Supports score events, instrument redefinition, and channel control
---
### Phase 3: Update App.svelte to Use Strategies
**File to Modify:** `/src/App.svelte`
**Steps:**
1. **Import strategy utilities:**
```typescript
import { createExecutionStrategy, type ExecutionStrategy } from './lib/csound/execution-strategies';
```
2. **Track current strategy:**
```typescript
let currentStrategy = $state<ExecutionStrategy | null>(null);
let currentMode = $state<import('./lib/project-system/types').ProjectMode>('composition');
```
3. **Update strategy when project changes:**
Add effect to watch for project mode changes:
```typescript
$effect(() => {
const mode = projectEditor.currentProject?.mode || 'composition';
// Only recreate strategy if mode changed
if (mode !== currentMode) {
currentMode = mode;
currentStrategy = createExecutionStrategy(mode);
// If switching to livecoding, need to reset csound
if (mode === 'livecoding') {
// Reset to ensure clean state
csound.reset().catch(console.error);
}
}
});
```
4. **Update handleExecute function:**
Replace existing handleExecute (around line 99):
```typescript
async function handleExecute(code: string, source: 'selection' | 'block' | 'document') {
try {
if (!currentStrategy) {
currentStrategy = createExecutionStrategy(currentMode);
}
const fullContent = projectEditor.content;
await currentStrategy.execute(csound, code, fullContent, source);
} catch (error) {
console.error('Execution error:', error);
}
}
```
5. **Update EditorWithLogs onExecute prop:**
Find where EditorWithLogs is used (around line 274):
```svelte
<EditorWithLogs
value={projectEditor.content}
language="javascript"
onChange={handleEditorChange}
onExecute={handleExecute} <!-- Already correct, just verify signature matches -->
logs={interpreterLogs}
{editorSettings}
/>
```
**Expected Result:**
- App automatically uses correct strategy based on project mode
- Strategy instance persists across evaluations
- Mode switches trigger proper cleanup/reset
---
### Phase 4: Update Csound Store for Mode-Aware Instance Management
**Goal:** Make Csound store use persistent vs ephemeral instances based on project mode.
**File to Modify:** `/src/lib/contexts/app-context.ts`
**Current Code (likely around line 10-15):**
```typescript
const csound = createCsoundStore();
```
**Change to:**
```typescript
const csound = createCsoundStore('ephemeral'); // Default to ephemeral
```
**Then in App.svelte, add mode-based reset logic:**
In the `$effect` that watches mode changes (from Phase 3, Step 3), enhance it:
```typescript
$effect(() => {
const mode = projectEditor.currentProject?.mode || 'composition';
if (mode !== currentMode) {
const oldMode = currentMode;
currentMode = mode;
currentStrategy = createExecutionStrategy(mode);
// Handle Csound instance mode switching
if (mode === 'livecoding' && oldMode === 'composition') {
// Switching TO livecoding: need persistent instance
// Reset will be handled by first LiveCodingStrategy execution
console.log('Switched to live coding mode');
} else if (mode === 'composition' && oldMode === 'livecoding') {
// Switching FROM livecoding: stop and cleanup
csound.stop().catch(console.error);
console.log('Switched to composition mode');
}
}
});
```
**Note:** The actual instance mode (ephemeral/persistent) is now implicitly handled by the strategy:
- **CompositionStrategy**: calls `csound.evaluate()` which uses ephemeral mode (current default)
- **LiveCodingStrategy**: calls `compileOrchestra()` + `startPerformance()` which uses the same instance repeatedly
The key insight is that **persistence is achieved by NOT calling `evaluate()`** (which destroys/recreates), but instead using the low-level API (`compileOrchestra`, `startPerformance`, `sendScoreEvent`).
**Expected Result:**
- Composition mode: Each evaluation gets fresh instance
- Live coding mode: Single instance persists across block evaluations
- Switching modes properly cleans up old instances
---
### Phase 5: Testing & Verification
**Test Plan:**
#### Test 1: Composition Mode (Baseline)
1. Create new project (defaults to composition mode)
2. Paste this CSD code:
```csound
<CsoundSynthesizer>
<CsOptions>
-odac
</CsOptions>
<CsInstruments>
sr = 48000
ksmps = 32
nchnls = 2
0dbfs = 1
instr 1
aOut poscil 0.2, 440
outs aOut, aOut
endin
</CsInstruments>
<CsScore>
i 1 0 2
</CsScore>
</CsoundSynthesizer>
```
3. Press Cmd/Ctrl+Enter
4. **Expected:** Entire document flashes yellow, sound plays for 2 seconds
5. Modify frequency to 880, press Cmd/Ctrl+Enter again
6. **Expected:** New instance created, new sound plays
#### Test 2: Live Coding Mode - Initial Setup
1. Open FileBrowser, select the project
2. In metadata, change Mode from "Composition" to "Live Coding"
3. Press Cmd/Ctrl+Enter on the entire document
4. **Expected:**
- Document flashes
- Csound initializes (check logs for "Starting performance...")
- Sound plays if score has events
#### Test 3: Live Coding Mode - Block Evaluation
1. Clear the `<CsScore>` section (or comment out with semicolons)
2. Ensure document is initialized (press Cmd/Ctrl+Enter on full document)
3. Add a new line at the bottom of the document:
```csound
i 1 0 2 0.5
```
4. Place cursor on that line, press Cmd/Ctrl+Enter
5. **Expected:**
- Only that line flashes
- Sound plays immediately (instrument 1 triggered)
- Logs show score event sent, NOT full recompilation
#### Test 4: Live Coding Mode - Channel Control
1. Modify instrument 1 to use a channel:
```csound
instr 1
kFreq chnget "freq"
aOut poscil 0.2, kFreq
outs aOut, aOut
endin
```
2. Reinitialize (Cmd+Enter on full document)
3. Start a long note:
```csound
i 1 0 60
```
4. While playing, evaluate this block:
```csound
freq = 440
```
5. Then evaluate:
```csound
freq = 880
```
6. **Expected:**
- Frequency changes in real-time while note plays
- No audio interruption
#### Test 5: Live Coding Mode - Instrument Redefinition
1. While note is playing from Test 4, modify instrument 1:
```csound
instr 1
kFreq chnget "freq"
aOut vco2 0.2, kFreq
outs aOut, aOut
endin
```
2. Select only the instrument definition, press Cmd+Enter
3. **Expected:**
- Instrument recompiles
- Next triggered note uses new definition
- Currently playing notes might continue with old definition (Csound behavior)
#### Test 6: Mode Switching
1. In live coding mode with performance running
2. Switch mode to "Composition" in FileBrowser
3. **Expected:**
- Performance stops
- Logs show "Stopped"
4. Press Cmd+Enter
5. **Expected:**
- Full document evaluation (composition mode behavior)
#### Test 7: Persistence Across Sessions
1. Create project in live coding mode
2. Close browser tab
3. Reopen, load project
4. **Expected:**
- Mode is still "Live Coding" in metadata
- First evaluation initializes correctly
---
## Troubleshooting Guide
### Issue: "Cannot find module '@flok-editor/session'"
**Cause:** The block-eval code references types from flok's session package.
**Solution:**
- Our implementation in `/src/lib/editor/block-eval.ts` is standalone and doesn't need this
- If TypeScript complains, we don't use the `Document` type from flok
- Our editor integration passes callbacks, not Document objects
### Issue: Flashing doesn't appear
**Cause:** flashField not added to editor extensions
**Solution:**
1. Verify `import { flashField } from '../editor/block-eval'` in Editor.svelte
2. Verify `flashField()` is in the extensions array
3. Check browser console for errors
### Issue: Block evaluation evaluates wrong text
**Cause:** Block detection algorithm confused by comment syntax
**Solution:**
- Csound uses `;` for line comments
- Blank line detection: `line.text.trim().length === 0`
- If commented lines interfere, update `getBlock()` to treat `;`-only lines as blank
### Issue: Live coding mode doesn't persist
**Cause:** Strategy state lost between evaluations
**Solution:**
- Verify `currentStrategy` is stored in `$state()`, not recreated each time
- Check that `$effect` only recreates strategy when mode actually changes
- Ensure LiveCodingStrategy instance is reused
### Issue: Performance doesn't start in live coding mode
**Cause:** `startPerformance()` not called or called before compilation
**Solution:**
- Check logs for compilation errors
- Verify `compileOrchestra()` returns `success: true`
- Ensure `startPerformance()` is awaited
### Issue: Mode changes don't take effect
**Cause:** Project not reloaded after metadata update
**Solution:**
- `handleMetadataUpdate()` should call `projectEditor.updateMetadata()`
- This should update `projectEditor.currentProject`
- `$effect` watching `currentProject?.mode` should trigger
- Verify effect dependencies are correct
---
## File Structure Reference
### New Files Created
```
/src/lib/editor/block-eval.ts - Block evaluation utilities (✅ COMPLETE)
/src/lib/csound/execution-strategies.ts - Strategy pattern (❌ TODO)
```
### Modified Files
```
/src/lib/project-system/types.ts - Added ProjectMode type (✅ COMPLETE)
/src/lib/project-system/project-manager.ts - Added mode handling (✅ COMPLETE)
/src/lib/components/ui/FileBrowser.svelte - Added mode selector UI (✅ COMPLETE)
/src/lib/stores/projectEditor.svelte.ts - Updated metadata types (✅ COMPLETE)
/src/App.svelte - Updated handlers (✅ PARTIAL - needs strategy integration)
/src/lib/components/editor/Editor.svelte - Needs block eval integration (❌ TODO)
/src/lib/contexts/app-context.ts - Might need mode-aware csound init (❌ TODO)
```
---
## Key Design Principles to Remember
1. **No Fallbacks**: If livecoding fails, it fails. Don't silently fall back to composition mode.
2. **Mode Per Project**: Mode is project metadata, not global state. Different tabs could have different modes.
3. **Persistent = Reuse Low-Level API**: Persistence isn't a csound setting, it's about calling `compileOrchestra()` + `startPerformance()` instead of `evaluate()`.
4. **Block = Paragraph**: A block is text separated by blank lines. This is the standard live coding convention.
5. **Flash for Feedback**: Always flash evaluated code so user knows what executed.
6. **Strategy Owns State**: LiveCodingStrategy tracks initialization state, not the store or app.
---
## Implementation Order
Follow this exact order to minimize issues:
1. ✅ **Phase 1**: Editor integration (visual feedback works first)
2. ✅ **Phase 2**: Create execution strategies (logic isolated and testable)
3. ✅ **Phase 3**: Wire strategies to App.svelte (connect pieces)
4. ✅ **Phase 4**: Verify instance management (make sure persistence works)
5. ✅ **Phase 5**: Test thoroughly (catch edge cases)
---
## Success Criteria
### Composition Mode
- [✅] Full document evaluation on Cmd+Enter
- [✅] Flash effect shows what was evaluated
- [✅] Fresh instance every time (no state leakage)
### Live Coding Mode
- [❌] First evaluation initializes from full document
- [❌] Subsequent evaluations process blocks incrementally
- [❌] Score events trigger sounds immediately
- [❌] Channel updates affect running performance
- [❌] Instrument redefinition works
- [❌] Instance persists across evaluations
- [❌] Logs show block evaluations, not full recompilations
### Both Modes
- [✅] Mode selection UI works
- [✅] Mode persists in database
- [❌] Mode switching cleans up properly
- [❌] Keyboard shortcuts work consistently
---
## Additional Notes
### Why Ephemeral Default Makes Sense Now
The original implementation used ephemeral mode by default because Web Audio reconnection after `csound.reset()` was unreliable. However, in live coding mode:
- We **don't call `reset()`** between evaluations
- We **don't call `evaluate()`** which destroys the instance
- We use **low-level API** (`compileOrchestra`, `sendScoreEvent`) which reuses the instance
So "persistent mode" is actually achieved by **avoiding the methods that destroy/recreate**.
### Why We Adapted cm-eval Instead of Using It
The flok cm-eval package:
- Assumes a `Document` object with an `evaluate()` method
- Is tightly coupled to flok's session architecture
- Uses their specific remote evaluation system
Our needs:
- Evaluate blocks locally (no remote)
- Different document format (CSD vs raw code)
- Integrate with existing Csound store
Solution: Extract the core block detection and flash logic, adapt for our architecture.
### Csound Score Event Syntax Quick Reference
For testing live coding mode:
```csound
; Start instrument 1 at time 0, duration 2 seconds, amplitude 0.5
i 1 0 2 0.5
; Start instrument 1 now (time 0), indefinite duration (-1)
i 1 0 -1 0.5
; Turn off all instances of instrument 1
i -1 0 0
; Function table: create table 1, size 8192, sine wave
f 1 0 8192 10 1
```
---
## Next Session Checklist
When you resume implementation:
- [ ] Read this document thoroughly
- [ ] Verify all completed work with `pnpm build`
- [ ] Start with Phase 1 (Editor integration)
- [ ] Test each phase before moving to next
- [ ] Update this document if you deviate from the plan
- [ ] Document any issues found and solutions
---
## Questions to Resolve
None at this time. The architecture is well-defined and ready for implementation.
---
**Document Version:** 1.0
**Last Updated:** 2025-01-15
**Status:** Ready for Phase 1 implementation