This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is NOT a typical functional JavaScript project!
Lively4 follows object-oriented principles with class hierarchies and inheritance:
LivelyChat → OpenaiRealtimeChat, LivelyOpencode, LivelyAiWorkspaceExample: Database Write Guards
canWriteToDatabase() to each component separatelycanWriteToDatabase() to LivelyChat base class, all subclasses inherit itExample: Composition Pattern - Propagating State
When a parent component contains child components, state changes must be explicitly propagated:
// ❌ WRONG: Only set state on parent
enableReplay() {
this._replayMode = true; // Only affects parent, children still write to DB!
}
// ✅ RIGHT: Propagate state to composed children
enableReplay() {
this._replayMode = true;
// CRITICAL: Propagate to child components
this.realtimeComponent._replayMode = true;
this.opencodeComponent._replayMode = true;
}
disableReplay() {
this._replayMode = false;
// CRITICAL: Clear from child components
if (this.realtimeComponent) this.realtimeComponent._replayMode = false;
if (this.opencodeComponent) this.opencodeComponent._replayMode = false;
}
Key principle: Child components don’t automatically inherit instance variables from their container. State must be explicitly synchronized in composition relationships.
When you find yourself duplicating code across components that share a base class, STOP and move it to the parent class instead.
A significant part of this AI collaboration is an experiment to teach Claude Code how to develop in Lively4. By working together on real development tasks, we are:
This documentation serves dual purposes: guiding AI development work and creating human-readable documentation of Lively4’s development practices. The demos/claude/ directory contains examples and experiments from this collaborative learning process.
Testing:
Use the MCP testing tools for in-browser test execution with minimal token usage:
Phase 1: Run Tests
// Run a single test file
mcp__lively4__run-tests(testPath: "test/client/strings-test.js")
// Run ALL tests (minimal output)
mcp__lively4__run-tests(runAll: true)
// Returns: "✅ All green! 456 tests passed across 77 files"
// or: "❌ 18 tests failed (438 passed) across 5 files: ..."
// Filter tests with grep pattern
mcp__lively4__run-tests(testPath: "test/client/strings-test.js", grep: "toUpperCaseFirst")
// Errors-only mode for single files
mcp__lively4__run-tests(testPath: "test/client/strings-test.js", errorsOnly: true)
Phase 2: Inspect Results (Hierarchical Navigation)
Supports 3 levels of detail - navigate like a directory tree:
// Level 1: Summary view (like `ls`) - shows file-level counts only
mcp__lively4__inspect-test-results()
// → "77 files, 456 passed, 18 failed"
// → Lists files with pass/fail counts
// Level 2: Suite view (like `cd` then `ls`) - shows suite hierarchy in a file
mcp__lively4__inspect-test-results(file: "test/client/claude-sessions-test.js")
// → "Suites:"
// → " ✅ Claude Sessions Message Classes (38 tests)"
// → " ✅ ClaudeMessage (12 tests)"
// → " ❌ ClaudeAgentMessage (18 tests, 3 failed)"
// Level 3: Detail view (like `ls -R`) - shows individual tests in a suite
mcp__lively4__inspect-test-results(
file: "test/client/claude-sessions-test.js",
suite: "ClaudeAgentMessage"
)
// → Shows individual test names with error details
// Add includeStacks for full error traces (only in Level 3)
mcp__lively4__inspect-test-results(
file: "test/client/claude-sessions-test.js",
suite: "ClaudeAgentMessage",
includeStacks: true
)
Two-Phase Workflow:
runAll: true → minimal summary (~50 tokens)Features:
Prerequisites:
lively-mcp component must be running in browserlively.openComponentInWindow('lively-mcp') or Right-click → Tools → MCPIMPORTANT - Testing Best Practices:
export to it in the source file and import it in testsDebugging SystemJS-Level Code:
When fixing bugs in core system code (like lively.js, bound-eval.js, or SystemJS integration), use npm test with describe.only for rapid iteration. The MCP test tools run in a live browser session where SystemJS behavior may differ from Karma’s test environment. Some bugs (like module re-execution during unload) only manifest in the Karma test runner with workspace modules. Add describe.only("TestSuiteName", ...) to your test file to run just that suite, then npm test to execute in the full test environment. Remove describe.only when done.
Test-Driven Development (TDD) - Red-Green Workflow: Before claiming a fix works, ALWAYS verify the test fails WITHOUT your fix and passes WITH it. Steps: (1) Write test for the bug, (2) Run test - confirm it FAILS (red), (3) Implement fix, (4) Run test - confirm it PASSES (green). Never trust a test that you haven’t seen fail first - it might be testing the wrong thing or not actually exercising your code path. This is especially critical for subtle bugs where tests may pass due to caching or environment differences.
Development:
npm run explore-lively4 - Explore the Lively4 codebase programmaticallynpm run explore-lively4:debug - Same as above with debug output enabledIn-Browser Development:
Lively4 System: Self-supporting browser-based development environment using:
Morph (from src/components/widgets/lively-morph.js)src/plugin-babel.jsinitialize(), connectedCallback(), livelyExample(), livelyMigrate()Module System:
src/systemjs-config.js defines transpilation rules per file patternliveES7, moduleOptionsNon, aexprViaDirective, workspacelively.reloadModule(path) updates JS and templates at runtimesrc/client/morphic/component-loader.jsCentral API (src/client/lively.js):
lively.create(tagName) - creates web componentslively.openComponentInWindow(name) - opens components in windowslively.components.loadByName(name) - loads component definitionslively.files.loadFile(), lively.files.saveFile() - file operations through lively4-serverSpecial URL Schemes (via fetch() with eval):
fetch("open://component-name") - opens component in windowfetch("edit://path/to/file") - opens file in editorfetch("browse://path/to/file") - opens file in browser/container.then(r => r.object()) - returns the actual component/container objectServer Integration:
../lively4-server (parallel directory)Directory Structure:
src/components/ - Web components (tools/, widgets/, demo/, halo/)
src/components/tools/ with both .html and .js filestemplates/ - DEPRECATED - Old component templates (do not create new components here)src/client/ - Core runtime and utilities (lively.js, boot.js, etc.)src/external/ - Third-party librariestest/ - Test files (Karma + Mocha)doc/ - Documentation and project notesComponent Development Pattern:
src/components/tools/: my-component.html + my-component.jsMorph and follow this structure:export default class MyComponent extends Morph {
async initialize() {
this.windowTitle = "Component Title";
this.registerButtons(); // auto-registers onButtonName handlers
lively.html.registerKeys(this); // auto-registers onKeyDown handlers
// IMPORTANT: Preserve existing state during live updates
// Use || operator to keep existing data during livelyMigrate
this._cachedData = this._cachedData || [];
this._processedResults = this._processedResults || new Map();
// Always reset volatile state:
this._currentOperation = null;
// IMPORTANT: Don't block in initialize() with async operations
// Use non-blocking calls for data loading:
this.loadData(); // NOT: await this.loadData()
}
async loadData() {
// Heavy async operations should be separate from initialize()
// This allows the component to render immediately
}
livelyExample() {
// Customize instance with example content
}
livelyMigrate(other) {
// Handle live updates during development
this.someProperty = other.someProperty;
}
}
Template Pattern (HTML):
<template id="my-component">
<style data-src="/templates/livelystyle.css"></style>
<style>/* component-specific styles */</style>
<div id="content">
<button id="myButton">Click Me</button>
</div>
</template>
Container System: lively-container (src/components/tools/lively-container.js)
Event System: Use lively.addEventListener() for proper cleanup:
lively.addEventListener("myId", this, "click", evt => this.onClick(evt))
// Automatically cleaned up with lively.removeEventListener("myId", this)
Component Access:
this.get("#selector") // querySelector in component and shadowRoot
await lively.openComponentInWindow("component-name")
lively.components.searchTemplateFilename() to locate templates programmaticallylivelyExample() method to provide example content for componentsdemos/claude/ directory to avoid cluttering main demosNaming Conventions:
onButtonName() (e.g., onRefreshButton(), onSaveButton())
this.registerButtons() auto-registrationBtn suffix - always full Buttonevt as parameter name (e.g., onClick(evt), onKeyDown(evt))id="refreshButton" → onRefreshButton())class="refresh-button")Event Handler Registration:
// Automatic button registration
this.registerButtons(); // Finds buttons by ID and registers on[ButtonName] handlers
// Manual event registration with cleanup
lively.addEventListener("myId", this, "click", evt => this.onClick(evt))
Script Integration in Markdown Files:
import lib from 'https://cdn.example.com/lib.js'lively.query(this, "lively-markdown")markdownComponent.shadowRoot for proper DOM scopingdoc/journal/ as directories named YYYY-MM-DD.md/ containing index.md- [ ] and - [x] for task lists (renders as checkboxes)[filename](edit://path/to/file) syntax for direct editing (not in code blocks)bash date +"%Y-%m-%d" to get correct dates, not environment contextJournal Entry Format:
## YYYY-MM-DD General Day Title #hashtags #topics #keywords
*Author: @JensLincke [with @BlindGoldie]*
Brief technical description of what was implemented/changed.
- **Added**: [file.js](edit://path/to/file.js), [file.html](edit://path/to/file.html)
- **Modified**: [existing-file.js](edit://path/to/file.js) - description of changes
- **Feature**: Technical details with method names and implementation specifics
- **UI**: Interface changes and user-facing features
**Technical details:**
- Specific implementation notes
- Method signatures or key code patterns
- Integration points
**TODO**:
- [ ] #TODO Future improvements needed
Model Context Protocol (MCP) enables Claude Code to interact directly with live Lively4 environments:
Architecture:
lively-mcp establishes WebSocket connection to server../lively4-server/src/services/mcp-server.js implements MCP protocol../lively4-server/tools.json defines available MCP toolsAvailable Tools:
evaluate_code - Execute JavaScript in live browser sessionslist_sessions - List active browser sessionsping_sessions - Check session connectivityAdding New Tools:
../lively4-server/tools.json with description, inputSchema, and endpointmcp-server.js following existing patternsUsage:
// Open MCP component in browser
lively.openComponentInWindow('lively-mcp')
// Claude Code can then execute code in the live environment
TODO:
open://, edit://, browse://) to support direct file/component operations without eval