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.

CRITICAL: Git Safety Guidelines

How to safely work with git and preserve all user work!

Guiding Principles:

Example of WRONG approach:

// User: "Let's scrap this feature"
// ❌ WRONG - immediately destroying work:
bash("git checkout -- src/file.js")

// What if user had OTHER uncommitted changes?
// What if the file had important work-in-progress?
// DISASTER!

Example of CORRECT approach:

// User: "Let's scrap this feature"

// Step 1: Check current state
bash("git status")
// Shows: "modified: src/file.js, modified: src/other-important-file.js"

// Step 2: Check what will be lost
bash("git diff src/file.js")
// Shows the actual changes

// Step 3: Ask user for confirmation
"I see you have changes in src/file.js. You also have uncommitted changes in 
src/other-important-file.js. Should I discard the changes in src/file.js, 
or do you want to keep working on it?"

// Step 4: Only proceed if user explicitly confirms
// Then run the git command they requested

Why this approach is safe:

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

Avoid Needless Documentation:

Module Reloading:

Verifying Live Updates:

Use lively.changes.verifyFileUpdate(pathFragment, sinceMs=60000, waitMs=1000) to confirm edits were detected:

// Waits 1 second, checks last minute of changes
await lively.changes.verifyFileUpdate('my-component.js');
// Output: ✓ my-component.js reloaded in 45ms (module reload)
// Output: ✓ my-component.js updated in container (1 container) (container update)
// Output: ⚠ No changes detected (not found)

// Check what was detected
let recent = await lively.changes.since(60000);
console.log(recent.map(c => c.relativePath));

Changes are tracked with containerUpdated (file updated in open editor) or reloadDuration (module reloaded). If verification fails, check if lively-change-watcher is running or if a full page reload is needed.

Development Best Practices:

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:

AI Workspace Integration

AI Workspace provides integrated AI coding assistance through multiple agents:

Special Notes