Files
testdrive-jsui/JS_FIRST_REFACTORING.md
tegwick ab4679126e docs: establish JavaScript-first architecture and refactoring plan
Clarified that testdrive-jsui is a JavaScript library with optional
backend adapters, not a Python package with JavaScript assets.

Added ARCHITECTURE.md:
- Core principle: JavaScript provides all functionality
- Python/Ruby/Java are integration adapters only
- Clear layer separation: JS library vs language adapters
- Distribution models: CDN, npm, with adapters
- Anti-patterns to avoid
- Success metrics

Added JS_FIRST_REFACTORING.md:
- Phase 1: Create standalone JavaScript bundle
- Phase 2: Refactor Python as thin adapter
- Phase 3: Build and distribution
- Concrete implementation steps with code examples
- Timeline: 2-4 days of focused work

Key Changes in Approach:
- JavaScript does ALL rendering (using marked.js)
- Backend adapters only serve assets and pass config
- No HTML generation in Python/Ruby/Java
- Library works completely standalone in browser

This establishes foundation for true language-agnostic design.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-16 12:04:12 +01:00

18 KiB

JavaScript-First Refactoring Plan

Goal: Transform testdrive-jsui from a Python package with JS assets into a pure JavaScript library with optional backend adapters.

Date: 2025-12-16 Status: Planning


🎯 Vision

Before (Current - Hybrid)

Python (TestDriveJSUIEngine)
  ↓ generates HTML template
  ↓ does fallback markdown rendering
  ↓ injects configuration
Browser receives: HTML with embedded JS config
JavaScript: Enhances the pre-rendered HTML

After (Target - JS-First)

JavaScript Library (testdrive-jsui.js)
  ↓ Standalone, works in any browser
  ↓ Renders markdown using marked.js
  ↓ Creates full UI
  ↓ Handles all interactions

Optional: Python/Ruby/Java Adapter
  ↓ Just serves JS/CSS files
  ↓ Passes config as JSON
  ↓ Provides testing helpers

📋 Phase 1: Create Standalone JavaScript Bundle

Goal

Create a single testdrive-jsui.js file that works completely standalone in a browser.

Tasks

1.1: Create Main Library Entry Point

Create: js/testdrive-jsui.js

/**
 * TestDrive-JSUI - Standalone JavaScript Markdown Editor
 *
 * A complete markdown editing and viewing UI that runs entirely in the browser.
 * No backend required!
 *
 * @version 1.0.0
 * @requires marked.js (for markdown parsing)
 */

class TestDriveJSUI {
    /**
     * Initialize TestDrive-JSUI
     *
     * @param {Object} config - Configuration options
     * @param {string|HTMLElement} config.container - Container element or selector
     * @param {string} config.mode - 'edit' or 'view'
     * @param {string} config.markdown - Initial markdown content
     * @param {string} config.theme - Theme name (default: 'github')
     * @param {boolean} config.autosave - Enable autosave (default: false)
     * @param {Object} config.callbacks - Event callbacks
     */
    constructor(config) {
        this.config = this._validateConfig(config);
        this.container = this._resolveContainer(config.container);

        // Initialize core systems
        this.debugSystem = new MarkitectDebugSystem();
        this.sectionManager = new SectionManager();
        this.domRenderer = new DOMRenderer(this.sectionManager, this.container);

        // Initialize controls based on mode
        this._initializeControls();

        // Load initial content
        if (config.markdown) {
            this.setContent(config.markdown);
        }
    }

    /**
     * Set markdown content
     * @param {string} markdown - Markdown text
     */
    setContent(markdown) {
        // Parse markdown to sections
        const sections = this.sectionManager.createSectionsFromMarkdown(markdown);

        // Render to DOM
        this.domRenderer.renderAllSections(sections);

        // Emit event
        this._emit('contentLoaded', { markdown, sections });
    }

    /**
     * Get current markdown content
     * @returns {string} Current markdown
     */
    getContent() {
        return this.sectionManager.getAllSectionsAsMarkdown();
    }

    /**
     * Switch between edit and view modes
     * @param {string} mode - 'edit' or 'view'
     */
    setMode(mode) {
        if (!['edit', 'view'].includes(mode)) {
            throw new Error(`Invalid mode: ${mode}`);
        }

        this.config.mode = mode;
        this._reinitializeControls();
        this._emit('modeChanged', { mode });
    }

