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

710 lines
18 KiB
Markdown

# 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`
```javascript
/**
* 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`
```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)
```javascript
/**
* 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:
```python
# 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
"""
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
```markdown
## 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)
```bash
pip install testdrive-jsui-python
```
```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`
```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!