# 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 TestDrive-JSUI Standalone Demo
``` #### 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""" TestDrive-JSUI {''.join(f'' for url in assets['css'])}
{''.join(f'' for url in assets['js'])} """ 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 ``` ### 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!