Files
testdrive-jsui/docs/prototypes/StatusPsychadelic.html
tegwick 1fe4b6b9fa refactor: complete post-migration cleanup
Implemented all cleanup items from CLEANUP_REPORT.md:

Legacy Code Removal:
- Removed document-controls-legacy.js wrapper
- Updated 4 test files to use DocumentControls directly
- Updated scripts/list_components.py acronym mappings
- Updated tests/test_component_listing.py expectations

Archive and Organization:
- Moved relicts/ to docs/prototypes/ with README explaining history
- Moved MIGRATION_STATUS.md to docs/migration/
- Removed IMPLEMENTATION_NOTES.md legacy references

Test Verification:
- All 68 JavaScript tests passing (Jest)
- All 3 Python component tests passing
- No breaking changes to functionality

The codebase is now cleaner with no legacy wrappers or empty
directories. Migration is complete and documented.

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

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

2316 lines
91 KiB
HTML
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog</title>
<style>
body {
font-family: Georgia, Times New Roman, serif;
max-width: 650px;
margin: 0 auto;
padding: 2rem;
line-height: 1.6;
color: #333333;
background-color: #ffffff;
}
#markdown-content {
min-height: 200px;
}
h1, h2, h3, h4, h5, h6 {
color: #333333;
margin-top: 2rem;
margin-bottom: 1rem;
}
h1 {
text-align: center;
font-size: 2.2em;
border-bottom: 2px solid #333333;
padding-bottom: 0.5rem;
}
p {
text-align: justify;
margin-bottom: 1.2rem;
}
pre {
background-color: #f6f8fa;
color: #333333;
padding: 1rem;
border-radius: 6px;
overflow-x: auto;
border: 1px solid #d0d7de;
}
code {
background-color: #f6f8fa;
color: #333333;
padding: 0.2em 0.4em;
border-radius: 3px;
font-size: 0.9em;
}
pre code {
background: none;
padding: 0;
}
blockquote {
border-left: 4px solid #dfe2e5;
margin: 0;
padding-left: 1rem;
color: #6a737d;
}
table {
font-size: 0.85em;
border-collapse: collapse;
margin: 1rem 0;
width: 100%;
border: 1px solid #d0d7de;
}
th, td {
font-size: inherit;
border: 1px solid #d0d7de;
padding: 0.5rem;
text-align: left;
}
th {
background-color: #f6f8fa;
font-weight: 600;
}
a {
color: #777777;
text-decoration: underline;
}
a:hover {
color: #999999;
}
.markitect-edit-mode .ui-edit-floater-panel {
background: linear-gradient(45deg, #ff6b35, #f7931e, #ffd23f, #06ffa5);
border: 1px solid #ff1493;
box-shadow: 0 4px 12px rgba(255,20,147,0.4);
color: #ffffff;
}
.markitect-edit-mode .ui-edit-floater-header {
color: #ffffff;
}
.markitect-edit-mode .ui-edit-floater-header h3 {
color: #ffffff;
}
.markitect-edit-mode .ui-edit-inline-panel {
background: linear-gradient(45deg, #ff6b35, #f7931e, #ffd23f, #06ffa5);
border: 1px solid #ff1493;
box-shadow: 0 2px 8px rgba(255,20,147,0.4);
color: #ffffff;
border-radius: 8px;
padding: 12px;
margin: 8px 0;
}
.markitect-edit-mode .ui-edit-button {
background: rgba(255,255,255,0.2);
color: #ffffff;
border: 1px solid #ff1493;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
min-width: 70px;
font-weight: 500;
transition: all 0.2s;
}
.markitect-edit-mode .ui-edit-button:hover {
background: rgba(255,20,147,0.3);
}
.markitect-edit-mode .ui-edit-button:active,
.markitect-edit-mode .ui-edit-button.active {
background: rgba(255,20,147,0.5);
}
.markitect-edit-mode .ui-edit-button-accept {
background: #4caf50;
color: white;
}
.markitect-edit-mode .ui-edit-button-accept:hover {
background: #388e3c;
}
.markitect-edit-mode .ui-edit-button-cancel {
background: #f44336;
color: white;
}
.markitect-edit-mode .ui-edit-button-cancel:hover {
background: #d32f2f;
}
.markitect-edit-mode .ui-edit-button-reset {
background: #ff9800;
color: white;
}
.markitect-edit-mode .ui-edit-button-reset:hover {
background: #f57c00;
}
.markitect-edit-mode .ui-edit-button-secondary {
background: #6c757d;
color: white;
}
.markitect-edit-mode .ui-edit-button-secondary:hover {
background: #545b62;
}
.markitect-edit-mode .ui-edit-section-frame {
border: 2px solid #ff1493;
box-shadow: 0 0 0 3px #ff149333;
}
.markitect-edit-mode .ui-edit-textarea {
border: 1px solid #ff1493;
color: #ffffff;
background: rgba(255,255,255,0.2);
}
.markitect-edit-mode .ui-edit-textarea:focus {
border-color: #ff1493;
box-shadow: 0 0 0 2px #ff149333;
}
.markitect-edit-mode .ui-edit-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s, visibility 0.3s;
}
.markitect-edit-mode .ui-edit-modal-overlay.active {
opacity: 1;
visibility: visible;
}
.markitect-edit-mode .ui-edit-modal {
background: linear-gradient(45deg, #ff6b35, #f7931e, #ffd23f, #06ffa5);
border: 1px solid #ff1493;
box-shadow: 0 8px 32px rgba(255,20,147,0.4);
color: #ffffff;
border-radius: 8px;
max-width: 600px;
max-height: 80vh;
width: 90%;
overflow: hidden;
transform: scale(0.9) translateY(-20px);
transition: transform 0.3s;
}
.markitect-edit-mode .ui-edit-modal-overlay.active .ui-edit-modal {
transform: scale(1) translateY(0);
}
.markitect-edit-mode .ui-edit-modal-header {
padding: 20px 24px 16px;
border-bottom: 1px solid #ff1493;
display: flex;
justify-content: space-between;
align-items: center;
}
.markitect-edit-mode .ui-edit-modal-title {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #ffffff;
}
.markitect-edit-mode .ui-edit-modal-close {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #ffffff;
padding: 4px;
border-radius: 4px;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s;
}
.markitect-edit-mode .ui-edit-modal-close:hover {
background: rgba(255,20,147,0.3);
}
.markitect-edit-mode .ui-edit-modal-body {
padding: 20px 24px;
overflow-y: auto;
max-height: 60vh;
}
.markitect-edit-mode .ui-edit-modal-content {
white-space: pre-line;
line-height: 1.5;
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif;
font-size: 14px;
}
.markitect-edit-mode .ui-edit-modal-section {
margin-bottom: 16px;
}
.markitect-edit-mode .ui-edit-modal-section:last-child {
margin-bottom: 0;
}
.markitect-edit-mode .ui-edit-modal-section-title {
font-weight: 600;
margin-bottom: 8px;
color: #ffffff;
}
.markitect-edit-mode .ui-edit-modal-footer {
padding: 16px 24px 20px;
border-top: 1px solid #ff1493;
text-align: right;
}
/* Confirmation Dialog Styles */
.markitect-edit-mode .ui-edit-confirmation-modal {
max-width: 500px;
}
.markitect-edit-mode .ui-edit-confirmation-content {
font-size: 16px;
line-height: 1.5;
margin-bottom: 24px;
color: #ffffff;
}
.markitect-edit-mode .ui-edit-confirmation-warning {
background: linear-gradient(45deg, #ffa500, #ff8c00);
border: 1px solid #ff1493;
color: #ffffff;
padding: 12px 16px;
border-radius: 6px;
margin: 16px 0;
font-size: 14px;
}
.markitect-edit-mode .ui-edit-confirmation-buttons {
display: flex;
gap: 12px;
justify-content: flex-end;
}
.markitect-edit-mode .ui-edit-button-confirm {
background: linear-gradient(45deg, #ff0066, #cc0044);
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s, transform 0.1s;
}
.markitect-edit-mode .ui-edit-button-confirm:hover {
background: linear-gradient(45deg, #ff3388, #dd1155);
transform: translateY(-1px);
}
.markitect-edit-mode .ui-edit-button-confirm:active {
transform: translateY(0);
}
.markitect-edit-mode .ui-edit-button-confirm:focus {
outline: 2px solid #ff1493;
outline-offset: 2px;
}
.markitect-edit-mode .ui-edit-button-cancel {
background: linear-gradient(45deg, #8a2be2, #4b0082);
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s, transform 0.1s;
}
.markitect-edit-mode .ui-edit-button-cancel:hover {
background: linear-gradient(45deg, #9932cc, #6a1a9a);
transform: translateY(-1px);
}
.markitect-edit-mode .ui-edit-button-cancel:active {
transform: translateY(0);
}
.markitect-edit-mode .ui-edit-button-cancel:focus {
outline: 2px solid #ff1493;
outline-offset: 2px;
}
/* Document Scroll Indicators */
.ui-scroll-indicator {
position: fixed;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 30px;
background: linear-gradient(45deg, #ff6b35, #f7931e, #ffd23f, #06ffa5);
border: 1px solid #ff1493;
border-radius: 15px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.2s ease, background-color 0.2s ease;
z-index: 1000;
box-shadow: 0 4px 12px rgba(255,20,147,0.4);
}
.ui-scroll-indicator:hover {
transform: translateX(-50%) scale(1.05);
}
.ui-scroll-indicator:not(.disabled):hover {
background: rgba(255,20,147,0.3);
}
.ui-scroll-indicator.active {
opacity: 0.9;
visibility: visible;
}
.ui-scroll-indicator.disabled {
background: rgba(255,20,147,0.5);
cursor: not-allowed;
opacity: 0.6;
}
.ui-scroll-indicator.disabled:hover {
transform: translateX(-50%);
background: rgba(255,20,147,0.5);
}
.ui-scroll-indicator-up {
top: 20px;
}
.ui-scroll-indicator-down {
bottom: 20px;
}
.ui-scroll-indicator::before {
content: '';
width: 0;
height: 0;
border-style: solid;
transition: border-color 0.2s ease;
}
.ui-scroll-indicator-up::before {
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-bottom: 12px solid #ffffff;
}
.ui-scroll-indicator-down::before {
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-top: 12px solid #ffffff;
}
.ui-scroll-indicator.disabled.ui-scroll-indicator-up::before {
border-bottom-color: linear-gradient(45deg, #8a2be2, #4b0082);
}
.ui-scroll-indicator.disabled.ui-scroll-indicator-down::before {
border-top-color: linear-gradient(45deg, #8a2be2, #4b0082);
}
/* Insert Mode Specific Styles */
.markitect-insert-mode .ui-edit-floater-panel {
background: linear-gradient(45deg, #ff6b35, #f7931e, #ffd23f, #06ffa5);
border: 1px solid #ff1493;
box-shadow: 0 4px 12px rgba(255,20,147,0.4);
color: #ffffff;
}
.markitect-insert-mode .ui-edit-floater-header {
color: #ffffff;
}
.markitect-insert-mode .ui-edit-floater-header h3 {
color: #ffffff;
}
.markitect-insert-mode .ui-edit-inline-panel {
background: linear-gradient(45deg, #ff6b35, #f7931e, #ffd23f, #06ffa5);
border: 1px solid #ff1493;
box-shadow: 0 2px 8px rgba(255,20,147,0.4);
color: #ffffff;
border-radius: 8px;
padding: 12px;
margin: 8px 0;
}
.markitect-insert-mode .ui-insert-protected-panel {
border-left: 4px solid #ff9800;
}
.markitect-insert-mode .ui-insert-heading-display {
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
font-size: 14px;
font-weight: bold;
padding: 8px 12px;
background: linear-gradient(45deg, #ffa500, #ff8c00);
border: 1px solid #ff1493;
border-radius: 4px;
border-left: 4px solid #007bff;
color: #ffffff;
margin-bottom: 8px;
}
.markitect-insert-mode .ui-insert-content-editor {
border: 1px solid #ff1493;
color: #ffffff;
background: rgba(255,255,255,0.2);
}
.markitect-insert-mode .ui-insert-content-editor:focus {
border-color: #ff1493;
box-shadow: 0 0 0 2px #ff149333;
}
.markitect-insert-mode .ui-edit-button {
background: rgba(255,255,255,0.2);
color: #ffffff;
border: 1px solid #ff1493;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
min-width: 70px;
font-weight: 500;
transition: all 0.2s;
}
.markitect-insert-mode .ui-edit-button:hover {
background: rgba(255,20,147,0.3);
}
.markitect-insert-mode .ui-edit-button:active,
.markitect-insert-mode .ui-edit-button.active {
background: rgba(255,20,147,0.5);
}
.markitect-insert-mode .ui-edit-button-accept {
background: #4caf50;
color: white;
}
.markitect-insert-mode .ui-edit-button-accept:hover {
background: #388e3c;
}
.markitect-insert-mode .ui-edit-button-cancel {
background: #f44336;
color: white;
}
.markitect-insert-mode .ui-edit-button-cancel:hover {
background: #d32f2f;
}
.markitect-insert-mode .ui-edit-button-reset {
background: #ff9800;
color: white;
}
.markitect-insert-mode .ui-edit-button-reset:hover {
background: #f57c00;
}
.markitect-insert-mode .ui-edit-section-frame {
border: 2px solid #ff1493;
box-shadow: 0 0 0 3px #ff149333;
}
/* Modal Overlay and Dialog Styles for Insert Mode */
.markitect-insert-mode .ui-edit-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s, visibility 0.3s;
}
.markitect-insert-mode .ui-edit-modal-overlay.active {
opacity: 1;
visibility: visible;
}
.markitect-insert-mode .ui-edit-modal {
background: linear-gradient(45deg, #ff6b35, #f7931e, #ffd23f, #06ffa5);
border: 1px solid #ff1493;
box-shadow: 0 8px 32px rgba(255,20,147,0.4);
color: #ffffff;
border-radius: 8px;
max-width: 600px;
max-height: 80vh;
width: 90%;
overflow: hidden;
transform: scale(0.9) translateY(-20px);
transition: transform 0.3s;
}
.markitect-insert-mode .ui-edit-modal-overlay.active .ui-edit-modal {
transform: scale(1) translateY(0);
}
.markitect-insert-mode .ui-edit-modal-header {
padding: 20px 24px 16px;
border-bottom: 1px solid #ff1493;
display: flex;
justify-content: space-between;
align-items: center;
}
.markitect-insert-mode .ui-edit-modal-title {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #ffffff;
}
.markitect-insert-mode .ui-edit-modal-close {
background: transparent;
border: none;
font-size: 24px;
cursor: pointer;
color: #ffffff;
padding: 0;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: background-color 0.2s;
}
.markitect-insert-mode .ui-edit-modal-close:hover {
background: rgba(255,20,147,0.3);
}
.markitect-insert-mode .ui-edit-modal-body {
padding: 20px 24px;
max-height: 400px;
overflow-y: auto;
color: #ffffff;
}
.markitect-insert-mode .ui-edit-modal-section {
margin-bottom: 8px;
color: #ffffff;
}
.markitect-insert-mode .ui-edit-modal-footer {
padding: 16px 24px 20px;
border-top: 1px solid #ff1493;
text-align: right;
}
/* Confirmation Dialog Styles for Insert Mode */
.markitect-insert-mode .ui-edit-confirmation-modal {
max-width: 500px;
}
.markitect-insert-mode .ui-edit-confirmation-content {
font-size: 16px;
line-height: 1.5;
margin-bottom: 24px;
color: #ffffff;
}
.markitect-insert-mode .ui-edit-confirmation-warning {
background: linear-gradient(45deg, #ffa500, #ff8c00);
color: #ffffff;
border: 1px solid #ff1493;
border-radius: 6px;
padding: 12px 16px;
margin: 16px 0;
font-size: 14px;
line-height: 1.4;
}
.markitect-insert-mode .ui-edit-confirmation-buttons {
display: flex;
gap: 12px;
justify-content: flex-end;
margin-top: 24px;
}
.markitect-insert-mode .ui-edit-button-confirm {
background: #dc3545;
color: white;
border: 1px solid #dc3545;
padding: 10px 20px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.markitect-insert-mode .ui-edit-button-confirm:hover {
background: #c82333;
border-color: #bd2130;
}
.markitect-insert-mode .ui-edit-button-cancel {
background: rgba(255,255,255,0.2);
color: #ffffff;
border: 1px solid #ff1493;
padding: 10px 20px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.markitect-insert-mode .ui-edit-button-cancel:hover {
background: rgba(255,20,147,0.3);
}
</style>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"
onload="window.markitectMarkedLoaded = true"
onerror="window.markitectMarkedError = true"></script>
</head>
<body class="markitect-edit-mode">
<div id="markdown-content"></div>
<script>
const markdownContent = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n## [0.6.0] - 2025-10-28\n\n### Added\n- **Custom Status Modal System**: Professional theme-consistent status dialogs replacing browser alerts with proper branding and accessibility\n- **HTML Generation Dogtag**: Automatic attribution with timestamp and username linking for generated HTML documents\n- **Enhanced Link Navigation**: All document links now open in new tabs without triggering edit mode for improved user experience\n- **Comprehensive UI Framework Documentation**: Complete guide (UserInterfaceFramework.md) for consistent UI development patterns\n- **Database Integration**: Added store_document method to CleanDocumentManager with proper front matter parsing\n- **Enhanced AST Processing**: Improved title extraction from front matter and heading detection with cache file generation\n\n### Changed\n- **Complete Document Manager Cleanup**: Removed 2000+ lines of legacy code while maintaining full backward compatibility\n- **Clean Architecture Implementation**: DocumentManager now extends CleanDocumentManager with clean wrapper pattern\n- **Improved Error Handling**: Enhanced validation and graceful error recovery throughout the system\n- **Standardized CSS Naming**: Consistent class naming conventions across all UI components\n\n### Fixed\n- **Test Suite Compatibility**: Updated all tests to work with clean implementation architecture\n- **JavaScript Syntax Issues**: Resolved template literal and string escaping problems in generated HTML\n- **Link Behavior**: Fixed issue where document links were incorrectly triggering edit mode\n- **Front Matter Parsing**: Proper integration with FrontMatterParser for metadata extraction\n\n### Technical\n- Added --nodogtag CLI option for clean output when attribution is not desired\n- Enhanced ingest_file method with proper title extraction from front matter and headings\n- Implemented theme-aware modal overlay patterns with proper CSS styling\n- Fixed CSS escape sequences and JavaScript syntax validation issues\n\n## [0.5.0] - 2025-10-26\n\n### Added\n- **Clean TDD-Driven Editor Architecture**: Complete rewrite with object-oriented JavaScript architecture featuring Section, SectionManager, and DOMRenderer classes\n- **Enhanced Test Framework**: Comprehensive testing framework with clean separation of concerns for robust development\n- **Multiple Concurrent Section Editing**: Support for editing multiple sections simultaneously with intelligent management\n- **Intelligent Section Splitting**: Advanced heading detection and section management capabilities\n- **Four-Layer Content Management**: Sophisticated content state management (original, current, pending, editing layers)\n- **Enhanced Status Dialog**: Repository info display showing version, git commit status, and actual save filename\n- **Elegant Slide-in Control Panel**: Floating control panel for edit mode with improved UX\n- **Intelligent Auto-sizing Textarea**: Optimal editing experience with smart textarea resizing\n- **Enhanced Empty Line Preservation**: Better markdown structure preservation with automatic paragraph separation\n\n### Fixed\n- **Textarea Sizing and Font Preservation**: Resolved sizing issues and maintained consistent font rendering\n- **Markdown Structure Preservation**: Fixed roundtrip formatting issues in save functionality\n- **Section Duplication Prevention**: Eliminated duplicate sections when saving edited content\n- **Section Position Preservation**: Prevented unwanted section jumping during editing\n- **CSS Embedding Issues**: Resolved import errors in HTML template generation\n- **Control Panel UX**: Hidden control ribbon when panel is expanded for cleaner interface\n\n### Changed\n- **Action Semantics**: Proper implementation of Accept, Cancel, and Reset operations\n- **Global Reset Functionality**: Enhanced reset capabilities across the editor\n- **Makefile Organization**: Reorganized installation targets for better user experience\n\n### Technical Improvements\n- Complete legacy editor system replacement\n- Test-driven development approach implementation\n- Enhanced UI/UX with better section positioning\n- Improved content management workflow\n\n## [0.4.0] - 2025-10-25\n\n### Added\n- feat: add comprehensive testing and error tracking for edit mode\n\n### Fixed\n- fix: resolve md-render --edit functionality and add enhanced version tracking\n- fix: resolve critical JavaScript syntax errors in md-render --edit\n- fix: resolve md-ingest Path object conversion error\n\n### Other\n- chore: clean up repository documentation files for release\n\n## [0.3.0] - 2025-10-25\n\n### Added\n- **Kaizen-agentic Framework Integration**: Integrated capability submodule for enhanced development workflow\n- **Test Reorganization System**: Reorganized tests by capability with improved modularity\n- **Capability Inclusion Management**: Comprehensive system for managing capability inclusions\n- **Todofile System**: Implemented todofile system to replace NEXT.md for better task tracking\n\n### Changed\n- **Directory Organization**: Logical separation and reorganization of project structure\n- **Historical File Organization**: Cleaner structure with better file organization\n\n## [0.2.0] - 2025-10-20\n\n### Added\n- **GraphQL Interface**: Advanced querying capabilities with full GraphQL implementation\n- **Full-text Search**: FTS5 backend integration for powerful search functionality\n- **Plugin Architecture**: Extensible framework with comprehensive plugin support\n- **Query Paradigms**: 14 different query paradigms for flexible data access\n- **Cost Management**: Activity tracking and resource cost management\n- **Template Rendering**: Template system with validation capabilities\n- **CLI Consolidation**: Unified command-line interface\n- **Production Asset Management**: Content-addressable storage system\n- **17 Kaizen-agentic Agents**: Integrated development agent ecosystem\n\n### Changed\n- **Performance Optimization**: 60-85% performance improvement through system optimization\n- **Error Handling**: Enterprise-grade error handling and recovery mechanisms\n- **Resource Management**: Memory-efficient and scalable architecture\n\n### Fixed\n- **Cross-platform Validation**: Comprehensive validation for Unix/Windows/macOS\n- **Type Safety**: Enhanced type safety and security validation\n- **Test Coverage**: 1983/1983 tests passing (100% success rate)\n\n## [0.1.0] - 2025-10-15\n\n### Added\n- **Development Infrastructure**: Comprehensive Makefile for development workflow\n- **Project Documentation**: ProjectStatusDigest.md and ProjectDiary.md for tracking\n- **TDD Workspace System**: Structured Test-Driven Development workflow implementation\n- **Issue Management**: Gitea integration for issue tracking and management\n- **Virtual Environment Management**: Enhanced venv detection and shell activation\n- **Wiki Integration**: Submodule tracking for project documentation\n- **Core Repository Setup**: Initial project structure and configuration\n\n### Changed\n- **Build System**: Enhanced build targets with venv Python and PYTHONPATH support\n- **Target Naming**: Renamed workspace targets to TDD Workspace with tdd- prefix\n\nxxx\n\n\n---\n*-- html from markdown by <a href=\"https://coulomb.social/open/MarkiTect\" target=\"_blank\">MarkiTect</a> on 2025-10-28 23:52:58 by <a href=\"https://coulomb.social/open/worsch\" target=\"_blank\">worsch</a>*";
const MARKITECT_EDIT_MODE = true;
const MARKITECT_EDITOR_CONFIG = {
mode: 'edit',
theme: 'github',
keyboardShortcuts: true,
autosave: false,
sections: true,
originalFilename: 'CHANGELOG',
version: 'Markitect v0.5.0.dev+dd3a000',
repoName: 'Markitect'
};
// Make config available globally
window.editorConfig = MARKITECT_EDITOR_CONFIG;
// Clean Editor Architecture
/**
* Test-Driven Section Editor Implementation
*
* A clean, object-oriented approach to handling section editing
* that can be tested independently of the DOM.
*/
// Enums for clear state management
const EditState = Object.freeze({
ORIGINAL: 'original',
EDITING: 'editing',
MODIFIED: 'modified',
SAVED: 'saved'
});
const SectionType = Object.freeze({
HEADING: 'heading',
PARAGRAPH: 'paragraph',
LIST: 'list',
CODE: 'code',
BLOCKQUOTE: 'blockquote'
});
/**
* Section class - Core business logic for a single editable section
*/
class Section {
constructor(id, originalMarkdown, sectionType = SectionType.PARAGRAPH) {
this.id = id;
this.originalMarkdown = originalMarkdown;
this.currentMarkdown = originalMarkdown;
this.editingMarkdown = null;
this.pendingMarkdown = null;
this.sectionType = sectionType;
this.headingLevel = Section.detectHeadingLevel(originalMarkdown);
this.state = EditState.ORIGINAL;
this.domElement = null;
this.lastSaved = null;
this.created = new Date();
}
startEdit() {
if (this.state === EditState.EDITING) {
throw new Error(`Section ${this.id} is already being edited`);
}
this.editingMarkdown = this.pendingMarkdown || this.currentMarkdown;
this.state = EditState.EDITING;
return this.editingMarkdown;
}
updateContent(markdown) {
if (this.state !== EditState.EDITING) {
throw new Error(`Section ${this.id} is not in editing state`);
}
this.editingMarkdown = markdown;
}
acceptChanges() {
if (this.state !== EditState.EDITING) {
throw new Error(`Section ${this.id} is not in editing state`);
}
this.currentMarkdown = this.editingMarkdown;
this.editingMarkdown = null;
this.pendingMarkdown = null;
this.state = EditState.SAVED;
this.lastSaved = new Date();
return this.currentMarkdown;
}
cancelChanges() {
if (this.state !== EditState.EDITING) {
throw new Error(`Section ${this.id} is not in editing state`);
}
this.editingMarkdown = null;
if (this.pendingMarkdown !== null) {
this.state = EditState.MODIFIED;
return this.pendingMarkdown;
} else if (this.lastSaved !== null) {
this.state = EditState.SAVED;
return this.currentMarkdown;
} else {
this.state = this.hasChanges() ? EditState.MODIFIED : EditState.ORIGINAL;
return this.currentMarkdown;
}
}
resetToOriginal() {
this.currentMarkdown = this.originalMarkdown;
this.editingMarkdown = null;
this.pendingMarkdown = null;
this.lastSaved = null;
this.state = EditState.ORIGINAL;
return this.originalMarkdown;
}
stopEditing() {
if (this.state !== EditState.EDITING) {
return this.state;
}
// If we have editing changes that differ from current content, preserve them as pending
if (this.editingMarkdown && this.editingMarkdown !== this.currentMarkdown) {
this.pendingMarkdown = this.editingMarkdown;
this.state = EditState.MODIFIED; // Has pending changes
} else {
// No changes made during this edit session
this.pendingMarkdown = null;
if (this.lastSaved !== null) {
this.state = EditState.SAVED;
} else {
this.state = this.hasChanges() ? EditState.MODIFIED : EditState.ORIGINAL;
}
}
this.editingMarkdown = null;
return this.state;
}
hasChanges() {
return this.currentMarkdown !== this.originalMarkdown;
}
isEditing() {
return this.state === EditState.EDITING;
}
getStatus() {
return {
id: this.id,
state: this.state,
hasChanges: this.hasChanges(),
isEditing: this.isEditing(),
contentLength: this.currentMarkdown.length,
lastSaved: this.lastSaved,
sectionType: this.sectionType
};
}
static generateId(content, position) {
const str = content.substring(0, 100) + position.toString();
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return `section_${Math.abs(hash)}_${position}`;
}
static detectType(markdown) {
const trimmed = markdown.trim();
if (trimmed.startsWith('#')) return SectionType.HEADING;
if (trimmed.startsWith('```')) return SectionType.CODE;
if (trimmed.startsWith('>')) return SectionType.BLOCKQUOTE;
if (trimmed.startsWith('-') || trimmed.startsWith('*') || /^\d+\./.test(trimmed)) {
return SectionType.LIST;
}
return SectionType.PARAGRAPH;
}
static detectHeadingLevel(markdown) {
const trimmed = markdown.trim();
const match = trimmed.match(/^(#{1,6})\s/);
return match ? match[1].length : null;
}
isHeading() {
return this.sectionType === SectionType.HEADING;
}
isProtectedHeading() {
if (!this.isHeading()) return false;
// Check if we're in insert mode and if this heading level is protected
const config = window.editorConfig || {};
const restrictedLevels = config.restrictedHeadingLevels || [];
return config.mode === 'insert' && restrictedLevels.includes(this.headingLevel);
}
getHeadingText() {
if (!this.isHeading()) return null;
// Extract first line for heading text
const firstLine = this.originalMarkdown.trim().split('\n')[0];
const match = firstLine.match(/^(#{1,6})\s+(.+)$/);
return match ? match[2] : null;
}
getHeadingContent() {
if (!this.isHeading()) return this.currentMarkdown;
const lines = this.currentMarkdown.split('\n');
// Return content after the heading line
return lines.slice(1).join('\n');
}
}
/**
* SectionManager class - Manages the collection of sections
*/
class SectionManager {
constructor() {
this.sections = new Map();
// Note: Removed single editingSection tracking to allow multiple concurrent edits
this.listeners = new Map();
}
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
emit(event, data) {
if (this.listeners.has(event)) {
this.listeners.get(event).forEach(callback => callback(data));
}
}
createSectionsFromMarkdown(markdownContent) {
const lines = markdownContent.split('\n');
const sections = [];
let currentSection = '';
let position = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const isHeading = /^#{1,6}\s/.test(line);
const isNewParagraph = line.trim() && i > 0 && !lines[i-1].trim();
const isNewSection = isHeading || isNewParagraph;
if (isNewSection && currentSection.trim()) {
const sectionId = Section.generateId(currentSection, position);
const sectionType = Section.detectType(currentSection);
const section = new Section(sectionId, currentSection.trim(), sectionType);
sections.push(section);
this.sections.set(sectionId, section);
position++;
currentSection = line;
} else {
if (currentSection) currentSection += '\n';
currentSection += line;
}
}
if (currentSection.trim()) {
const sectionId = Section.generateId(currentSection, position);
const sectionType = Section.detectType(currentSection);
const section = new Section(sectionId, currentSection.trim(), sectionType);
sections.push(section);
this.sections.set(sectionId, section);
}
this.emit('sections-created', { sections, count: sections.length });
return sections;
}
startEditing(sectionId) {
const section = this.sections.get(sectionId);
if (!section) {
throw new Error(`Section ${sectionId} not found`);
}
// Check if section is already being edited
if (section.isEditing()) {
console.log('Section already in editing state:', sectionId);
return section.editingMarkdown;
}
const content = section.startEdit();
// Note: No longer tracking single editingSection - allowing multiple
this.emit('edit-started', { sectionId, content, section: section.getStatus() });
return content;
}
updateContent(sectionId, markdown) {
const section = this.sections.get(sectionId);
if (!section) {
throw new Error(`Section ${sectionId} not found`);
}
section.updateContent(markdown);
this.emit('content-updated', { sectionId, markdown, section: section.getStatus() });
}
acceptChanges(sectionId) {
const section = this.sections.get(sectionId);
if (!section) {
throw new Error(`Section ${sectionId} not found`);
}
// For protected headings in insert mode, validate that heading hasn't changed
if (section.isProtectedHeading()) {
const originalHeadingLine = section.originalMarkdown.split('\n')[0];
const newHeadingLine = section.editingMarkdown.split('\n')[0];
if (originalHeadingLine !== newHeadingLine) {
throw new Error(`Cannot modify protected heading in insert mode. Heading level ${section.headingLevel} is read-only.`);
}
}
// Check if the edited content contains new headings that would create splits
const newContent = section.editingMarkdown;
const originalContent = section.originalMarkdown;
const shouldSplit = this.checkForSectionSplits(newContent, originalContent);
if (shouldSplit) {
// Handle section splitting
this.handleSectionSplit(sectionId, newContent);
} else {
// Normal accept without splitting
const content = section.acceptChanges();
// Note: No longer tracking single editingSection
this.emit('changes-accepted', { sectionId, content, section: section.getStatus() });
}
return section.currentMarkdown;
}
checkForSectionSplits(content, originalContent) {
if (!content) return false;
// Split by lines and check for headings
const lines = content.split('\n');
const originalLines = originalContent ? originalContent.split('\n') : [];
let newHeadingCount = 0;
let originalHeadingCount = 0;
// Count headings in new content
for (const line of lines) {
if (/^#{1,6}\s/.test(line.trim())) {
newHeadingCount++;
}
}
// Count headings in original content
for (const line of originalLines) {
if (/^#{1,6}\s/.test(line.trim())) {
originalHeadingCount++;
}
}
// Split if:
// 1. We have multiple headings now, OR
// 2. We added headings where there were none before, OR
// 3. We have more headings than we started with
return newHeadingCount > 1 ||
(originalHeadingCount === 0 && newHeadingCount > 0) ||
newHeadingCount > originalHeadingCount;
}
handleSectionSplit(originalSectionId, content) {
console.log('Splitting section:', originalSectionId);
const originalSection = this.sections.get(originalSectionId);
if (!originalSection) return;
// Accept the current changes first
originalSection.acceptChanges();
// Split the content into new sections
const newSections = this.createSectionsFromContent(content, originalSectionId);
// Get all sections as an ordered array to maintain document order
const allSectionsArray = Array.from(this.sections.values());
const originalIndex = allSectionsArray.findIndex(s => s.id === originalSectionId);
// Clear the sections map and rebuild it with proper order
this.sections.clear();
// Add sections before the original
for (let i = 0; i < originalIndex; i++) {
const section = allSectionsArray[i];
this.sections.set(section.id, section);
}
// Add the new split sections
newSections.forEach(section => {
this.sections.set(section.id, section);
});
// Add sections after the original
for (let i = originalIndex + 1; i < allSectionsArray.length; i++) {
const section = allSectionsArray[i];
this.sections.set(section.id, section);
}
// Note: No longer tracking single editingSection
// Emit event to trigger UI re-render
this.emit('section-split', {
originalSectionId,
newSections: newSections.map(s => s.getStatus()),
allSections: Array.from(this.sections.values())
});
}
createSectionsFromContent(content, baseSectionId) {
const lines = content.split('\n');
const sections = [];
let currentSection = '';
let position = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const isHeading = /^#{1,6}\s/.test(line.trim());
if (isHeading) {
// When we encounter a heading, complete any previous section
if (currentSection.trim()) {
const sectionId = `${baseSectionId}_split_${position}`;
const sectionType = Section.detectType(currentSection);
const section = new Section(sectionId, currentSection.trim(), sectionType);
sections.push(section);
position++;
}
// Start new section with this heading
currentSection = line;
} else {
// Add content to current section
if (currentSection) currentSection += '\n';
currentSection += line;
}
}
// Add the final section if it has content
if (currentSection.trim()) {
const sectionId = `${baseSectionId}_split_${position}`;
const sectionType = Section.detectType(currentSection);
const section = new Section(sectionId, currentSection.trim(), sectionType);
sections.push(section);
}
return sections;
}
cancelChanges(sectionId) {
const section = this.sections.get(sectionId);
if (!section) {
throw new Error(`Section ${sectionId} not found`);
}
const content = section.cancelChanges();
// Note: No longer tracking single editingSection
this.emit('changes-cancelled', { sectionId, content, section: section.getStatus() });
return content;
}
resetToOriginal(sectionId) {
const section = this.sections.get(sectionId);
if (!section) {
throw new Error(`Section ${sectionId} not found`);
}
const content = section.resetToOriginal();
this.emit('section-reset', { sectionId, content, section: section.getStatus() });
return content;
}
stopEditing(sectionId) {
const section = this.sections.get(sectionId);
if (!section) {
throw new Error(`Section ${sectionId} not found`);
}
const newState = section.stopEditing();
// Note: No longer tracking single editingSection
this.emit('edit-stopped', { sectionId, newState, section: section.getStatus() });
return newState;
}
getAllSections() {
return Array.from(this.sections.values());
}
getDocumentMarkdown() {
return this.getAllSections()
.map(section => section.currentMarkdown)
.join('\n\n');
}
}
/**
* DOM Renderer - Handles DOM interactions
*/
class DOMRenderer {
constructor(sectionManager, containerElement) {
this.sectionManager = sectionManager;
this.container = containerElement;
// Note: Removed single currentSection tracking to allow multiple concurrent edits
this.editingSections = new Set(); // Track multiple editing sections
this.handleSectionClick = this.handleSectionClick.bind(this);
this.handleAccept = this.handleAccept.bind(this);
this.handleCancel = this.handleCancel.bind(this);
this.handleReset = this.handleReset.bind(this);
this.handleKeydown = this.handleKeydown.bind(this);
this.setupEventListeners();
}
setupEventListeners() {
this.sectionManager.on('sections-created', (data) => {
this.renderAllSections(data.sections);
});
this.sectionManager.on('edit-started', (data) => {
this.showEditor(data.sectionId, data.content);
});
this.sectionManager.on('edit-stopped', (data) => {
this.hideEditor(data.sectionId);
// Don't update content - let pending changes remain
});
this.sectionManager.on('changes-accepted', (data) => {
this.hideEditor(data.sectionId);
this.updateSectionContent(data.sectionId, data.content);
});
this.sectionManager.on('changes-cancelled', (data) => {
this.hideEditor(data.sectionId);
this.updateSectionContent(data.sectionId, data.content);
});
this.sectionManager.on('section-reset', (data) => {
this.updateTextareaContent(data.content, data.sectionId);
});
this.sectionManager.on('section-split', (data) => {
console.log('Handling section split in UI');
this.handleSectionSplit(data);
});
}
renderAllSections(sections) {
this.container.innerHTML = '';
sections.forEach(section => {
const element = this.createSectionElement(section);
section.domElement = element;
this.container.appendChild(element);
});
this.container.addEventListener('click', this.handleSectionClick);
}
createSectionElement(section) {
const element = document.createElement('div');
element.setAttribute('data-section-id', section.id);
if (typeof marked !== 'undefined') {
const html = marked.parse(section.currentMarkdown);
// Add target="_blank" to all links
const htmlWithTargetBlank = html.replace(/<a href="([^"]*)"([^>]*)>/g, '<a href="$1" target="_blank"$2>');
element.innerHTML = htmlWithTargetBlank;
} else {
element.innerHTML = `<p>${section.currentMarkdown}</p>`;
}
// Setup styling and event handlers
this.setupSectionElement(element);
return element;
}
handleSectionClick(event) {
// Don't handle clicks on form elements, buttons, or links
if (event.target.closest('textarea, button, input, a')) {
return;
}
const sectionElement = event.target.closest('.ui-edit-section');
if (!sectionElement) return;
const sectionId = sectionElement.getAttribute('data-section-id');
if (!sectionId) return;
// Check if this section is already being edited
const section = this.sectionManager.sections.get(sectionId);
if (section && section.isEditing()) {
console.log('Section already being edited:', sectionId);
return;
}
try {
console.log('Starting edit for section:', sectionId);
this.sectionManager.startEditing(sectionId);
} catch (error) {
console.error('Failed to start editing:', error);
}
}
showEditor(sectionId, content) {
const element = this.findSectionElement(sectionId);
if (!element) return;
this.hideCurrentEditor();
const section = this.sectionManager.sections.get(sectionId);
const isProtectedHeading = section && section.isProtectedHeading();
const editorContainer = document.createElement('div');
editorContainer.className = isProtectedHeading ? 'ui-edit-inline-panel ui-insert-protected-panel' : 'ui-edit-inline-panel';
editorContainer.style.cssText = `
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
`;
// If this is a protected heading, show the heading display
if (isProtectedHeading) {
const headingDisplay = document.createElement('div');
headingDisplay.className = 'ui-insert-heading-display';
const headingText = section.getHeadingText();
const headingLevel = section.headingLevel;
const headingMarkdown = '#'.repeat(headingLevel) + ' ' + headingText;
headingDisplay.textContent = headingMarkdown;
headingDisplay.style.cssText = `
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
font-size: 14px;
font-weight: bold;
padding: 8px 12px;
background: rgba(0, 0, 0, 0.05);
border-radius: 4px;
border-left: 4px solid #007bff;
color: #333;
`;
editorContainer.appendChild(headingDisplay);
}
// Create content editing area
const editingArea = document.createElement('div');
editingArea.style.cssText = `
display: flex;
gap: 12px;
align-items: flex-start;
`;
const textarea = document.createElement('textarea');
textarea.className = isProtectedHeading ? 'ui-edit-textarea ui-insert-content-editor' : 'ui-edit-textarea ui-edit-textarea-main';
// For protected headings, only show content after the heading
const textareaContent = isProtectedHeading ? section.getHeadingContent() : content;
textarea.value = textareaContent;
textarea.style.cssText = `
flex: 1;
min-height: 100px;
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
border-radius: 6px;
padding: 12px;
font-size: 14px;
line-height: 1.5;
resize: vertical;
`;
textarea.addEventListener('input', () => {
if (isProtectedHeading) {
// Reconstruct full content with protected heading
const headingLine = section.originalMarkdown.split('\n')[0];
const fullContent = headingLine + '\n' + textarea.value;
this.sectionManager.updateContent(sectionId, fullContent);
} else {
this.sectionManager.updateContent(sectionId, textarea.value);
}
});
textarea.addEventListener('keydown', this.handleKeydown);
const controls = document.createElement('div');
controls.style.cssText = `
display: flex;
flex-direction: column;
gap: 6px;
`;
const createButton = (text, className, handler) => {
const btn = document.createElement('button');
btn.textContent = text;
btn.className = className;
btn.addEventListener('click', handler);
return btn;
};
controls.appendChild(createButton('✓ Accept', 'ui-edit-button ui-edit-button-accept', () => this.handleAccept(sectionId)));
controls.appendChild(createButton('✗ Cancel', 'ui-edit-button ui-edit-button-cancel', () => this.handleCancel(sectionId)));
controls.appendChild(createButton('🔄 Reset', 'ui-edit-button ui-edit-button-reset', () => this.handleReset(sectionId)));
editingArea.appendChild(textarea);
editingArea.appendChild(controls);
editorContainer.appendChild(editingArea);
element.innerHTML = '';
element.appendChild(editorContainer);
textarea.focus();
// Track this section as being edited
this.editingSections.add(sectionId);
}
hideCurrentEditor() {
// This method is no longer needed since we support multiple editors
// Individual editors are hidden via hideEditor(sectionId)
}
hideEditor(sectionId) {
// Remove from editing sections set
this.editingSections.delete(sectionId);
// Force re-render the section to ensure it displays correctly
const section = this.sectionManager.sections.get(sectionId);
if (section) {
this.updateSectionContent(sectionId, section.currentMarkdown);
}
}
updateSectionContent(sectionId, content) {
const element = this.findSectionElement(sectionId);
if (!element) return;
if (typeof marked !== 'undefined') {
const html = marked.parse(content);
// Add target="_blank" to all links
const htmlWithTargetBlank = html.replace(/<a href="([^"]*)"([^>]*)>/g, '<a href="$1" target="_blank"$2>');
element.innerHTML = htmlWithTargetBlank;
} else {
element.innerHTML = `<p>${content}</p>`;
}
// Restore the section styling and click behavior
this.setupSectionElement(element);
}
setupSectionElement(element) {
element.className = 'ui-edit-section';
element.style.cssText = `
margin: 16px 0;
padding: 12px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
border: 2px solid transparent;
`;
// Remove any existing event listeners to avoid duplicates
element.removeEventListener('mouseenter', element._mouseenterHandler);
element.removeEventListener('mouseleave', element._mouseleaveHandler);
// Create new handlers and store references
element._mouseenterHandler = () => {
element.style.backgroundColor = 'rgba(0, 0, 0, 0.02)';
element.style.borderColor = 'rgba(0, 0, 0, 0.1)';
};
element._mouseleaveHandler = () => {
element.style.backgroundColor = '';
element.style.borderColor = 'transparent';
};
element.addEventListener('mouseenter', element._mouseenterHandler);
element.addEventListener('mouseleave', element._mouseleaveHandler);
}
updateTextareaContent(content, sectionId) {
// Find the specific textarea for this section
const element = this.findSectionElement(sectionId);
if (element) {
const textarea = element.querySelector('textarea');
if (textarea) {
textarea.value = content;
}
}
}
handleSectionSplit(data) {
// Clear the editor state for the original section
this.editingSections.delete(data.originalSectionId);
// Find the original section element and its position
const originalElement = this.findSectionElement(data.originalSectionId);
if (!originalElement) {
console.error('Original section element not found');
return;
}
// Get the position of the original element
const originalPosition = Array.from(this.container.children).indexOf(originalElement);
// Create new section elements for the split sections
const newElements = [];
data.newSections.forEach(sectionData => {
const section = this.sectionManager.sections.get(sectionData.id);
if (section) {
const element = this.createSectionElement(section);
section.domElement = element;
newElements.push(element);
}
});
// Remove the original element
originalElement.remove();
// Insert new elements at the original position
if (originalPosition < this.container.children.length) {
const nextElement = this.container.children[originalPosition];
newElements.forEach(element => {
this.container.insertBefore(element, nextElement);
});
} else {
// If original was at the end, just append
newElements.forEach(element => {
this.container.appendChild(element);
});
}
// Show success message
console.log(`Section split into ${data.newSections.length} sections`);
// Notify the main editor about the split
if (window.markitectCleanEditor) {
window.markitectCleanEditor.showMessage(
`✂️ Section split into ${data.newSections.length} sections!`,
'success'
);
}
}
findSectionElement(sectionId) {
return this.container.querySelector(`[data-section-id="${sectionId}"]`);
}
handleAccept(sectionId) {
try {
console.log('Accepting changes for section:', sectionId);
this.sectionManager.acceptChanges(sectionId);
console.log('Changes accepted successfully');
} catch (error) {
console.error('Failed to accept changes:', error);
}
}
handleCancel(sectionId) {
try {
console.log('Canceling changes for section:', sectionId);
this.sectionManager.cancelChanges(sectionId);
console.log('Changes canceled successfully');
} catch (error) {
console.error('Failed to cancel changes:', error);
}
}
handleReset(sectionId) {
try {
this.sectionManager.resetToOriginal(sectionId);
} catch (error) {
console.error('Failed to reset section:', error);
}
}
handleKeydown(event) {
if (!this.currentSection) return;
if (event.ctrlKey || event.metaKey) {
switch (event.key) {
case 'Enter':
event.preventDefault();
this.handleAccept(this.currentSection);
break;
case 'Escape':
event.preventDefault();
this.handleCancel(this.currentSection);
break;
}
}
if (event.key === 'Escape') {
event.preventDefault();
this.handleCancel(this.currentSection);
}
}
}
/**
* Main Editor Integration
*/
class MarkitectCleanEditor {
constructor(markdownContent, containerElement, options = {}) {
this.options = {
theme: 'github',
keyboardShortcuts: true,
autosave: false,
...options
};
this.sectionManager = new SectionManager();
this.domRenderer = new DOMRenderer(this.sectionManager, containerElement);
this.originalMarkdown = markdownContent;
this.initialize();
}
initialize() {
try {
const sections = this.sectionManager.createSectionsFromMarkdown(this.originalMarkdown);
console.log(`✓ Initialized clean editor with ${sections.length} sections`);
// Add global control panel
this.addGlobalControls();
return true;
} catch (error) {
console.error('Failed to initialize clean editor:', error);
return false;
}
}
addGlobalControls() {
// Create floating control panel
const panel = document.createElement('div');
panel.id = 'ui-edit-floater';
panel.className = 'ui-edit-floater-panel';
panel.innerHTML = `
<div class="ui-edit-floater-header">
<h3>📝 Editor</h3>
<div class="ui-edit-floater-status" id="editor-status">Ready</div>
</div>
<div class="ui-edit-floater-actions">
<button id="save-document" class="ui-edit-button ui-edit-button-accept">💾 Save Document</button>
<button id="reset-all" class="ui-edit-button ui-edit-button-reset">🔄 Reset All</button>
<button id="show-status" class="ui-edit-button ui-edit-button-secondary">📊 Show Status</button>
</div>
`;
// Style the panel
panel.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
border-radius: 8px;
padding: 16px;
z-index: 1000;
font-family: system-ui, -apple-system, sans-serif;
min-width: 200px;
max-width: 250px;
`;
// Add internal styling for structural layout (theme colors come from CSS)
const style = document.createElement('style');
style.textContent = `
.ui-edit-floater-header h3 {
margin: 0 0 8px 0;
font-size: 16px;
}
.ui-edit-floater-status {
font-size: 12px;
margin-bottom: 12px;
}
.ui-edit-button {
display: block;
width: 100%;
margin: 6px 0;
padding: 10px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
border: 1px solid transparent;
}
`;
document.head.appendChild(style);
document.body.appendChild(panel);
// Add event listeners
document.getElementById('save-document').addEventListener('click', () => this.saveDocument());
document.getElementById('reset-all').addEventListener('click', () => this.resetAllSections());
document.getElementById('show-status').addEventListener('click', () => this.showStatus());
// Update status periodically
this.statusInterval = setInterval(() => this.updateGlobalStatus(), 2000);
}
updateGlobalStatus() {
const statusEl = document.getElementById('editor-status');
if (!statusEl) return;
const sections = this.sectionManager.getAllSections();
const modified = sections.filter(s => s.hasChanges()).length;
const editing = sections.filter(s => s.isEditing()).length;
if (editing > 0) {
statusEl.textContent = `Editing ${editing} section${editing !== 1 ? 's' : ''}`;
statusEl.className = 'ui-edit-floater-status editing';
} else if (modified > 0) {
statusEl.textContent = `${modified} section${modified !== 1 ? 's' : ''} modified`;
statusEl.style.color = '';
} else {
statusEl.textContent = 'All sections saved ✓';
statusEl.style.color = '';
}
}
saveDocument() {
const markdown = this.getDocumentMarkdown();
const blob = new Blob([markdown], { type: 'text/markdown' });
const url = URL.createObjectURL(blob);
// Generate intelligent filename
const filename = this.generateSaveFilename();
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log('📄 Document saved as:', filename);
this.showMessage(`Document saved as: ${filename}`, 'success');
}
generateSaveFilename() {
// Try to get original filename from config
let baseName = 'document';
// Method 1: Use original filename from config if available
if (typeof MARKITECT_EDITOR_CONFIG !== 'undefined' && MARKITECT_EDITOR_CONFIG.originalFilename) {
baseName = MARKITECT_EDITOR_CONFIG.originalFilename;
}
// Method 2: Try to extract from page title
if (baseName === 'document') {
const title = document.title;
if (title && title !== 'Markdown Document' && !title.includes('Debug') && !title.includes('Test')) {
baseName = title.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '') // Remove special chars
.replace(/\s+/g, '-') // Replace spaces with dashes
.replace(/-+/g, '-') // Collapse multiple dashes
.replace(/^-|-$/g, ''); // Remove leading/trailing dashes
}
}
// Method 3: Try to extract from URL pathname
if (baseName === 'document') {
const urlPath = window.location.pathname;
const match = urlPath.match(/\/([^\/]+)\.html?$/);
if (match) {
const urlBaseName = match[1];
if (!urlBaseName.includes('debug') && !urlBaseName.includes('test')) {
baseName = urlBaseName.replace(/_/g, '-');
}
}
}
// Method 4: Try to extract from first heading
if (baseName === 'document') {
const firstHeading = this.sectionManager.getAllSections()
.find(section => section.sectionType === 'heading');
if (firstHeading) {
baseName = firstHeading.originalMarkdown
.replace(/^#+\s*/, '') // Remove markdown heading syntax
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '')
.substring(0, 30); // Limit length
}
}
// Generate timestamp
const now = new Date();
const timestamp = now.toISOString()
.replace(/T/, '-')
.replace(/:/g, '-')
.replace(/\.\d{3}Z$/, '');
// Check if there are modifications
const hasModifications = this.sectionManager.getAllSections()
.some(section => section.hasChanges());
if (hasModifications) {
return `${baseName}-edited-${timestamp}.md`;
} else {
return `${baseName}-${timestamp}.md`;
}
}
/**
* Show custom confirmation dialog with theme-consistent styling
* @param {string} message - The confirmation message
* @param {string} confirmText - Text for confirm button (default: "Confirm")
* @param {string} cancelText - Text for cancel button (default: "Cancel")
* @param {string} warningText - Optional warning text to highlight consequences
* @returns {Promise<boolean>} - True if confirmed, false if cancelled
*/
showConfirmation(message, confirmText = "Confirm", cancelText = "Cancel", warningText = null) {
return new Promise((resolve) => {
// Remove any existing modal
const existingModal = document.querySelector('.ui-edit-modal-overlay');
if (existingModal) {
existingModal.remove();
}
// Create modal overlay
const overlay = document.createElement('div');
overlay.className = 'ui-edit-modal-overlay';
// Create modal content
const modal = document.createElement('div');
modal.className = 'ui-edit-modal ui-edit-confirmation-modal';
// Create header
const header = document.createElement('div');
header.className = 'ui-edit-modal-header';
const title = document.createElement('h3');
title.className = 'ui-edit-modal-title';
title.textContent = 'Confirm Action';
const closeBtn = document.createElement('button');
closeBtn.className = 'ui-edit-modal-close';
closeBtn.innerHTML = '×';
closeBtn.setAttribute('aria-label', 'Close');
header.appendChild(title);
header.appendChild(closeBtn);
// Create body
const body = document.createElement('div');
body.className = 'ui-edit-modal-body';
const content = document.createElement('div');
content.className = 'ui-edit-confirmation-content';
content.textContent = message;
body.appendChild(content);
// Add warning section if provided
if (warningText) {
const warning = document.createElement('div');
warning.className = 'ui-edit-confirmation-warning';
warning.textContent = warningText;
body.appendChild(warning);
}
// Create footer with action buttons
const footer = document.createElement('div');
footer.className = 'ui-edit-modal-footer';
const buttonContainer = document.createElement('div');
buttonContainer.className = 'ui-edit-confirmation-buttons';
const cancelBtn = document.createElement('button');
cancelBtn.className = 'ui-edit-button-cancel';
cancelBtn.textContent = cancelText;
const confirmBtn = document.createElement('button');
confirmBtn.className = 'ui-edit-button-confirm';
confirmBtn.textContent = confirmText;
buttonContainer.appendChild(cancelBtn);
buttonContainer.appendChild(confirmBtn);
footer.appendChild(buttonContainer);
// Assemble modal
modal.appendChild(header);
modal.appendChild(body);
modal.appendChild(footer);
overlay.appendChild(modal);
document.body.appendChild(overlay);
// Function to close modal and resolve
const closeModal = (result) => {
overlay.remove();
resolve(result);
};
// Event listeners
closeBtn.addEventListener('click', () => closeModal(false));
cancelBtn.addEventListener('click', () => closeModal(false));
confirmBtn.addEventListener('click', () => closeModal(true));
// Close on overlay click
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
closeModal(false);
}
});
// Keyboard support
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
closeModal(false);
} else if (e.key === 'Enter') {
closeModal(true);
}
};
document.addEventListener('keydown', handleKeyDown);
// Clean up event listener when modal is closed
const originalResolve = resolve;
resolve = (result) => {
document.removeEventListener('keydown', handleKeyDown);
originalResolve(result);
};
// Show modal with animation
setTimeout(() => {
overlay.classList.add('active');
// Focus the confirm button for accessibility
confirmBtn.focus();
}, 10);
});
}
async resetAllSections() {
const confirmed = await this.showConfirmation(
'Reset all content to original markdown?',
'Reset Document',
'Keep Changes',
'This will permanently lose all edits and remove any split sections. This action cannot be undone.'
);
if (confirmed) {
// Clear the section manager completely
this.sectionManager.sections.clear();
// Note: No longer tracking single editingSection
// Recreate sections from original markdown
const sections = this.sectionManager.createSectionsFromMarkdown(this.originalMarkdown);
console.log('🔄 All sections reset to original structure');
this.showMessage('Document reset to original structure', 'info');
}
}
showStatus() {
const sections = this.sectionManager.getAllSections();
const total = sections.length;
const modified = sections.filter(s => s.hasChanges()).length;
const editing = sections.filter(s => s.isEditing()).length;
// Get the actual save filename that will be used
const saveFilename = this.generateSaveFilename();
// Create structured content for the modal
const modalContent = {
title: `📊 ${window.editorConfig.repoName} Status`,
sections: [
{
title: 'Application Information',
content: `${window.editorConfig.version}`
},
{
title: 'File Information',
content: `Save file: ${saveFilename}
Source: ${window.editorConfig.originalFilename}
URL: ${window.location.protocol}//${window.location.host}${window.location.pathname}`
},
{
title: 'Document Status',
content: `• Total sections: ${total}
• Modified sections: ${modified}
• Currently editing: ${editing}
• Unsaved changes: ${modified > 0 || editing > 0 ? 'Yes' : 'No'}`
},
{
title: 'Section Behavior',
content: `• Each section is a logical unit (heading + content until next heading)
• Content with line breaks stays in one section
• To split content: Create new headings (# ## ###)
• Sections don't auto-split on line breaks`
},
{
title: 'Editing Controls',
content: `• Click any section to edit its content
• Accept (✓) - Save changes to that section
• Cancel (✗) - Discard changes, return to previous state
• Reset (🔄) - Restore original content for that section
• Save Document - Download all current content
• Reset All - Restore entire document to original state`
}
]
};
this.showModal(modalContent);
}
showModal(content) {
// Remove any existing modal
const existingModal = document.querySelector('.ui-edit-modal-overlay');
if (existingModal) {
existingModal.remove();
}
// Create modal overlay
const overlay = document.createElement('div');
overlay.className = 'ui-edit-modal-overlay';
// Create modal content
const modal = document.createElement('div');
modal.className = 'ui-edit-modal';
// Create header
const header = document.createElement('div');
header.className = 'ui-edit-modal-header';
const title = document.createElement('h3');
title.className = 'ui-edit-modal-title';
title.textContent = content.title;
const closeBtn = document.createElement('button');
closeBtn.className = 'ui-edit-modal-close';
closeBtn.innerHTML = '×';
closeBtn.setAttribute('aria-label', 'Close');
header.appendChild(title);
header.appendChild(closeBtn);
// Create body
const body = document.createElement('div');
body.className = 'ui-edit-modal-body';
// Add sections
content.sections.forEach(section => {
const sectionDiv = document.createElement('div');
sectionDiv.className = 'ui-edit-modal-section';
const sectionTitle = document.createElement('div');
sectionTitle.className = 'ui-edit-modal-section-title';
sectionTitle.textContent = section.title;
const sectionContent = document.createElement('div');
sectionContent.className = 'ui-edit-modal-content';
sectionContent.textContent = section.content;
sectionDiv.appendChild(sectionTitle);
sectionDiv.appendChild(sectionContent);
body.appendChild(sectionDiv);
});
// Create footer with close button
const footer = document.createElement('div');
footer.className = 'ui-edit-modal-footer';
const footerCloseBtn = document.createElement('button');
footerCloseBtn.className = 'ui-edit-button ui-edit-button-accept';
footerCloseBtn.textContent = 'Close';
footer.appendChild(footerCloseBtn);
// Assemble modal
modal.appendChild(header);
modal.appendChild(body);
modal.appendChild(footer);
overlay.appendChild(modal);
// Add to page
document.body.appendChild(overlay);
// Close handlers
const closeModal = () => {
overlay.classList.remove('active');
setTimeout(() => {
if (overlay.parentNode) {
overlay.parentNode.removeChild(overlay);
}
}, 300);
};
closeBtn.addEventListener('click', closeModal);
footerCloseBtn.addEventListener('click', closeModal);
// Close on overlay click (but not modal content)
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
closeModal();
}
});
// Close on Escape key
const handleKeydown = (e) => {
if (e.key === 'Escape') {
closeModal();
document.removeEventListener('keydown', handleKeydown);
}
};
document.addEventListener('keydown', handleKeydown);
// Show modal with animation
requestAnimationFrame(() => {
overlay.classList.add('active');
});
// Focus management
setTimeout(() => {
closeBtn.focus();
}, 100);
}
showMessage(message, type = 'info') {
const messageDiv = document.createElement('div');
messageDiv.textContent = message;
const colors = {
'success': '#28a745',
'error': '#dc3545',
'info': '#007acc'
};
messageDiv.style.cssText = `
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: ${colors[type] || colors.info};
color: white;
padding: 12px 20px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
z-index: 10001;
font-size: 14px;
max-width: 400px;
text-align: center;
`;
document.body.appendChild(messageDiv);
setTimeout(() => {
if (messageDiv.parentNode) {
messageDiv.parentNode.removeChild(messageDiv);
}
}, 3000);
}
getDocumentMarkdown() {
return this.sectionManager.getDocumentMarkdown();
}
}
// Initialize the clean editor system
let markitectCleanEditor;
function initializeCleanEditor() {
const container = document.getElementById('markdown-content');
if (!container) {
console.error('Markdown content container not found');
return;
}
if (typeof window.MarkitectEditor === 'undefined') {
console.error('MarkitectEditor not found');
return;
}
markitectCleanEditor = new window.MarkitectEditor.MarkitectCleanEditor(markdownContent, container);
window.markitectCleanEditor = markitectCleanEditor; // Make globally available
console.log('✅ Clean section editor initialized successfully');
}
// Document scroll indicators
function initializeScrollIndicators() {
// Create scroll indicators
const scrollUpIndicator = document.createElement('div');
scrollUpIndicator.className = 'ui-scroll-indicator ui-scroll-indicator-up';
scrollUpIndicator.setAttribute('aria-label', 'Scroll to top');
scrollUpIndicator.setAttribute('title', 'Scroll up');
const scrollDownIndicator = document.createElement('div');
scrollDownIndicator.className = 'ui-scroll-indicator ui-scroll-indicator-down';
scrollDownIndicator.setAttribute('aria-label', 'Scroll to bottom');
scrollDownIndicator.setAttribute('title', 'Scroll down');
document.body.appendChild(scrollUpIndicator);
document.body.appendChild(scrollDownIndicator);
let scrollIndicatorTimeout = null;
// Function to show/hide indicators based on scroll position and mouse position
function updateScrollIndicators(mouseY = null) {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
// Determine if scrolling is possible in each direction
const canScrollUp = scrollTop > 0;
const canScrollDown = scrollTop < documentHeight - windowHeight;
// Only show indicators if there's any scroll possibility or if document is short
let showUp = false;
let showDown = false;
// Show indicators on mouseover near top/bottom of viewport
if (mouseY !== null) {
const topZone = 100; // pixels from top
const bottomZone = windowHeight - 100; // pixels from bottom
if (mouseY <= topZone) {
showUp = true;
}
if (mouseY >= bottomZone) {
showDown = true;
}
}
// Update indicator visibility and state
if (showUp) {
scrollUpIndicator.classList.add('active');
if (canScrollUp) {
scrollUpIndicator.classList.remove('disabled');
} else {
scrollUpIndicator.classList.add('disabled');
}
} else {
scrollUpIndicator.classList.remove('active');
}
if (showDown) {
scrollDownIndicator.classList.add('active');
if (canScrollDown) {
scrollDownIndicator.classList.remove('disabled');
} else {
scrollDownIndicator.classList.add('disabled');
}
} else {
scrollDownIndicator.classList.remove('active');
}
// Auto-hide after a delay when mouse moves away
if (scrollIndicatorTimeout) {
clearTimeout(scrollIndicatorTimeout);
}
scrollIndicatorTimeout = setTimeout(() => {
scrollUpIndicator.classList.remove('active');
scrollDownIndicator.classList.remove('active');
}, 2000);
}
// Mouse move handler
function handleMouseMove(e) {
updateScrollIndicators(e.clientY);
}
// Smooth scroll function
function smoothScroll(targetY, duration = 500) {
const startY = window.pageYOffset;
const difference = targetY - startY;
const startTime = performance.now();
function step(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// Easing function (ease-out)
const easeOut = 1 - Math.pow(1 - progress, 3);
window.scrollTo(0, startY + difference * easeOut);
if (progress < 1) {
requestAnimationFrame(step);
}
}
requestAnimationFrame(step);
}
// Click handlers for smooth scrolling
scrollUpIndicator.addEventListener('click', () => {
const currentScroll = window.pageYOffset;
const targetScroll = Math.max(0, currentScroll - window.innerHeight * 0.8);
smoothScroll(targetScroll);
});
scrollDownIndicator.addEventListener('click', () => {
const currentScroll = window.pageYOffset;
const maxScroll = document.documentElement.scrollHeight - window.innerHeight;
const targetScroll = Math.min(maxScroll, currentScroll + window.innerHeight * 0.8);
smoothScroll(targetScroll);
});
// Event listeners
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('scroll', () => updateScrollIndicators());
// Initial check
updateScrollIndicators();
console.log('✅ Document scroll indicators initialized');
}
// Export for testing and usage
if (typeof module !== 'undefined' && module.exports) {
module.exports = { Section, SectionManager, DOMRenderer, MarkitectCleanEditor };
} else {
window.MarkitectEditor = { Section, SectionManager, DOMRenderer, MarkitectCleanEditor };
}
// Always render content first (graceful degradation)
document.addEventListener('DOMContentLoaded', function() {
console.log("Rendering content...");
const contentDiv = document.getElementById('markdown-content');
// Step 1: Ensure content is always displayed
if (contentDiv) {
if (typeof marked !== 'undefined') {
try {
const html = marked.parse(markdownContent);
// Add target="_blank" to all links
const htmlWithTargetBlank = html.replace(/<a href="([^"]*)"([^>]*)>/g, '<a href="$1" target="_blank"$2>');
contentDiv.innerHTML = htmlWithTargetBlank;
console.log("✓ Content rendered successfully");
console.log('✓ Markdown rendered successfully');
} catch (error) {
contentDiv.innerHTML = '<p>Error rendering markdown: ' + error.message + '</p>';
console.error("Content rendered with errors");
console.error("Markdown parsing failed:", error.message);
}
} else {
// Fallback: display raw markdown with basic formatting
const fallbackHtml = markdownContent
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/^- (.*$)/gim, '<li>$1</li>')
.replace(/\n\n/g, '<br><br>')
.replace(/\n/g, '<br>');
contentDiv.innerHTML = '<div style="white-space: pre-wrap;">' + fallbackHtml + '</div>';
console.warn("Content rendered with fallback parser");
console.warn("CDN library failed to load - using basic fallback rendering");
}
}
// Step 2: Initialize edit/insert capabilities if enabled
if ((typeof MARKITECT_EDIT_MODE !== 'undefined' && MARKITECT_EDIT_MODE) ||
(typeof MARKITECT_INSERT_MODE !== 'undefined' && MARKITECT_INSERT_MODE)) {
const mode = (typeof MARKITECT_INSERT_MODE !== 'undefined' && MARKITECT_INSERT_MODE) ? 'insert' : 'edit';
console.log(`Initializing clean ${mode} capabilities...`);
try {
console.log("Creating clean editor instance...");
initializeCleanEditor();
if (mode === 'insert') {
console.log("✓ Clean insert mode active - click any section to edit (headings 1-3 protected)");
} else {
console.log("✓ Clean edit mode active - click any section to edit");
}
} catch (error) {
console.error(`Clean ${mode} mode failed to initialize:`, error);
}
}
// Step 3: Initialize document scroll indicators (always available)
try {
initializeScrollIndicators();
} catch (error) {
console.error("Scroll indicators failed to initialize:", error);
}
});
// Handle CDN loading errors
window.addEventListener('load', function() {
if (window.markitectMarkedError) {
console.error("CDN library failed to load - network or firewall blocking marked.js");
}
});
</script>
</body>
</html>