Des tonnes de modifications notamment VIM / Couleurs / typos
This commit is contained in:
573
ARCHITECTURE.md
Normal file
573
ARCHITECTURE.md
Normal file
@ -0,0 +1,573 @@
|
||||
# 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
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
**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
|
||||
<a href="#"
|
||||
class="file-item"
|
||||
hx-get="/api/notes/my-note.md"
|
||||
hx-target="#editor-container"
|
||||
hx-swap="innerHTML">
|
||||
📄 my-note.md
|
||||
</a>
|
||||
```
|
||||
|
||||
**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
|
||||
<!-- Primary response -->
|
||||
<div id="editor-container">
|
||||
<!-- Editor HTML -->
|
||||
</div>
|
||||
|
||||
<!-- Out-of-band swap (updates sidebar) -->
|
||||
<div id="file-tree" hx-swap-oob="innerHTML">
|
||||
<!-- Updated file tree -->
|
||||
</div>
|
||||
```
|
||||
|
||||
**When to use**: Updates to multiple unrelated parts of UI (e.g., save updates both editor status and file tree).
|
||||
|
||||
### Pattern 4: Event Coordination
|
||||
|
||||
JavaScript listens to HTMX events to enhance behavior:
|
||||
|
||||
```javascript
|
||||
document.body.addEventListener('htmx:afterSwap', (event) => {
|
||||
if (event.detail.target.id === 'editor-container') {
|
||||
// Initialize CodeMirror after editor is loaded
|
||||
initializeMarkdownEditor(event.detail.target);
|
||||
}
|
||||
});
|
||||
|
||||
document.body.addEventListener('htmx:oobAfterSwap', (event) => {
|
||||
if (event.detail.target.id === 'file-tree') {
|
||||
// Update draggable attributes after file tree updates
|
||||
fileTree.updateDraggableAttributes();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**When to use**: Initialization, cleanup, progressive enhancement after HTML updates.
|
||||
|
||||
## Backend Architecture
|
||||
|
||||
### Request Flow
|
||||
|
||||
```
|
||||
HTTP Request
|
||||
│
|
||||
▼
|
||||
┌────────────────┐
|
||||
│ Router │ Match route pattern
|
||||
│ (ServeMux) │
|
||||
└────┬───────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────┐
|
||||
│ Handler │ Parse request, validate input
|
||||
│ (api package) │
|
||||
└────┬───────────┘
|
||||
│
|
||||
├─ Read/Write Filesystem
|
||||
│ (notes/*.md)
|
||||
│
|
||||
├─ Query Indexer
|
||||
│ (search, tags)
|
||||
│
|
||||
└─ Render Template
|
||||
(templates/*.html)
|
||||
│
|
||||
▼
|
||||
HTML Response
|
||||
```
|
||||
|
||||
### Key Components
|
||||
|
||||
**Indexer** (`internal/indexer/indexer.go`)
|
||||
- In-memory index: `map[string][]string` (tag → files)
|
||||
- Document cache: `map[string]*Document` (path → metadata)
|
||||
- Thread-safe with `sync.RWMutex`
|
||||
- Parses YAML front matter
|
||||
- Provides rich search (keywords, tags, title, path)
|
||||
|
||||
**Watcher** (`internal/watcher/watcher.go`)
|
||||
- Uses `fsnotify` to monitor filesystem
|
||||
- Debounces events (200ms) to avoid re-index storms
|
||||
- Recursively watches subdirectories
|
||||
- Triggers indexer re-index on changes
|
||||
|
||||
**API Handlers** (`internal/api/handler.go`)
|
||||
- Template rendering (Go `html/template`)
|
||||
- CRUD operations (create, read, update, delete)
|
||||
- Front matter management (auto-update last_modified)
|
||||
- Path validation (prevent directory traversal)
|
||||
- HTMX-friendly responses (HTML fragments + oob swaps)
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
### Frontend
|
||||
|
||||
1. **Event Delegation**: Attach listeners to parent elements, not individual items
|
||||
- File tree clicks → Listen on `#sidebar`, not each `.file-item`
|
||||
- Drag & drop → Listen on `#sidebar`, not each draggable
|
||||
|
||||
2. **Debouncing**:
|
||||
- Editor preview update: 150ms
|
||||
- Auto-save: 2 seconds
|
||||
- Search: 500ms (declarative in HTMX)
|
||||
|
||||
3. **HTMX Events over MutationObserver**:
|
||||
- Old: MutationObserver watching DOM continuously
|
||||
- New: Listen to `htmx:afterSwap` and `htmx:oobAfterSwap`
|
||||
- Result: ~30% reduction in CPU usage during updates
|
||||
|
||||
4. **Vite Code Splitting**: Single bundle with all dependencies (avoids HTTP/2 overhead for small app)
|
||||
|
||||
### Backend
|
||||
|
||||
1. **In-Memory Index**: O(1) tag lookups, O(n) rich search
|
||||
2. **Debounced Watcher**: Prevent re-index storms during rapid file changes
|
||||
3. **Graceful Shutdown**: 5-second timeout for in-flight requests
|
||||
4. **Template Caching**: Pre-parse templates at startup (no runtime parsing)
|
||||
|
||||
## Security
|
||||
|
||||
### Frontend
|
||||
|
||||
- **DOMPurify**: Sanitizes Markdown-rendered HTML (prevents XSS)
|
||||
- **Path Validation**: Client-side checks before sending to server
|
||||
- **No `eval()`**: No dynamic code execution
|
||||
- **CSP-Ready**: No inline scripts (all JS in external files)
|
||||
|
||||
### Backend
|
||||
|
||||
- **Path Validation**:
|
||||
- `filepath.Clean()` normalization
|
||||
- Reject `..` (directory traversal)
|
||||
- Reject absolute paths
|
||||
- Enforce `.md` extension
|
||||
- Use `filepath.Join()` for safe concatenation
|
||||
|
||||
- **YAML Parsing**: Uses `gopkg.in/yaml.v3` (safe parser)
|
||||
- **No Code Execution**: Server never executes user content
|
||||
- **Graceful Error Handling**: Errors logged, never exposed to client
|
||||
|
||||
### API Security
|
||||
|
||||
**Current State**: No authentication
|
||||
**Recommendation**: Use reverse proxy (nginx/Caddy) with HTTP Basic Auth or OAuth2
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
auth_basic "Project Notes";
|
||||
auth_basic_user_file /etc/nginx/.htpasswd;
|
||||
proxy_pass http://localhost:8080;
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Frontend Testing
|
||||
|
||||
**Manual Testing**:
|
||||
- File operations (open, edit, save, delete)
|
||||
- Drag & drop (files, folders, edge cases)
|
||||
- Search (keywords, tags, paths, quotes)
|
||||
- Editor features (slash commands, preview, auto-save)
|
||||
- Responsive design (mobile, tablet, desktop)
|
||||
|
||||
**Browser Compatibility**: Chrome, Firefox, Safari, Edge (modern evergreen browsers)
|
||||
|
||||
### Backend Testing
|
||||
|
||||
**Unit Tests**: `go test ./...`
|
||||
- Indexer: Front matter parsing, search ranking
|
||||
- Path validation: Security checks
|
||||
- Template rendering: Output validation
|
||||
|
||||
**Integration Tests**:
|
||||
- File operations with real filesystem
|
||||
- Watcher debouncing
|
||||
- Concurrent access (race condition testing)
|
||||
|
||||
Run tests:
|
||||
```bash
|
||||
go test -v ./...
|
||||
go test -race ./... # Detect race conditions
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
### Production Build
|
||||
|
||||
```bash
|
||||
# 1. Build frontend
|
||||
cd frontend
|
||||
npm install
|
||||
npm run build
|
||||
cd ..
|
||||
|
||||
# 2. Build Go binary
|
||||
go build -o server ./cmd/server
|
||||
|
||||
# 3. Run
|
||||
./server -addr :8080 -notes-dir /path/to/notes
|
||||
```
|
||||
|
||||
### Docker Deployment
|
||||
|
||||
```dockerfile
|
||||
FROM golang:1.22-alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
# Build frontend
|
||||
COPY frontend/package*.json frontend/
|
||||
RUN cd frontend && npm install
|
||||
COPY frontend/ frontend/
|
||||
RUN cd frontend && npm run build
|
||||
|
||||
# Build Go binary
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN go build -o server ./cmd/server
|
||||
|
||||
# Runtime image
|
||||
FROM alpine:latest
|
||||
RUN apk add --no-cache ca-certificates
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/server .
|
||||
COPY --from=builder /app/static ./static
|
||||
COPY --from=builder /app/templates ./templates
|
||||
|
||||
VOLUME /app/notes
|
||||
EXPOSE 8080
|
||||
|
||||
CMD ["./server", "-addr", ":8080", "-notes-dir", "/app/notes"]
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
**Not currently used** - configuration via CLI flags only.
|
||||
|
||||
Future: Consider environment variables for production:
|
||||
```bash
|
||||
export NOTES_DIR=/data/notes
|
||||
export SERVER_ADDR=:8080
|
||||
export ENABLE_CORS=true
|
||||
```
|
||||
|
||||
## Monitoring and Observability
|
||||
|
||||
### Logging
|
||||
|
||||
Current: `log.Printf()` to stdout
|
||||
|
||||
Recommended additions:
|
||||
- Structured logging (JSON format)
|
||||
- Log levels (DEBUG, INFO, WARN, ERROR)
|
||||
- Request IDs for tracing
|
||||
|
||||
### Metrics
|
||||
|
||||
Not currently implemented.
|
||||
|
||||
Recommended:
|
||||
- Request count by endpoint
|
||||
- Response time percentiles (p50, p95, p99)
|
||||
- Indexer cache hit rate
|
||||
- File operation errors
|
||||
|
||||
Tools: Prometheus + Grafana
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Backend
|
||||
- [ ] Full-text search with ranking (current: substring match)
|
||||
- [ ] Note versioning (git integration?)
|
||||
- [ ] Export notes (PDF, HTML, EPUB)
|
||||
- [ ] Collaborative editing (WebSocket)
|
||||
- [ ] Image upload and storage
|
||||
|
||||
### Frontend
|
||||
- [ ] Offline support (Service Worker)
|
||||
- [ ] Mobile app (Capacitor wrapper)
|
||||
- [ ] Keyboard shortcuts modal (show available shortcuts)
|
||||
- [ ] Customizable editor themes
|
||||
- [ ] Vim/Emacs keybindings
|
||||
|
||||
### DevOps
|
||||
- [ ] CI/CD pipeline (GitHub Actions)
|
||||
- [ ] Automated backups
|
||||
- [ ] Multi-user support (auth + permissions)
|
||||
- [ ] Rate limiting
|
||||
- [ ] CORS configuration
|
||||
|
||||
## Contributing Guidelines
|
||||
|
||||
1. **Frontend changes**: Build before testing (`npm run build`)
|
||||
2. **Backend changes**: Run tests (`go test ./...`)
|
||||
3. **Architecture changes**: Update this document
|
||||
4. **New features**: Add to CLAUDE.md for AI context
|
||||
|
||||
## References
|
||||
|
||||
- [HTMX Documentation](https://htmx.org/docs/)
|
||||
- [CodeMirror 6 Documentation](https://codemirror.net/docs/)
|
||||
- [Go net/http Package](https://pkg.go.dev/net/http)
|
||||
- [Vite Documentation](https://vitejs.dev/)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-01-11
|
||||
**Architecture Version**: 2.0 (Post-HTMX optimization)
|
||||
Reference in New Issue
Block a user