Files
personotes/CLAUDE.md

24 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

A lightweight web-based Markdown note-taking application with a Go backend and modern JavaScript frontend. Notes are stored as plain Markdown files with YAML front matter containing metadata (title, date, last_modified, tags). The system provides a sophisticated CodeMirror 6 editor with live preview, rich search capabilities, hierarchical organization, and automatic front matter management.

Recent Modernization: The project has been migrated from a simple textarea editor to CodeMirror 6, with a Vite build system for frontend modules. The backend remains unchanged, maintaining the same Go architecture with htmx for dynamic interactions.

Architecture

Backend (Go)

Three main packages under internal/:

  • indexer: Maintains an in-memory index mapping tags to note files. Parses YAML front matter from .md files to build the index. Thread-safe with RWMutex.
  • watcher: Uses fsnotify to monitor filesystem changes and trigger re-indexing with 200ms debounce. Recursively watches all subdirectories.
  • api: HTTP handlers that serve templates and handle CRUD operations on notes. Updates front matter automatically on save.

The server (cmd/server/main.go) coordinates these components:

  1. Loads initial index from notes directory
  2. Starts filesystem watcher for automatic re-indexing
  3. Pre-parses HTML templates from templates/
  4. Serves routes:
    • / (main page)
    • /api/v1/notes and /api/v1/notes/* (REST API - JSON responses)
    • /api/search (HTML search results)
    • /api/notes/* (HTML editor for notes)
    • /api/tree (HTML file tree)
    • /api/folders/create (Folder management)
    • /api/files/move (File/folder moving)
    • /api/home (Home page)
  5. Handles static files from static/ directory

Frontend

The frontend uses a modern build system with Vite and CodeMirror 6:

Architecture

  • Build System: Vite compiles frontend modules from frontend/src/ to static/dist/
  • Editor: CodeMirror 6 with Markdown language support, One Dark theme, and syntax highlighting
  • Templates: index.html, editor.html, file-tree.html, search-results.html, new-note-prompt.html
  • HTMX Integration: Server returns HTML fragments that htmx swaps into the DOM
  • Out-of-band swaps: Update the file tree after saves/deletes without full page reload

Frontend Source Structure

frontend/src/
├── main.js           # Entry point - imports all modules
├── editor.js         # CodeMirror 6 editor implementation with slash commands
├── search.js         # Search modal with Ctrl/Cmd+K keyboard shortcut
├── file-tree.js      # Drag-and-drop file organization
└── ui.js             # Sidebar toggle functionality

CodeMirror 6 Editor Features

  • Syntax Highlighting: Full Markdown language support (@codemirror/lang-markdown)
  • Theme: One Dark theme (@codemirror/theme-one-dark) - VS Code-inspired dark theme
  • Live Preview: Debounced updates (150ms) synchronized with editor scroll position
  • Auto-Save: Triggers after 2 seconds of inactivity
  • Keyboard Shortcuts:
    • Ctrl/Cmd+S for manual save
    • Tab for proper indentation
    • Full keyboard navigation
  • View Modes: Toggle between split view, editor-only, and preview-only
  • Slash Commands: Type / to open command palette for quick Markdown insertion
  • Front Matter Handling: Automatically strips YAML front matter in preview

File Tree Features

  • Folder Management: Expand/collapse folders with visual indicators (📁/📂)
  • Drag & Drop: Move files between folders with visual feedback
  • Folder Creation: Modal-based creation supporting nested paths
  • Safe Validation: Prevents dangerous path operations

Rendering Pipeline

  • marked.js: Markdown to HTML conversion
  • DOMPurify: HTML sanitization to prevent XSS attacks
  • Highlight.js: Syntax highlighting for code blocks in preview
  • Custom Theme: Material Darker theme in static/theme.css with CSS custom properties

HTMX + JavaScript Coordination (Optimized Architecture)

Key Principle: HTMX handles ALL server interactions and DOM updates. JavaScript handles UI enhancements (editor, drag-and-drop, animations).

Architecture Flow

User Interaction → HTMX (AJAX) → Go Server (HTML) → HTMX (DOM update) → JS Events (enhancements)

Best Practices

1. Use htmx.ajax() for JavaScript-initiated requests:

// ✅ Good: Let HTMX handle the request and DOM updates
htmx.ajax('POST', '/api/files/move', {
    values: { source, destination },
    swap: 'none'  // Server uses hx-swap-oob
});

// ❌ Bad: Manual fetch + DOM manipulation
const response = await fetch('/api/files/move', {...});
const html = await response.text();
target.innerHTML = html;
htmx.process(target);  // This is now unnecessary

2. Listen to HTMX events instead of DOM observers:

// ✅ Good: React to HTMX swaps
document.body.addEventListener('htmx:afterSwap', (event) => {
    if (event.detail.target.id === 'file-tree') {
        updateDraggableAttributes();
    }
});

document.body.addEventListener('htmx:oobAfterSwap', (event) => {
    if (event.detail.target.id === 'file-tree') {
        updateDraggableAttributes();
    }
});

// ❌ Bad: MutationObserver (performance overhead)
const observer = new MutationObserver(() => {...});
observer.observe(element, { childList: true, subtree: true });

3. Let HTMX process out-of-band swaps automatically: The server returns HTML with hx-swap-oob attributes. HTMX automatically finds these elements and swaps them, even when they're not the primary target.

// Server response (Go template)
<div id="file-tree" hx-swap-oob="innerHTML">
    <!-- Updated file tree HTML -->
</div>

HTMX automatically:

  • Finds the element with id="file-tree"
  • Replaces its innerHTML
  • Processes any HTMX attributes in the new content
  • Triggers htmx:oobAfterSwap event

4. Event-driven architecture:

// File tree initialization (file-tree.js)
class FileTree {
    constructor() {
        this.init();
    }

    init() {
        // Use event delegation on stable parent
        const sidebar = document.getElementById('sidebar');

        // Click handlers (delegated)
        sidebar.addEventListener('click', (e) => {
            const folderHeader = e.target.closest('.folder-header');
            if (folderHeader) this.toggleFolder(folderHeader);
        });

        // Drag-and-drop handlers (delegated)
        sidebar.addEventListener('dragstart', (e) => {...});
        sidebar.addEventListener('drop', (e) => {...});

        // React to HTMX updates
        document.body.addEventListener('htmx:oobAfterSwap', (event) => {
            if (event.detail.target.id === 'file-tree') {
                this.updateDraggableAttributes();
            }
        });
    }
}

Implementation Examples

Creating a folder (file-tree.js:438-476):

htmx.ajax('POST', '/api/folders/create', {
    values: { path: folderName },
    swap: 'none'  // Server handles oob swap
}).then(() => {
    hideNewFolderModal();
}).catch(() => {
    alert('Erreur lors de la création du dossier');
});

Moving a file (file-tree.js:338-362):

htmx.ajax('POST', '/api/files/move', {
    values: { source: sourcePath, destination: destinationPath },
    swap: 'none'  // Server handles oob swap
}).then(() => {
    console.log('File moved successfully');
});

Benefits of This Architecture

  1. Less Code: ~60 lines removed by eliminating manual DOM manipulation
  2. Better Performance: HTMX events instead of MutationObserver
  3. Consistency: All server interactions use the same pattern
  4. Maintainability: Clear separation between HTMX (data) and JavaScript (UI enhancements)
  5. Reliability: HTMX handles edge cases (race conditions, partial updates, etc.)

When to Use What

Use HTMX for:

  • Loading content from server
  • Form submissions
  • Search/filtering
  • File operations (move, delete, create)
  • Automatic DOM updates

Use JavaScript for:

  • CodeMirror editor initialization
  • Drag-and-drop UI logic
  • Slash command palette
  • Scroll synchronization
  • View mode toggles
  • Client-side animations

Never:

  • Parse HTML manually from fetch() responses
  • Call htmx.process() manually (HTMX does it automatically)
  • Use MutationObserver when HTMX events are available
  • Mix fetch() and htmx.ajax() for similar operations

Note Format

Notes have YAML front matter with these fields:

---
title: "Note Title"
date: "08-11-2025"
last_modified: "08-11-2025:13:02"
tags: [tag1, tag2]
---

The indexer package handles both single-value and array-format tags via custom UnmarshalYAML.

Development Commands

Building the Frontend

IMPORTANT: The frontend must be built before running the application. The compiled JavaScript is required.

cd frontend
npm install          # Install dependencies (first time only)
npm run build       # Compile frontend modules to static/dist/

Output files (loaded by templates):

  • static/dist/project-notes-frontend.es.js (ES module)
  • static/dist/project-notes-frontend.umd.js (UMD format)

Frontend dependencies (from frontend/package.json):

  • @codemirror/basic-setup - Base editor functionality
  • @codemirror/lang-markdown - Markdown language support
  • @codemirror/state - Editor state management
  • @codemirror/view - Editor view layer
  • @codemirror/theme-one-dark - Dark theme
  • vite - Build tool

Running the Server

go run ./cmd/server

Server starts on http://localhost:8080. Use flags to customize:

  • -addr :PORT - Change server address (default: :8080)
  • -notes-dir PATH - Change notes directory (default: ./notes)

Testing

Run all tests:

go test ./...

Run specific package tests:

go test ./internal/indexer
go test ./internal/api

Run tests with verbose output:

go test -v ./...

Building the Server Binary

go build ./cmd/server

Backend Dependencies

Update Go dependencies:

go mod tidy

Key backend dependencies:

  • github.com/fsnotify/fsnotify - Filesystem watcher
  • gopkg.in/yaml.v3 - YAML parsing for front matter

Vite Build System

The frontend uses Vite (frontend/vite.config.js) for bundling JavaScript modules:

Configuration:

  • Entry Point: frontend/src/main.js (imports editor.js, file-tree.js, ui.js)
  • Output Directory: static/dist/ (served by Go server)
  • Library Mode: Builds as a library with both ES and UMD formats
  • Single Bundle: No code splitting - all dependencies bundled together
  • Aliases: Path aliases for @codemirror/state and @codemirror/view to ensure consistent versions

Build Process:

  1. Vite reads all source files from frontend/src/
  2. Resolves npm dependencies (@codemirror packages)
  3. Bundles everything into two formats:
    • ES module (project-notes-frontend.es.js) - 1.0 MB
    • UMD (project-notes-frontend.umd.js) - 679 KB
  4. Outputs to static/dist/ where Go server can serve them
  5. Templates load the ES module version via <script type="module">

Development Workflow:

cd frontend
npm run build          # Build for production
npm run build -- --watch  # Watch mode for development

The Go server does not need to be restarted after frontend builds - simply refresh the browser.

Key Implementation Details

Front Matter Management

The system automatically manages front matter when saving notes:

  • title: Auto-generated from filename if missing (converts hyphens to spaces, title-cases)
  • date: Set to creation date for new files, preserved for existing files
  • last_modified: Always updated to current timestamp on save (format: DD-MM-YYYY:HH:MM)
  • tags: Preserved from user input; defaults to ["default"] for new notes

Front matter extraction happens in two places:

  • indexer.ExtractFrontMatterAndBody() - For file paths
  • indexer.ExtractFrontMatterAndBodyFromReader() - For io.Reader (used when parsing form content)

The indexer maintains two data structures:

  • tags map[string][]string - Maps tags to file paths for tag-based lookup
  • docs map[string]*Document - Stores full document metadata for rich search

Re-indexing happens:

  • On server startup
  • When files are created/modified/deleted (via watcher with 200ms debounce)
  • After POST/DELETE/MOVE operations (background goroutine)

Rich search supports multiple query formats:

  • General terms: Search across title, tags, path, and body (AND logic)
  • tag:value - Filter by specific tag (case-insensitive)
  • title:value - Filter by title content
  • path:value - Filter by file path
  • Quoted phrases: "exact phrase" preserves spaces
  • Results are scored and ranked by relevance (title matches score highest)

REST API (v1)

The application includes a full REST API for programmatic access:

Implementation: internal/api/rest_handler.go

Endpoints:

  • GET /api/v1/notes - List all notes with metadata (JSON)
  • GET /api/v1/notes/{path} - Get a specific note (JSON or Markdown based on Accept header)
  • PUT /api/v1/notes/{path} - Create or update a note (accepts JSON or raw Markdown)
  • DELETE /api/v1/notes/{path} - Delete a note

Content Negotiation:

  • Accept: application/json → Returns JSON with full metadata
  • Accept: text/markdown → Returns raw Markdown content
  • Content-Type: application/json → Accepts structured JSON request
  • Content-Type: text/markdown → Accepts raw Markdown body

Key Features:

  • Automatic front matter generation for new notes
  • Front matter update (last_modified) on PUT operations
  • Background re-indexing after modifications
  • Path validation (same security as HTML endpoints)
  • Supports nested folders (creates parent directories automatically)

Documentation: See API.md for full REST API documentation with examples.

Search Modal

A modern command-palette style search modal is available:

Implementation: frontend/src/search.js

Features:

  • Keyboard shortcut: Ctrl/Cmd+K to open anywhere
  • Real-time search with 300ms debounce
  • Keyboard navigation: / to navigate, Enter to open, Esc to close
  • Highlighting of search terms in results
  • Uses existing search API (/api/search)
  • Displays results with icons, titles, paths, snippets, tags, and dates

Styling: Custom styles in static/theme.css with Material Darker theme integration.

Security Considerations

File path validation in handler.go and rest_handler.go:

  • filepath.Clean() to normalize paths
  • Reject paths starting with .. or absolute paths (directory traversal prevention)
  • Enforce .md extension for notes
  • Use filepath.Join() to construct safe paths within notes directory
  • DOMPurify sanitizes Markdown-rendered HTML to prevent XSS attacks

REST API Security:

  • No authentication currently implemented
  • Recommend using reverse proxy (nginx, Caddy) with auth for public exposure
  • CORS not configured (same-origin only)
  • No rate limiting (add middleware if needed)

Template System

Templates are pre-parsed at startup. The API handler returns HTML fragments that htmx inserts into the page. Out-of-band swaps update the file tree sidebar without full page reload.

Concurrency

  • Indexer uses sync.RWMutex for thread-safe access (read locks for searches, write locks for re-indexing)
  • Re-indexing after saves/deletes/moves happens in background goroutines to avoid blocking HTTP responses
  • Watcher runs in separate goroutine with proper context cancellation
  • Server shutdown uses 5-second graceful shutdown timeout with context

File Organization

The application supports hierarchical note organization:

  • Notes can be organized in subdirectories within the notes/ directory
  • The file tree displays folders and files in a hierarchical view
  • Folders are displayed before files, both sorted alphabetically (case-insensitive)
  • API endpoints for folder creation (/api/folders/create) and file moving (/api/files/move)
  • Watcher recursively monitors all subdirectories for changes

CodeMirror 6 Editor Implementation

The editor is implemented in frontend/src/editor.js with two main classes:

MarkdownEditor Class

Manages the CodeMirror 6 instance and its interactions:

Initialization:

EditorState.create({
    doc: content,
    extensions: [
        basicSetup,              // Line numbers, search, fold gutter
        markdown(),              // Markdown syntax support
        oneDark,                 // One Dark theme
        keymap.of([indentWithTab]),
        EditorView.updateListener.of(...),  // Preview & auto-save
        keymap.of([{ key: "Mod-s", run: saveFunction }])
    ]
});

Key Features:

  • Live Preview: Updates preview pane with 150ms debounce on editor changes
  • Auto-Save: Triggers form submission after 2 seconds of inactivity
  • Scroll Sync: Bidirectional scroll synchronization between editor and preview (50ms debounce)
  • Front Matter Stripping: Removes YAML front matter from preview rendering
  • View Modes: Three modes (split/editor-only/preview-only) saved to localStorage
  • Height Management: Dynamic height calculation (window.innerHeight - 180px) with resize handling
  • Cleanup: Proper destruction of editor instances to prevent memory leaks

Integration:

  • Replaces the hidden textarea element
  • HTMX events trigger editor initialization (htmx:afterSwap)
  • Form submission uses requestSubmit() to trigger HTMX POST

SlashCommands Class

Provides command palette for quick Markdown insertion:

Trigger: Type / at the beginning of a line to open the palette

Implementation:

  • Monitors editor state changes for / character
  • Filters commands in real-time as user types
  • Positions palette dynamically using CodeMirror's coordsAtPos()
  • Keyboard navigation with arrow keys, Enter/Tab to select
  • Inserts Markdown text using CodeMirror transactions
  • 13 built-in commands with text templates

UI/UX:

  • Styled palette with gradient selection indicator
  • Smooth animations and transitions
  • Accessible keyboard navigation
  • Auto-closes on selection or Escape key

Slash Commands

The editor includes a slash command system integrated with CodeMirror 6:

  • Type / at the start of a line to trigger the command palette
  • Available commands (13 total):
    • Headings: h1, h2, h3 - Insert Markdown headers
    • Formatting: bold, italic, code - Text formatting
    • Blocks: codeblock, quote, hr, table - Block-level elements
    • Lists: list - Unordered list
    • Dynamic: date - Insert current date in French format (DD/MM/YYYY)
    • Links: link - Insert link template [text](url)
  • Navigate with Arrow Up/Down, select with Enter/Tab, cancel with Escape
  • Commands are filtered in real-time as you type after the /
  • The palette is positioned dynamically near the cursor using CodeMirror coordinates
  • Implementation in frontend/src/editor.js with the SlashCommands class
  • Styled command palette with gradient selection indicator

Frontend Libraries

The application uses a mix of npm packages (for the editor) and CDN-loaded libraries (for utilities):

NPM Packages (Built with Vite)

Managed in frontend/package.json:

  • @codemirror/basic-setup (^0.20.0): Base editor functionality (line numbers, search, etc.)
  • @codemirror/lang-markdown (^6.5.0): Markdown language support and syntax highlighting
  • @codemirror/state (^6.5.2): Editor state management
  • @codemirror/view (^6.38.6): Editor view layer and rendering
  • @codemirror/theme-one-dark (^6.1.3): Dark theme for CodeMirror
  • vite (^5.0.0): Build tool for bundling frontend modules

CDN Libraries

Loaded in templates/index.html:

  • htmx (1.9.10): AJAX interactions and dynamic content loading
  • marked.js: Markdown to HTML conversion for preview
  • DOMPurify: HTML sanitization to prevent XSS attacks
  • Highlight.js (11.9.0): Syntax highlighting for code blocks in preview with Atom One Dark theme

Styling

  • Material Darker Theme: Custom dark theme in static/theme.css
  • Color System: CSS custom properties for consistent theming
    • Background colors: --bg-primary, --bg-secondary, --bg-tertiary, --bg-elevated
    • Text colors: --text-primary, --text-secondary, --text-muted
    • Accent colors: --accent-blue, --accent-violet
  • No CSS Framework: All styles hand-crafted with CSS Grid and Flexbox
  • Responsive Design: Adaptive layout for different screen sizes
  • Custom Scrollbars: Styled scrollbars matching the dark theme

Build Output

The Vite build process produces:

  • static/dist/project-notes-frontend.es.js - ES module format (1.0 MB, includes all CodeMirror 6 dependencies)
  • static/dist/project-notes-frontend.umd.js - UMD format (679 KB, legacy compatibility)

Project Structure

project-notes/
├── cmd/
│   └── server/
│       └── main.go              # Server entry point
├── internal/
│   ├── api/
│   │   └── handler.go           # HTTP handlers for CRUD operations
│   ├── indexer/
│   │   └── indexer.go           # Note indexing and search
│   └── watcher/
│       └── watcher.go           # Filesystem watcher with fsnotify
├── frontend/                    # Frontend build system (NEW)
│   ├── src/
│   │   ├── main.js              # Entry point
│   │   ├── editor.js            # CodeMirror 6 implementation (26 KB)
│   │   ├── file-tree.js         # Drag-and-drop file management (11 KB)
│   │   └── ui.js                # Sidebar toggle (720 B)
│   ├── package.json             # NPM dependencies
│   ├── package-lock.json
│   └── vite.config.js           # Vite build configuration
├── static/
│   ├── dist/                    # Compiled frontend (generated)
│   │   ├── project-notes-frontend.es.js
│   │   └── project-notes-frontend.umd.js
│   └── theme.css                # Material Darker theme
├── templates/
│   ├── index.html               # Main page layout
│   ├── editor.html              # Editor component
│   ├── file-tree.html           # File tree sidebar
│   ├── search-results.html      # Search results
│   └── new-note-prompt.html     # New note modal
├── notes/                       # Note storage directory
│   └── *.md                     # Markdown files with YAML front matter
├── go.mod                       # Go dependencies
├── go.sum
└── CLAUDE.md                    # This file

Key Files to Edit

Backend Development:

  • cmd/server/main.go - Server initialization and routing
  • internal/api/handler.go - API endpoints and request handling
  • internal/indexer/indexer.go - Search and indexing logic
  • internal/watcher/watcher.go - Filesystem monitoring

Frontend Development:

  • frontend/src/editor.js - CodeMirror editor, preview, slash commands
  • frontend/src/file-tree.js - File tree interactions and drag-and-drop
  • frontend/src/ui.js - UI utilities (sidebar toggle)
  • static/theme.css - Styling and theming
  • templates/*.html - HTML templates (Go template syntax)

Configuration:

  • frontend/vite.config.js - Frontend build configuration
  • frontend/package.json - NPM dependencies and scripts
  • go.mod - Go dependencies

Important Notes

  1. Frontend builds are required: The application will not work without compiled JavaScript in static/dist/
  2. No hot reload for frontend: Changes to frontend/src/ require running npm run build and refreshing the browser
  3. Backend changes: Require restarting the Go server (go run ./cmd/server)
  4. Template changes: Require restarting the Go server (templates are pre-parsed at startup)
  5. CSS changes: Only require browser refresh (loaded via <link> tag)
  6. Note changes: Automatically detected by filesystem watcher, trigger re-indexing