lively4-core

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

IMPORTANT: Object-Oriented Architecture

This is NOT a typical functional JavaScript project!

Lively4 follows object-oriented principles with class hierarchies and inheritance:

Example: Database Write Guards

Example: 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.

AI Collaboration Experiment

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.

Essential Commands

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:

  1. Run all tests with runAll: true → minimal summary (~50 tokens)
  2. Navigate failures hierarchically → drill down only where needed
  3. This saves ~15,000 tokens compared to dumping all test details at once

Features:

Prerequisites:

IMPORTANT - Testing Best Practices:

Debugging 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:

In-Browser Development:

Core Architecture

Lively4 System: Self-supporting browser-based development environment using:

Module System:

Central API (src/client/lively.js):

Special URL Schemes (via fetch() with eval):

Server Integration:

File Organization & Patterns

Directory Structure:

Component Development Pattern:

  1. Create paired files in src/components/tools/: my-component.html + my-component.js
  2. Components extend Morph 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>

Key Integration Points

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")

Development Guidelines

Naming Conventions:

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))

Interactive Markdown Development

Script Integration in Markdown Files:

Development Journal

Journal 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

MCP Integration

Model Context Protocol (MCP) enables Claude Code to interact directly with live Lively4 environments:

Architecture:

Available Tools:

Adding New Tools:

  1. Define tool in ../lively4-server/tools.json with description, inputSchema, and endpoint
  2. Implement handler method in mcp-server.js following existing patterns
  3. Tools automatically registered on server startup

Usage:

// Open MCP component in browser
lively.openComponentInWindow('lively-mcp')

// Claude Code can then execute code in the live environment

TODO:

Special Notes