    /**
     * Save current content (triggers callback)
     */
    save() {
        const markdown = this.getContent();

        if (this.config.callbacks?.onSave) {
            this.config.callbacks.onSave(markdown);
        } else if (this.config.autosave) {
            // Default: save to localStorage
            localStorage.setItem('testdrive-jsui-content', markdown);
        }

        this._emit('saved', { markdown });
    }

    /**
     * Destroy the editor and clean up
     */
    destroy() {
        // Clean up controls
        this.controls?.forEach(control => control?.destroy?.());

        // Clear container
        this.container.innerHTML = '';

        this._emit('destroyed');
    }

    // Internal methods
    _validateConfig(config) {
        const defaults = {
            mode: 'edit',
            theme: 'github',
            autosave: false,
            shortcuts: true,
            sections: true,
            debug: false,
            callbacks: {}
        };

        return { ...defaults, ...config };
    }

    _resolveContainer(selector) {
        if (typeof selector === 'string') {
            const element = document.querySelector(selector);
            if (!element) {
                throw new Error(`Container not found: ${selector}`);
            }
            return element;
        }
        return selector;
    }

    _initializeControls() {
        this.controls = [];

        if (this.config.mode === 'edit') {
            this.controls.push(
                new EditControl(this),
                new StatusControl(this),
                new ContentsControl(this)
            );
        }

        if (this.config.debug) {
            this.controls.push(new DebugControl(this));
        }

        // Initialize all controls
        this.controls.forEach(control => control.initialize());
    }

    _reinitializeControls() {
        // Destroy old controls
        this.controls?.forEach(control => control?.destroy?.());

        // Create new controls for current mode
        this._initializeControls();
    }

    _emit(event, data) {
        const callback = this.config.callbacks?.[`on${event.charAt(0).toUpperCase()}${event.slice(1)}`];
        if (callback) {
            callback(data);
        }

        // Also emit as custom event
        this.container.dispatchEvent(new CustomEvent(`testdrive:${event}`, { detail: data }));
    }
}

// Expose globally
if (typeof window !== 'undefined') {
    window.TestDriveJSUI = TestDriveJSUI;
}

// ES module export
if (typeof module !== 'undefined' && module.exports) {
    module.exports = TestDriveJSUI;
}

1.2: Create Standalone HTML Example

Create: examples/standalone.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TestDrive-JSUI Standalone Demo</title>

    <!-- TestDrive-JSUI Styles -->
    <link rel="stylesheet" href="../static/css/editor.css">
    <link rel="stylesheet" href="../static/css/controls.css">
    <link rel="stylesheet" href="../static/css/themes/github.css">
</head>
<body>
    <div id="editor-container"></div>

    <!-- Dependencies -->
    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>

    <!-- TestDrive-JSUI Core Components -->
    <script src="../js/core/debug-system.js"></script>
    <script src="../js/core/section-manager.js"></script>
    <script src="../js/components/dom-renderer.js"></script>
    <script src="../js/components/debug-panel.js"></script>
    <script src="../js/controls/control-base.js"></script>
    <script src="../js/controls/edit-control.js"></script>
    <script src="../js/controls/debug-control.js"></script>
    <script src="../js/controls/status-control.js"></script>
    <script src="../js/controls/contents-control.js"></script>

    <!-- Main library -->
    <script src="../js/testdrive-jsui.js"></script>

    <!-- Initialize -->
    <script>
        // Example markdown content
        const sampleMarkdown = `# TestDrive-JSUI Standalone Demo

## Welcome!

This is a **completely standalone** markdown editor running entirely in your browser. No backend required!

### Features

- ✅ Real-time markdown rendering
- ✅ Section-based editing
- ✅ Interactive controls
- ✅ Keyboard shortcuts
- ✅ Debug mode

### Try It Out

1. Click the edit button on any section
2. Modify the markdown
3. Save your changes

## Code Example

\`\`\`javascript
const editor = new TestDriveJSUI({
    container: '#editor-container',
    mode: 'edit',
    markdown: '# Hello World'
});
\`\`\`

That's it! No backend needed.`;

        // Initialize TestDrive-JSUI
        const editor = new TestDriveJSUI({
            container: '#editor-container',
            mode: 'edit',
            markdown: sampleMarkdown,
            theme: 'github',
            autosave: true,
            debug: true,
            callbacks: {
                onSave: (markdown) => {
                    console.log('Content saved:', markdown);
                    alert('Content saved to localStorage!');
                },
                onContentLoaded: (data) => {
                    console.log('Content loaded:', data);
                },
                onModeChanged: (data) => {
                    console.log('Mode changed to:', data.mode);
                }
            }
        });

        // Expose for debugging
        window.editor = editor;

        console.log('✅ TestDrive-JSUI initialized!');
        console.log('Try: editor.getContent(), editor.setMode("view"), editor.save()');
    </script>
