docs: add DocumentNavigator development infrastructure and test suite
Some checks failed
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled

- Add comprehensive widget plugin infrastructure documentation and workplan
- Include complete DocumentNavigator integration documentation
- Add TDD test suite with 15 comprehensive test cases for DocumentNavigator
- Include widget base classes (Widget, UIWidget) for future development
- Add DocumentNavigator plugin definition following planned architecture
- Include test runner and demo pages for development validation

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-10 19:41:18 +01:00
parent 2d516b205a
commit b963940144
9 changed files with 3604 additions and 0 deletions

View File

@@ -0,0 +1,193 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DocumentNavigator TDD Test Runner</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 2rem;
background-color: #f8f9fa;
}
.test-header {
background: white;
padding: 2rem;
border-radius: 8px;
margin-bottom: 2rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.test-output {
background: #1a1a1a;
color: #00ff00;
font-family: 'Courier New', monospace;
padding: 1rem;
border-radius: 8px;
margin-top: 1rem;
white-space: pre-wrap;
overflow-x: auto;
max-height: 400px;
}
.run-button {
background: #007bff;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
transition: background 0.2s;
}
.run-button:hover {
background: #0056b3;
}
.run-button:disabled {
background: #6c757d;
cursor: not-allowed;
}
.status {
margin-top: 1rem;
padding: 1rem;
border-radius: 6px;
font-weight: bold;
}
.status.running {
background: #fff3cd;
color: #856404;
}
.status.passed {
background: #d4edda;
color: #155724;
}
.status.failed {
background: #f8d7da;
color: #721c24;
}
</style>
</head>
<body>
<div class="test-header">
<h1>📋 DocumentNavigator Widget TDD Test Suite</h1>
<p>
This test suite follows Test-Driven Development methodology to implement a Substack-style
floating document navigation widget. The tests define the expected behavior before
implementation begins.
</p>
<div>
<strong>Test Coverage:</strong>
<ul>
<li>✅ Widget class structure and inheritance</li>
<li>✅ Configuration and initialization</li>
<li>✅ DOM rendering and UI elements</li>
<li>✅ Heading extraction and hierarchy building</li>
<li>✅ Navigation functionality and smooth scrolling</li>
<li>✅ Expand/collapse behavior</li>
<li>✅ Scroll spy and active section detection</li>
<li>✅ Responsive behavior and auto-hide</li>
<li>✅ Keyboard navigation support</li>
<li>✅ Event emission and user interaction</li>
<li>✅ Edge cases and error handling</li>
</ul>
</div>
<button id="runTests" class="run-button">🧪 Run TDD Test Suite</button>
<div id="status" class="status" style="display: none;"></div>
</div>
<div id="testOutput" class="test-output" style="display: none;"></div>
<script type="module">
const runButton = document.getElementById('runTests');
const statusDiv = document.getElementById('status');
const outputDiv = document.getElementById('testOutput');
// Capture console output
const originalConsoleLog = console.log;
const originalConsoleError = console.error;
let capturedOutput = '';
function captureConsole() {
capturedOutput = '';
console.log = (...args) => {
capturedOutput += args.join(' ') + '\n';
originalConsoleLog(...args);
};
console.error = (...args) => {
capturedOutput += 'ERROR: ' + args.join(' ') + '\n';
originalConsoleError(...args);
};
}
function restoreConsole() {
console.log = originalConsoleLog;
console.error = originalConsoleError;
}
function updateStatus(message, type) {
statusDiv.textContent = message;
statusDiv.className = `status ${type}`;
statusDiv.style.display = 'block';
}
function showOutput() {
outputDiv.textContent = capturedOutput;
outputDiv.style.display = 'block';
}
runButton.addEventListener('click', async () => {
runButton.disabled = true;
updateStatus('🧪 Running tests...', 'running');
captureConsole();
try {
// Import and run tests
const { runner } = await import('./test-document-navigator.js');
console.log('Starting DocumentNavigator TDD Test Suite...\n');
console.log('Note: Tests are expected to FAIL initially (Red phase of TDD)');
console.log('We will implement functionality to make them pass (Green phase).\n');
await runner.run();
if (runner.results.failed === 0) {
updateStatus(`🎉 All ${runner.results.total} tests passed!`, 'passed');
} else {
updateStatus(`${runner.results.failed} of ${runner.results.total} tests failed (Expected in TDD Red phase)`, 'failed');
}
} catch (error) {
console.error('Test execution failed:', error);
updateStatus('💥 Test execution failed - this is expected in TDD Red phase', 'failed');
} finally {
restoreConsole();
showOutput();
runButton.disabled = false;
}
});
// Auto-run tests on page load for development
document.addEventListener('DOMContentLoaded', () => {
console.log('DocumentNavigator TDD Test Runner loaded');
console.log('Ready to run tests - click the button above');
});
</script>
<!-- Test content for heading extraction tests -->
<div style="display: none;" id="test-content">
<h1>Test Chapter 1</h1>
<p>Sample content for testing heading extraction.</p>
<h2>Section 1.1</h2>
<h3>Subsection 1.1.1</h3>
<p>More sample content.</p>
<h2>Section 1.2</h2>
<h1>Test Chapter 2</h1>
</div>
</body>
</html>

View File

@@ -0,0 +1,432 @@
/**
* TDD Test Suite for DocumentNavigator Widget
*
* Tests the Substack-style floating navigation widget for document headings.
* Following TDD methodology: write tests first, then implement functionality.
*/
// Simple test runner for browser environment
class DocumentNavigatorTestRunner {
constructor() {
this.tests = [];
this.results = {
passed: 0,
failed: 0,
total: 0
};
}
test(name, testFn) {
this.tests.push({ name, testFn });
}
expect(actual) {
return {
toBe: (expected) => {
if (actual !== expected) {
throw new Error(`Expected ${actual} to be ${expected}`);
}
},
toBeInstanceOf: (expectedClass) => {
if (!(actual instanceof expectedClass)) {
throw new Error(`Expected ${actual} to be instance of ${expectedClass.name}`);
}
},
toBeTruthy: () => {
if (!actual) {
throw new Error(`Expected ${actual} to be truthy`);
}
},
toBeFalsy: () => {
if (actual) {
throw new Error(`Expected ${actual} to be falsy`);
}
},
toContain: (expected) => {
if (typeof actual === 'string' && !actual.includes(expected)) {
throw new Error(`Expected "${actual}" to contain "${expected}"`);
}
if (Array.isArray(actual) && !actual.includes(expected)) {
throw new Error(`Expected array to contain ${expected}`);
}
},
toHaveLength: (expected) => {
if (actual.length !== expected) {
throw new Error(`Expected length ${actual.length} to be ${expected}`);
}
},
toBeGreaterThan: (expected) => {
if (actual <= expected) {
throw new Error(`Expected ${actual} to be greater than ${expected}`);
}
}
};
}
async run() {
console.log('🧪 Running DocumentNavigator TDD Test Suite...\n');
for (const { name, testFn } of this.tests) {
this.results.total++;
try {
await testFn.call(this);
this.results.passed++;
console.log(`${name}`);
} catch (error) {
this.results.failed++;
console.log(`${name}`);
console.log(` ${error.message}\n`);
}
}
this.printSummary();
}
printSummary() {
console.log(`\n📊 Test Results:`);
console.log(` Passed: ${this.results.passed}`);
console.log(` Failed: ${this.results.failed}`);
console.log(` Total: ${this.results.total}`);
if (this.results.failed === 0) {
console.log(`\n🎉 All tests passed!`);
} else {
console.log(`\n${this.results.failed} test(s) failed.`);
}
}
}
// Create test runner
const runner = new DocumentNavigatorTestRunner();
// Test Suite: DocumentNavigator Widget
runner.test('DocumentNavigator class should exist and be importable', async function() {
// This test will fail initially - we haven't created the class yet
try {
const { DocumentNavigator } = await import('../widgets/navigation/DocumentNavigator.js');
this.expect(DocumentNavigator).toBeTruthy();
this.expect(typeof DocumentNavigator).toBe('function');
} catch (error) {
throw new Error(`DocumentNavigator class not found: ${error.message}`);
}
});
runner.test('DocumentNavigator should extend UIWidget', async function() {
const { DocumentNavigator } = await import('../widgets/navigation/DocumentNavigator.js');
const { UIWidget } = await import('../widgets/base/UIWidget.js');
const navigator = new DocumentNavigator();
this.expect(navigator).toBeInstanceOf(UIWidget);
});
runner.test('DocumentNavigator should initialize with default configuration', async function() {
const { DocumentNavigator } = await import('../widgets/navigation/DocumentNavigator.js');
const navigator = new DocumentNavigator();
// Test default configuration
this.expect(navigator.config.position).toBe('left');
this.expect(navigator.config.collapsed).toBe(true);
this.expect(navigator.config.autoHide).toBe(true);
this.expect(navigator.config.maxHeadingLevel).toBe(3);
this.expect(navigator.config.enableScrollSpy).toBe(true);
});
runner.test('DocumentNavigator should accept custom configuration', async function() {
const { DocumentNavigator } = await import('../widgets/navigation/DocumentNavigator.js');
const customConfig = {
position: 'right',
collapsed: false,
maxHeadingLevel: 4,
theme: 'dark'
};
const navigator = new DocumentNavigator(customConfig);
this.expect(navigator.config.position).toBe('right');
this.expect(navigator.config.collapsed).toBe(false);
this.expect(navigator.config.maxHeadingLevel).toBe(4);
this.expect(navigator.config.theme).toBe('dark');
});
runner.test('DocumentNavigator should render floating panel element', async function() {
const { DocumentNavigator } = await import('../widgets/navigation/DocumentNavigator.js');
const navigator = new DocumentNavigator();
await navigator.render();
this.expect(navigator.element).toBeInstanceOf(HTMLElement);
this.expect(navigator.element.classList.contains('document-navigator')).toBeTruthy();
this.expect(navigator.element.style.position).toBe('fixed');
});
runner.test('DocumentNavigator should have toggle button in collapsed state', async function() {
const { DocumentNavigator } = await import('../widgets/navigation/DocumentNavigator.js');
const navigator = new DocumentNavigator({ collapsed: true });
await navigator.render();
const toggleButton = navigator.findElement('.navigator-toggle');
this.expect(toggleButton).toBeInstanceOf(HTMLElement);
this.expect(toggleButton.style.display).not.toBe('none');
const navList = navigator.findElement('.navigator-list');
this.expect(navList.style.display).toBe('none');
});
runner.test('DocumentNavigator should extract headings from document', async function() {
const { DocumentNavigator } = await import('../widgets/navigation/DocumentNavigator.js');
// Create test document with headings
const testContainer = document.createElement('div');
testContainer.innerHTML = `
<h1 id="heading1">First Heading</h1>
<p>Some content</p>
<h2 id="heading2">Second Heading</h2>
<h3 id="heading3">Third Heading</h3>
<p>More content</p>
<h2 id="heading4">Fourth Heading</h2>
`;
document.body.appendChild(testContainer);
const navigator = new DocumentNavigator({
container: testContainer,
maxHeadingLevel: 3
});
const headings = navigator.extractHeadings();
this.expect(headings).toHaveLength(4);
this.expect(headings[0].tagName).toBe('H1');
this.expect(headings[0].textContent).toBe('First Heading');
this.expect(headings[1].tagName).toBe('H2');
this.expect(headings[2].tagName).toBe('H3');
this.expect(headings[3].tagName).toBe('H2');
// Cleanup
document.body.removeChild(testContainer);
});
runner.test('DocumentNavigator should build navigation hierarchy', async function() {
const { DocumentNavigator } = await import('../widgets/navigation/DocumentNavigator.js');
// Create test document with nested headings
const testContainer = document.createElement('div');
testContainer.innerHTML = `
<h1>Chapter 1</h1>
<h2>Section 1.1</h2>
<h3>Subsection 1.1.1</h3>
<h3>Subsection 1.1.2</h3>
<h2>Section 1.2</h2>
<h1>Chapter 2</h1>
`;
document.body.appendChild(testContainer);
const navigator = new DocumentNavigator({ container: testContainer });
await navigator.render();
const navItems = navigator.buildNavigationTree();
// Should have hierarchical structure
this.expect(navItems).toHaveLength(2); // 2 H1 elements
this.expect(navItems[0].children).toHaveLength(2); // 2 H2 under first H1
this.expect(navItems[0].children[0].children).toHaveLength(2); // 2 H3 under first H2
// Cleanup
document.body.removeChild(testContainer);
});
runner.test('DocumentNavigator should handle click navigation', async function() {
const { DocumentNavigator } = await import('../widgets/navigation/DocumentNavigator.js');
// Create test document
const testContainer = document.createElement('div');
testContainer.innerHTML = `
<h1 id="target-heading">Target Heading</h1>
<p style="height: 1000px;">Spacer content</p>
`;
document.body.appendChild(testContainer);
const navigator = new DocumentNavigator({ container: testContainer });
await navigator.render();
// Simulate click on navigation item
const navItem = navigator.findElement('[data-target="target-heading"]');
this.expect(navItem).toBeTruthy();
// Mock scrollIntoView for testing
const targetElement = document.getElementById('target-heading');
let scrollCalled = false;
targetElement.scrollIntoView = () => { scrollCalled = true; };
// Click navigation item
navItem.click();
this.expect(scrollCalled).toBeTruthy();
// Cleanup
document.body.removeChild(testContainer);
});
runner.test('DocumentNavigator should support expand/collapse functionality', async function() {
const { DocumentNavigator } = await import('../widgets/navigation/DocumentNavigator.js');
const navigator = new DocumentNavigator({ collapsed: true });
await navigator.render();
// Should start collapsed
this.expect(navigator.isCollapsed).toBeTruthy();
const toggleButton = navigator.findElement('.navigator-toggle');
const navList = navigator.findElement('.navigator-list');
// Toggle to expanded
await navigator.expand();
this.expect(navigator.isCollapsed).toBeFalsy();
this.expect(navList.style.display).not.toBe('none');
// Toggle back to collapsed
await navigator.collapse();
this.expect(navigator.isCollapsed).toBeTruthy();
this.expect(navList.style.display).toBe('none');
});
runner.test('DocumentNavigator should implement scroll spy functionality', async function() {
const { DocumentNavigator } = await import('../widgets/navigation/DocumentNavigator.js');
// Create test document with multiple sections
const testContainer = document.createElement('div');
testContainer.innerHTML = `
<div style="height: 100px;"></div>
<h1 id="section1">Section 1</h1>
<div style="height: 400px;"></div>
<h2 id="section2">Section 2</h2>
<div style="height: 400px;"></div>
<h2 id="section3">Section 3</h2>
<div style="height: 400px;"></div>
`;
document.body.appendChild(testContainer);
const navigator = new DocumentNavigator({
container: testContainer,
enableScrollSpy: true
});
await navigator.render();
// Test current section detection
const currentSection = navigator.getCurrentSection();
this.expect(currentSection).toBeTruthy();
// Cleanup
document.body.removeChild(testContainer);
});
runner.test('DocumentNavigator should handle responsive behavior', async function() {
const { DocumentNavigator } = await import('../widgets/navigation/DocumentNavigator.js');
const navigator = new DocumentNavigator({ autoHide: true });
await navigator.render();
// Mock viewport resize
const originalInnerWidth = window.innerWidth;
// Test mobile viewport
Object.defineProperty(window, 'innerWidth', { value: 500, configurable: true });
navigator.handleResize();
this.expect(navigator.element.style.display).toBe('none');
// Test desktop viewport
Object.defineProperty(window, 'innerWidth', { value: 1200, configurable: true });
navigator.handleResize();
this.expect(navigator.element.style.display).not.toBe('none');
// Restore original
Object.defineProperty(window, 'innerWidth', { value: originalInnerWidth, configurable: true });
});
runner.test('DocumentNavigator should provide keyboard navigation support', async function() {
const { DocumentNavigator } = await import('../widgets/navigation/DocumentNavigator.js');
const navigator = new DocumentNavigator();
await navigator.render();
// Test keyboard shortcuts
let expandCalled = false;
let collapseCalled = false;
navigator.expand = async () => { expandCalled = true; };
navigator.collapse = async () => { collapseCalled = true; };
// Simulate keyboard events
const element = navigator.element;
// Test Escape key (should collapse)
const escapeEvent = new KeyboardEvent('keydown', { key: 'Escape' });
element.dispatchEvent(escapeEvent);
this.expect(collapseCalled).toBeTruthy();
// Test Enter/Space key (should expand)
const enterEvent = new KeyboardEvent('keydown', { key: 'Enter' });
element.dispatchEvent(enterEvent);
this.expect(expandCalled).toBeTruthy();
});
runner.test('DocumentNavigator should emit events for user interactions', async function() {
const { DocumentNavigator } = await import('../widgets/navigation/DocumentNavigator.js');
const navigator = new DocumentNavigator();
await navigator.render();
// Test event emission
let navigationEvent = null;
navigator.addEventListener('navigate', (e) => {
navigationEvent = e;
});
let toggleEvent = null;
navigator.addEventListener('toggle', (e) => {
toggleEvent = e;
});
// Trigger navigation
navigator.navigateToHeading('test-heading');
this.expect(navigationEvent).toBeTruthy();
this.expect(navigationEvent.detail.target).toBe('test-heading');
// Trigger toggle
await navigator.toggle();
this.expect(toggleEvent).toBeTruthy();
});
runner.test('DocumentNavigator should handle empty document gracefully', async function() {
const { DocumentNavigator } = await import('../widgets/navigation/DocumentNavigator.js');
// Create empty container
const emptyContainer = document.createElement('div');
document.body.appendChild(emptyContainer);
const navigator = new DocumentNavigator({ container: emptyContainer });
const headings = navigator.extractHeadings();
this.expect(headings).toHaveLength(0);
await navigator.render();
const navList = navigator.findElement('.navigator-list');
this.expect(navList.children).toHaveLength(0);
// Should show empty state message
const emptyMessage = navigator.findElement('.navigator-empty');
this.expect(emptyMessage).toBeTruthy();
// Cleanup
document.body.removeChild(emptyContainer);
});
// Export test runner for use in HTML
window.runDocumentNavigatorTests = () => runner.run();
console.log('📋 DocumentNavigator TDD Test Suite loaded. Run with: runDocumentNavigatorTests()');
export { runner };

View File

@@ -0,0 +1,342 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DocumentNavigator Live Demo</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
line-height: 1.6;
max-width: 800px;
margin: 0 auto;
padding: 2rem;
color: #333;
}
.demo-header {
text-align: center;
background: #f8f9fa;
padding: 2rem;
border-radius: 8px;
margin-bottom: 2rem;
}
.demo-content {
margin-top: 3rem;
}
h1, h2, h3 {
scroll-margin-top: 100px; /* Account for navigator */
}
h1 {
color: #2c3e50;
border-bottom: 3px solid #3498db;
padding-bottom: 0.5rem;
}
h2 {
color: #34495e;
margin-top: 3rem;
}
h3 {
color: #7f8c8d;
margin-top: 2rem;
}
.content-section {
margin-bottom: 3rem;
}
.highlight {
background: #fff3cd;
padding: 1rem;
border-radius: 4px;
border-left: 4px solid #ffc107;
margin: 1rem 0;
}
code {
background: #f8f9fa;
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-family: 'Monaco', 'Consolas', monospace;
}
</style>
</head>
<body>
<div class="demo-header">
<h1>📋 DocumentNavigator Live Demo</h1>
<p>This page demonstrates the Substack-style floating navigation widget in action.</p>
<p><strong>Look for the hamburger menu (☰) on the left side!</strong></p>
<div class="highlight">
<strong>Features to test:</strong><br>
• Click the hamburger menu to expand navigation<br>
• Click any heading in the navigator to jump to it<br>
• Scroll and watch the current section highlight<br>
• Try keyboard shortcuts (Enter/Space to toggle, Escape to close)<br>
• Resize window to test responsive behavior
</div>
</div>
<div id="markdown-content" class="demo-content">
<h1 id="introduction">1. Introduction to MarkiTect</h1>
<div class="content-section">
<p>MarkiTect is an advanced markdown processing engine that provides sophisticated document management capabilities. This demo showcases the DocumentNavigator widget, which provides Substack-style navigation for long-form documents.</p>
<p>The navigator automatically extracts headings from your content and builds a hierarchical table of contents that floats elegantly on the side of your document.</p>
</div>
<h2 id="features">1.1 Core Features</h2>
<div class="content-section">
<p>The DocumentNavigator widget includes numerous advanced features designed for optimal user experience:</p>
<ul>
<li><strong>Automatic Heading Detection</strong>: Scans document for H1, H2, H3 elements</li>
<li><strong>Hierarchical Structure</strong>: Maintains proper heading hierarchy with indentation</li>
<li><strong>Scroll Spy</strong>: Highlights current section as you scroll</li>
<li><strong>Smooth Navigation</strong>: Animated scrolling to clicked sections</li>
<li><strong>Responsive Design</strong>: Auto-hides on mobile devices</li>
</ul>
</div>
<h3 id="responsive">1.1.1 Responsive Behavior</h3>
<div class="content-section">
<p>The navigator intelligently adapts to different screen sizes. On desktop computers, it remains visible as a floating panel. On mobile devices, it automatically hides to preserve screen real estate for content.</p>
<p>Try resizing your browser window to see this behavior in action. The navigator will disappear when the viewport becomes narrow (under 768px wide).</p>
</div>
<h3 id="accessibility">1.1.2 Accessibility Features</h3>
<div class="content-section">
<p>The DocumentNavigator is built with accessibility in mind:</p>
<ul>
<li>Full keyboard navigation support</li>
<li>ARIA labels and proper semantic markup</li>
<li>Screen reader compatibility</li>
<li>High contrast hover states</li>
<li>Focus management</li>
</ul>
</div>
<h2 id="implementation">1.2 Implementation Details</h2>
<div class="content-section">
<p>The DocumentNavigator is implemented as a modular ES6 class that extends our base UIWidget class. This follows the planned plugin architecture for MarkiTect widgets.</p>
<p>Key implementation highlights include:</p>
<ul>
<li><code>extractHeadings()</code> - Scans DOM for heading elements</li>
<li><code>buildNavigationTree()</code> - Creates hierarchical structure</li>
<li><code>handleScroll()</code> - Manages scroll spy functionality</li>
<li><code>navigateToHeading()</code> - Handles smooth scrolling</li>
</ul>
</div>
<h1 id="architecture">2. Widget Architecture</h1>
<div class="content-section">
<p>The DocumentNavigator follows a clean architectural pattern that separates concerns and provides maximum flexibility for customization and extension.</p>
<p>The widget is designed as part of a larger plugin ecosystem that will allow developers to create custom UI components that can be loaded dynamically and configured independently.</p>
</div>
<h2 id="base-classes">2.1 Base Class Hierarchy</h2>
<div class="content-section">
<p>Our widget system is built on a foundation of base classes that provide common functionality:</p>
<ul>
<li><strong>Widget</strong>: Core functionality (events, state, lifecycle)</li>
<li><strong>UIWidget</strong>: DOM manipulation and visual behavior</li>
<li><strong>InteractiveWidget</strong>: Event handling and user interaction</li>
</ul>
<p>DocumentNavigator extends UIWidget directly since it doesn't require complex interaction handling beyond basic click and keyboard events.</p>
</div>
<h3 id="events">2.1.1 Event System</h3>
<div class="content-section">
<p>The widget uses a custom event system built on the native EventTarget API. This allows for clean separation of concerns and easy integration with other components.</p>
<p>Key events emitted by DocumentNavigator:</p>
<ul>
<li><code>rendered</code> - Widget has been rendered to DOM</li>
<li><code>navigate</code> - User navigated to a heading</li>
<li><code>toggle</code> - Widget was expanded or collapsed</li>
<li><code>theme-changed</code> - Theme was changed</li>
<li><code>destroyed</code> - Widget was destroyed</li>
</ul>
</div>
<h3 id="state">2.1.2 State Management</h3>
<div class="content-section">
<p>State management is handled through a simple Map-based system that provides reactive updates and event emission when state changes occur.</p>
<p>This approach is lightweight but powerful enough for most widget use cases while remaining debuggable and predictable.</p>
</div>
<h2 id="plugin-system">2.2 Plugin System Integration</h2>
<div class="content-section">
<p>While the current implementation works standalone, it's designed to integrate seamlessly with our planned plugin system. The plugin definition includes:</p>
<ul>
<li>Metadata and versioning information</li>
<li>Dependency declarations</li>
<li>Default configuration options</li>
<li>Lifecycle hooks</li>
<li>Theme variants</li>
<li>Development helpers</li>
</ul>
</div>
<h1 id="usage">3. Usage Examples</h1>
<div class="content-section">
<p>The DocumentNavigator can be used in several ways, from simple instantiation to advanced configuration with custom themes and behavior.</p>
</div>
<h2 id="basic-usage">3.1 Basic Usage</h2>
<div class="content-section">
<p>The simplest way to use DocumentNavigator is with default settings:</p>
<pre><code>const navigator = new DocumentNavigator();
await navigator.initialize();
await navigator.render();</code></pre>
<p>This creates a navigator with default settings that will scan the entire document for headings and display them in a collapsible panel on the left side.</p>
</div>
<h2 id="advanced-usage">3.2 Advanced Configuration</h2>
<div class="content-section">
<p>For more control, you can specify detailed configuration options:</p>
<pre><code>const navigator = new DocumentNavigator({
position: 'right',
collapsed: false,
theme: 'dark',
maxHeadingLevel: 4,
enableScrollSpy: true,
smoothScroll: true
});</code></pre>
<p>This creates a navigator on the right side that starts expanded, includes H4 headings, and uses the dark theme.</p>
</div>
<h3 id="theming">3.2.1 Custom Theming</h3>
<div class="content-section">
<p>The navigator supports multiple built-in themes and can be extended with custom themes. The theming system integrates with MarkiTect's document themes for consistent styling.</p>
<p>Available themes include <code>default</code>, <code>dark</code>, and <code>minimal</code>, each optimized for different use cases and aesthetics.</p>
</div>
<h1 id="testing">4. Testing and Quality</h1>
<div class="content-section">
<p>The DocumentNavigator implementation follows Test-Driven Development (TDD) methodology with comprehensive test coverage ensuring reliability and maintainability.</p>
</div>
<h2 id="test-coverage">4.1 Test Coverage</h2>
<div class="content-section">
<p>Our test suite covers all major functionality:</p>
<ul>
<li>Widget instantiation and configuration</li>
<li>DOM rendering and element creation</li>
<li>Heading extraction and hierarchy building</li>
<li>Navigation and smooth scrolling</li>
<li>Expand/collapse animations</li>
<li>Scroll spy functionality</li>
<li>Responsive behavior</li>
<li>Keyboard navigation</li>
<li>Event emission</li>
<li>Edge cases and error handling</li>
</ul>
</div>
<h2 id="performance">4.2 Performance Considerations</h2>
<div class="content-section">
<p>The navigator is optimized for performance with several key strategies:</p>
<ul>
<li><strong>Throttled Scroll Events</strong>: Scroll spy updates are throttled to 100ms intervals</li>
<li><strong>Efficient DOM Queries</strong>: Heading extraction is done once and cached</li>
<li><strong>Conditional Rendering</strong>: Navigator only renders if minimum heading count is met</li>
<li><strong>Memory Management</strong>: Proper cleanup prevents memory leaks</li>
<li><strong>Responsive Loading</strong>: Navigator automatically hides on mobile to save resources</li>
</ul>
</div>
<h1 id="conclusion">5. Conclusion</h1>
<div class="content-section">
<p>The DocumentNavigator widget successfully brings Substack-style navigation to MarkiTect documents. It provides an intuitive, accessible, and performant way for users to navigate long-form content.</p>
<p>The implementation demonstrates the power of our widget architecture approach, with clean separation of concerns, comprehensive testing, and excellent extensibility for future enhancements.</p>
<p><strong>Scroll back to the top and try the navigation features!</strong> The hamburger menu should be visible on the left side of your screen.</p>
</div>
</div>
<!-- Load widget classes -->
<script type="module">
// Import our widget classes
import { Widget } from '../widgets/base/Widget.js';
import { UIWidget } from '../widgets/base/UIWidget.js';
import { DocumentNavigator } from '../widgets/navigation/DocumentNavigator.js';
// Make classes available globally for demo
window.Widget = Widget;
window.UIWidget = UIWidget;
window.DocumentNavigator = DocumentNavigator;
// Initialize navigator on page load
document.addEventListener('DOMContentLoaded', async () => {
console.log('🧭 Initializing DocumentNavigator demo...');
try {
// Create navigator with demo settings
const navigator = new DocumentNavigator({
container: document.getElementById('markdown-content'),
position: 'left',
collapsed: true,
theme: 'default',
enableScrollSpy: true,
autoHide: true,
maxHeadingLevel: 3,
minHeadings: 1 // Show navigator even with few headings for demo
});
// Initialize and render
await navigator.initialize();
const element = await navigator.render();
if (element) {
console.log('✅ DocumentNavigator initialized successfully!');
console.log(` Found ${navigator.headings.length} headings`);
console.log(' Click the hamburger menu (☰) to expand navigation');
} else {
console.log(' DocumentNavigator not rendered (insufficient headings)');
}
// Add some debugging helpers
window.navigator = navigator;
window.testNavigator = {
expand: () => navigator.expand(),
collapse: () => navigator.collapse(),
toggle: () => navigator.toggle(),
showHeadings: () => console.table(navigator.headings),
showTree: () => console.log(navigator.navigationTree)
};
console.log('🔧 Debugging helpers available:');
console.log(' window.navigator - navigator instance');
console.log(' window.testNavigator - helper functions');
} catch (error) {
console.error('❌ DocumentNavigator initialization failed:', error);
}
});
</script>
</body>
</html>