From c939cdd463b8d06a67074e4d45405cf7b7e54ef9 Mon Sep 17 00:00:00 2001 From: Gamosoft Date: Mon, 8 Jun 2026 11:02:05 +0200 Subject: [PATCH] configurable autosave --- backend/main.py | 11 +++++++++++ config.yaml | 9 ++++++++- documentation/ENVIRONMENT_VARIABLES.md | 24 ++++++++++++++++++++++++ frontend/app.js | 11 ++++++++--- 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/backend/main.py b/backend/main.py index bcf5ff2..72781ff 100644 --- a/backend/main.py +++ b/backend/main.py @@ -230,6 +230,16 @@ UPLOAD_MAX_AUDIO_MB = int(os.getenv('UPLOAD_MAX_AUDIO_MB', '50')) UPLOAD_MAX_VIDEO_MB = int(os.getenv('UPLOAD_MAX_VIDEO_MB', '100')) UPLOAD_MAX_PDF_MB = int(os.getenv('UPLOAD_MAX_PDF_MB', '20')) +# Autosave debounce in milliseconds (applies to note typing AND drawing PNG autosave). +try: + _autosave_raw = int(os.getenv( + 'AUTOSAVE_DELAY_MS', + str(config.get('ui', {}).get('autosave_delay_ms', 1000)) + )) +except (TypeError, ValueError): + _autosave_raw = 1000 +AUTOSAVE_DELAY_MS = max(250, min(60000, _autosave_raw)) + if DEMO_MODE: # Enable rate limiting for demo deployments limiter = Limiter(key_func=get_remote_address, default_limits=["200/hour"]) @@ -488,6 +498,7 @@ async def get_config(): "searchEnabled": config['search']['enabled'], "demoMode": DEMO_MODE, # Expose demo mode flag to frontend "alreadyDonated": ALREADY_DONATED, # Hide support buttons if true + "autosaveDelayMs": AUTOSAVE_DELAY_MS, # Debounce for note/drawing autosave "authentication": { "enabled": config.get('authentication', {}).get('enabled', False) } diff --git a/config.yaml b/config.yaml index a62458a..770f7bb 100644 --- a/config.yaml +++ b/config.yaml @@ -22,7 +22,14 @@ storage: search: enabled: true - + +# User interface behavior +ui: + # Autosave debounce in milliseconds (applies to note typing and drawing autosave). + # Lower = saves more often (more disk writes); higher = saves less often. + # Override via the AUTOSAVE_DELAY_MS env var. Server clamps to 250-60000ms. + autosave_delay_ms: 1000 + authentication: # Authentication settings # Set enabled to true to require login diff --git a/documentation/ENVIRONMENT_VARIABLES.md b/documentation/ENVIRONMENT_VARIABLES.md index 3dfe1b9..138c1d3 100644 --- a/documentation/ENVIRONMENT_VARIABLES.md +++ b/documentation/ENVIRONMENT_VARIABLES.md @@ -67,6 +67,30 @@ environment: - UPLOAD_MAX_VIDEO_MB=500 ``` +### User Interface + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `AUTOSAVE_DELAY_MS` | integer | `1000` | Autosave debounce in milliseconds (applies to note typing and drawing autosave). Server-clamped to 250–60000ms. | + +#### Example: Slower autosave for very large notes + +```bash +# Docker +docker run -e AUTOSAVE_DELAY_MS=5000 ... + +# Docker Compose +environment: + - AUTOSAVE_DELAY_MS=5000 +``` + +Equivalent in `config.yaml`: + +```yaml +ui: + autosave_delay_ms: 5000 +``` + ## 🎯 Configuration Priority Configuration is loaded in this order (later overrides earlier): diff --git a/frontend/app.js b/frontend/app.js index 70c8ad3..4e0507c 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -2,7 +2,7 @@ // Configuration constants const CONFIG = { - AUTOSAVE_DELAY: 1000, // ms - Debounce before note save (autoSave) and drawing PNG autosave (_drawingScheduleAutosave) + AUTOSAVE_DELAY: 1000, // ms - Fallback only; runtime value lives on this.autosaveDelayMs (hydrated from /api/config). Used by autoSave() and _drawingScheduleAutosave(). /** Must match drawingRedraw() fill and eraser stroke color (opaque “whiteboard”). */ DRAWING_BACKGROUND: '#ffffff', /** @@ -260,6 +260,7 @@ function noteApp() { authEnabled: false, demoMode: false, alreadyDonated: false, + autosaveDelayMs: CONFIG.AUTOSAVE_DELAY, // hydrated from /api/config in loadConfig() notes: [], currentNote: '', currentNoteName: '', @@ -959,6 +960,10 @@ function noteApp() { this.authEnabled = config.authentication?.enabled || false; this.demoMode = config.demoMode || false; this.alreadyDonated = config.alreadyDonated || false; + // Server already clamps autosaveDelayMs to a sane range; we just sanity-check the type. + if (Number.isFinite(config.autosaveDelayMs) && config.autosaveDelayMs > 0) { + this.autosaveDelayMs = config.autosaveDelayMs; + } } catch (error) { console.error('Failed to load config:', error); } @@ -3253,7 +3258,7 @@ function noteApp() { this._drawingAutosaveTimeout = null; this.drawingSave(); }; - this._drawingAutosaveTimeout = setTimeout(attemptSave, CONFIG.AUTOSAVE_DELAY); + this._drawingAutosaveTimeout = setTimeout(attemptSave, this.autosaveDelayMs); }, /** @@ -5064,7 +5069,7 @@ function noteApp() { this.commitToHistory(); } this.saveNote(); - }, CONFIG.AUTOSAVE_DELAY); + }, this.autosaveDelayMs); }, // Mark that we have pending changes (called on each keystroke)