</body>
</html>

1.3: Update Components to Work Standalone

Tasks:

  • Ensure all JS components work without Python-generated HTML
  • Remove any assumptions about server-side rendering
  • Make all components initialize from JavaScript

1.4: Add Markdown Rendering Helper

Update: js/utils/markdown-helper.js (create if needed)

/**
 * Markdown rendering utilities
 * Uses marked.js for parsing
 */
const MarkdownHelper = {
    /**
     * Render markdown to HTML
     * @param {string} markdown - Markdown text
     * @returns {string} HTML string
     */
    render(markdown) {
        if (!window.marked) {
            throw new Error('marked.js is required but not loaded');
        }

        // Configure marked
        marked.setOptions({
            gfm: true,
            breaks: true,
            headerIds: true,
            mangle: false
        });

        return marked.parse(markdown);
    },

    /**
     * Extract sections from markdown
     * @param {string} markdown - Markdown text
     * @returns {Array} Array of section objects
     */
    extractSections(markdown) {
        // Split on headers
        const sections = [];
        const lines = markdown.split('\n');
        let currentSection = { level: 0, title: '', content: '' };

        for (const line of lines) {
            const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);

            if (headerMatch) {
                // Save previous section
                if (currentSection.content) {
                    sections.push(currentSection);
                }

                // Start new section
                currentSection = {
                    level: headerMatch[1].length,
                    title: headerMatch[2],
                    content: line + '\n'
                };
            } else {
                currentSection.content += line + '\n';
            }
        }

        // Add final section
        if (currentSection.content) {
            sections.push(currentSection);
        }

        return sections;
    }
};

if (typeof window !== 'undefined') {
    window.MarkdownHelper = MarkdownHelper;
}

📋 Phase 2: Refactor Python as Thin Adapter

Goal

Transform Python code from "rendering engine" to "integration adapter".

Tasks

2.1: Rename Python Package Structure

src/testdrive_jsui/              # OLD NAME
  ↓
src/testdrive_jsui_python/       # NEW NAME (makes it clear it's an adapter)

Or keep name but rename classes to show they're adapters:

# Old (implies it does rendering)
class TestDriveJSUIEngine

# New (clear it's just an adapter)
class TestDriveJSUIAdapter
class PythonIntegrationAdapter

2.2: Simplify Python Adapter

New: src/testdrive_jsui/adapter.py

"""
Python Integration Adapter for TestDrive-JSUI

This is NOT the rendering engine - it's a helper for Python projects.
The JavaScript library does all the actual work.
"""
from pathlib import Path
from typing import Dict, Optional
import json

class TestDriveJSUIAdapter:
    """
    Python integration adapter for TestDrive-JSUI JavaScript library.

    Purpose:
    - Serve JS/CSS assets
    - Generate HTML templates with config
    - Provide testing helpers

    Does NOT:
    - Render markdown (JS does this)
    - Implement UI (JS does this)
    - Transform content (JS does this)
    """

    def __init__(self):
        self.js_library_path = self._find_js_library()

    def _find_js_library(self) -> Path:
        """Locate the JavaScript library files."""
        # Look in capability directory
        current = Path(__file__).parent
        while current.parent != current:
            js_dir = current / "js"
            if js_dir.exists() and (js_dir / "testdrive-jsui.js").exists():
                return js_dir
            current = current.parent
        raise FileNotFoundError("testdrive-jsui.js not found")

    def get_asset_urls(self, base_url: str = "/static") -> Dict[str, list]:
        """
        Get URLs for JS/CSS assets.

        Helper for web frameworks to include assets in templates.
        """
        return {
            "css": [
                f"{base_url}/testdrive-jsui/css/editor.css",
                f"{base_url}/testdrive-jsui/css/controls.css",
                f"{base_url}/testdrive-jsui/css/themes/github.css",
            ],
            "js": [
                "https://cdn.jsdelivr.net/npm/marked/marked.min.js",
                f"{base_url}/testdrive-jsui/js/testdrive-jsui.js",
            ]
        }

    def generate_html(self,
                     markdown: str,
                     mode: str = "edit",
                     config: Optional[Dict] = None) -> str:
        """
        Generate HTML that loads and initializes the JS library.

        This is just a convenience helper - you can write your own template!
        """
        config = config or {}
        config.update({
            "markdown": markdown,
            "mode": mode
        })

        assets = self.get_asset_urls()

        return f"""<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>TestDrive-JSUI</title>
    {''.join(f'<link rel="stylesheet" href="{url}">' for url in assets['css'])}
</head>
<body>
    <div id="editor"></div>

    {''.join(f'<script src="{url}"></script>' for url in assets['js'])}

    <script>
        const editor = new TestDriveJSUI({json.dumps(config, indent=2)});
    </script>
</body>
</html>"""

    def run_js_tests(self):
        """Run JavaScript tests (existing functionality - keep this!)."""
        from .testing import JavaScriptTestRunner
        runner = JavaScriptTestRunner(self.js_library_path.parent)
        return runner.run_js_tests()

