# Architecture Overview Project Notes is a web-based Markdown note-taking application built with a hybrid architecture combining Go backend, HTMX for interactions, and modern JavaScript for UI enhancements. ## Design Philosophy **HTML Over The Wire**: The server renders HTML, not JSON. HTMX enables dynamic interactions without building a full SPA. **Progressive Enhancement**: Core functionality works with basic HTTP. JavaScript enhances the experience (CodeMirror editor, drag-and-drop, search modal). **Simplicity First**: Avoid framework complexity. Use the right tool for each job: - Go for backend (fast, simple, type-safe) - HTMX for AJAX (declarative, low JavaScript) - Vanilla JS for UI (no framework overhead) - Vite for building (fast, modern) ## System Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ Browser (Client) │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ HTMX │ │ CodeMirror │ │ JavaScript │ │ │ │ (interactions)│ │ (editor) │ │ (UI logic) │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ └──────────────────┴──────────────────┘ │ │ │ │ └────────────────────────────┼──────────────────────────────────┘ │ HTTP (HTML) ▼ ┌─────────────────────────────────────────────────────────────┐ │ Go HTTP Server │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Handlers │ │ Indexer │ │ Watcher │ │ │ │ (API) │◄─┤ (search) │◄─┤ (fsnotify) │ │ │ └──────┬───────┘ └──────────────┘ └──────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────┐ │ │ │ Templates │ │ │ │ (Go html) │ │ │ └──────────────┘ │ │ │ └────────────────────────────┬────────────────────────────────┘ │ Filesystem ▼ ┌─────────────────────────────────────────────────────────────┐ │ Markdown Files (.md) │ │ YAML Front Matter │ └─────────────────────────────────────────────────────────────┘ ``` ## Component Interaction Patterns ### 1. Page Load (Initial Render) ``` User → Browser │ ├─ GET / → Go Server │ │ │ ├─ Parse index.html template │ ├─ Inject file tree │ └─ Return HTML │ ├─ Load static/dist/project-notes-frontend.es.js (Vite bundle) │ │ │ ├─ Initialize FileTree (file-tree.js) │ ├─ Initialize Search (search.js) │ └─ Setup UI handlers (ui.js) │ └─ HTMX processes hx-* attributes │ └─ Triggers hx-get="/api/tree" (load file tree) hx-get="/api/home" (load home page) ``` ### 2. Opening a Note ``` User clicks file in tree → HTMX intercepts click (hx-get attribute) │ ├─ GET /api/notes/my-note.md → Go Server │ │ │ ├─ Read file │ ├─ Parse front matter │ ├─ Render editor.html template │ └─ Return HTML fragment │ └─ HTMX swaps into #editor-container │ └─ Triggers htmx:afterSwap event │ └─ editor.js initializes CodeMirror ``` ### 3. Drag and Drop File ``` User drags file → JavaScript (file-tree.js) │ ├─ dragstart: Store source path ├─ dragover: Validate drop target └─ drop: Calculate destination │ └─ htmx.ajax('POST', '/api/files/move') │ ├─ Go Server moves file ├─ Re-indexes ├─ Renders new file tree └─ Returns HTML with hx-swap-oob="innerHTML" #file-tree │ └─ HTMX swaps file tree automatically │ └─ Triggers htmx:oobAfterSwap event │ └─ file-tree.js updates draggable attributes ``` ### 4. Searching Notes ``` User types in search → HTMX (hx-get="/api/search" with debounce) │ ├─ Go Server │ │ │ ├─ Query indexer │ ├─ Rank results │ ├─ Render search-results.html │ └─ Return HTML │ └─ HTMX swaps into #search-results ``` Alternative: Search Modal (Ctrl/Cmd+K) ``` User presses Ctrl+K → search.js opens modal │ └─ User types → Debounced fetch to /api/search │ ├─ Renders results in modal └─ Keyboard navigation (JS) ``` ### 5. Auto-Save in Editor ``` User types in editor → CodeMirror EditorView.updateListener │ ├─ Debounce 150ms → Update preview (JavaScript) │ └─ Debounce 2s → Trigger save │ ├─ Sync content to hidden textarea └─ form.requestSubmit() │ └─ HTMX intercepts (hx-post="/api/notes/...") │ ├─ Go Server saves file ├─ Updates front matter (last_modified) ├─ Re-indexes └─ Returns HTML with oob swap for file tree │ └─ HTMX updates file tree automatically ``` ### 6. Creating Links Between Notes (Internal Links) ``` User types /ilink → SlashCommands detects slash + query │ ├─ Filters commands by query └─ Shows command palette │ └─ User selects "ilink" → SlashCommands.openLinkInserter() │ ├─ Saves current cursor position └─ Opens LinkInserter modal │ User types query → LinkInserter searches │ ├─ Debounce 200ms └─ fetch('/api/search?query=...') │ ├─ Go Server queries indexer ├─ Returns HTML results └─ LinkInserter parses HTML │ ├─ Extracts title, path, tags ├─ Renders in modal └─ Updates keyboard selection │ User selects note → LinkInserter.selectResult() (Enter/click) │ ├─ Calls callback with {title, path} └─ SlashCommands.openLinkInserter callback │ ├─ Builds HTML with HTMX: title ├─ Uses CodeMirror transaction ├─ Replaces /ilink with HTML link ├─ Positions cursor after link └─ Closes modal │ Preview Rendering → marked.js parses Markdown (including inline HTML) │ ├─ DOMPurify sanitizes (allows hx-* attributes) ├─ htmx.process() activates HTMX on links └─ Links become clickable → load note via HTMX ``` **Key Design Decisions**: - **No new backend code**: Reuses existing `/api/search` endpoint for search, `/api/notes/` for navigation - **Database-free**: Leverages in-memory indexer for speed - **Consistent UX**: Modal design matches SearchModal styling - **Clickable links**: HTML with HTMX attributes, rendered directly by marked.js - **HTMX integration**: Links use `hx-get` to load notes without page reload - **Keyboard-first**: Full keyboard navigation without mouse ## Frontend Architecture ### Build Process (Vite) ``` frontend/src/ ├── main.js → Entry point ├── editor.js → CodeMirror 6 + Slash Commands ├── file-tree.js → Drag & drop + HTMX coordination ├── search.js → Search modal (Ctrl/Cmd+K) └── ui.js → Sidebar toggle ↓ (Vite build) static/dist/ ├── project-notes-frontend.es.js (1.0 MB - ES modules) └── project-notes-frontend.umd.js (679 KB - UMD) ↓ (Loaded by browser) Executed in browser → Initializes components ``` ### Module Responsibilities **main.js** - Entry point - Imports all modules - No logic, just imports **editor.js** - MarkdownEditor class (CodeMirror 6) - SlashCommands class (command palette) - View mode management (split/editor-only/preview-only) - Preview rendering (marked.js + DOMPurify) - Scroll synchronization - Auto-save logic - HTMX event listeners for editor initialization **file-tree.js** - FileTree class (drag & drop) - Event delegation for clicks (folder expand/collapse) - Drag & drop event handlers - htmx.ajax() for move operations - Folder creation modal - HTMX event listeners (htmx:oobAfterSwap) for updates **search.js** - Search modal (Ctrl/Cmd+K) - Keyboard navigation - Debounced search - Result highlighting - Uses HTMX search API **link-inserter.js** - LinkInserter class for internal note linking - Modal search interface for `/ilink` command - Fuzzy search across notes - Keyboard navigation (↑/↓/Enter/Esc) - Integration with SlashCommands - Uses HTMX search API for consistency - Inserts Markdown links into editor **Note**: `/link` command inserts standard Markdown template `[texte](url)` for external links **ui.js** - Sidebar toggle (mobile/desktop) - Simple utility functions ## HTMX Integration Patterns ### Pattern 1: Declarative Links (Preferred) Use HTMX attributes directly in HTML for static interactions: ```html 📄 my-note.md ``` **When to use**: Static content, links, forms with fixed targets. ### Pattern 2: JavaScript-Initiated Requests Use `htmx.ajax()` for dynamic interactions initiated by JavaScript: ```javascript htmx.ajax('POST', '/api/files/move', { values: { source: 'old/path.md', destination: 'new/path.md' }, swap: 'none' // Server uses hx-swap-oob }); ``` **When to use**: Drag & drop, programmatic actions, complex validations. ### Pattern 3: Out-of-Band Swaps (OOB) Server includes additional HTML fragments to update multiple parts of the page: ```html