2.3: Update Documentation

Update: README.md to show Python as optional

## Installation

### Option 1: Use JavaScript Directly (No Backend)

```html
<script src="https://cdn.jsdelivr.net/npm/testdrive-jsui@1.0.0"></script>
<script>
  const editor = new TestDriveJSUI({ ... });
</script>

Option 2: With Python Adapter (for Django/Flask)

pip install testdrive-jsui-python
from testdrive_jsui import TestDriveJSUIAdapter
adapter = TestDriveJSUIAdapter()
html = adapter.generate_html(markdown="# Hello")

Option 3: With Ruby Adapter (for Rails) - Coming Soon

Option 4: With Java Adapter (for Spring) - Coming Soon


---

## 📋 Phase 3: Build and Distribution

### Goal
Package JavaScript library for easy distribution.

### Tasks

#### 3.1: Create Build Script

**Create**: `js/build.js`

```javascript
/**
 * Build script for testdrive-jsui
 * Combines all modules into single distributable file
 */
const fs = require('fs');
const path = require('path');

// Files to concatenate in order
const files = [
    'utils/markdown-helper.js',
    'core/debug-system.js',
    'core/section-manager.js',
    'components/dom-renderer.js',
    'components/debug-panel.js',
    'controls/control-base.js',
    'controls/edit-control.js',
    'controls/debug-control.js',
    'controls/status-control.js',
    'controls/contents-control.js',
    'testdrive-jsui.js'  // Main class last
];

// Read and concatenate
let bundle = '// TestDrive-JSUI v1.0.0 - MIT License\n';
bundle += '(function() {\n';

for (const file of files) {
    const content = fs.readFileSync(path.join(__dirname, file), 'utf8');
    bundle += '\n// === ' + file + ' ===\n';
    bundle += content + '\n';
}

bundle += '})();\n';

// Write bundle
fs.writeFileSync(path.join(__dirname, 'dist', 'testdrive-jsui.js'), bundle);

console.log('✅ Built: dist/testdrive-jsui.js');

3.2: Create package.json for npm

Update: package.json

{
  "name": "testdrive-jsui",
  "version": "1.0.0",
  "description": "Standalone JavaScript markdown editor with interactive UI",
  "main": "js/dist/testdrive-jsui.js",
  "types": "js/testdrive-jsui.d.ts",
  "files": [
    "js/dist/",
    "static/css/",
    "README.md"
  ],
  "scripts": {
    "build": "node js/build.js",
    "test": "jest",
    "prepublish": "npm run build"
  },
  "keywords": [
    "markdown", "editor", "javascript", "standalone", "ui"
  ],
  "dependencies": {
    "marked": "^4.0.0"
  }
}

Success Criteria

Phase 1 Complete When:

  • Can open standalone.html in browser (no server needed)
  • Can edit markdown completely in JavaScript
  • All UI controls work without Python
  • marked.js renders markdown in browser

Phase 2 Complete When:

  • Python adapter is < 200 lines of code
  • Python doesn't render markdown
  • Python doesn't generate HTML structure
  • Can use testdrive-jsui with Flask/Django via adapter

Phase 3 Complete When:

  • Single testdrive-jsui.js file works everywhere
  • Published to npm
  • Available via CDN
  • < 100KB minified + gzipped

📅 Timeline Estimate

  • Phase 1: 1-2 days (core JavaScript refactoring)
  • Phase 2: 1 day (simplify Python adapter)
  • Phase 3: 0.5 day (build and distribution)

Total: 2-4 days of focused work


🎯 Priority Order

  1. Phase 1.2: Create standalone.html example (proves concept)
  2. Phase 1.4: Markdown rendering in JS
  3. Phase 1.1: Main library class
  4. Phase 2.2: Simplify Python adapter
  5. Phase 3: Build and distribution

Start with #1 to validate the approach!