17 Commits

Author SHA1 Message Date
c9126f718d DB resilience: hourly WAL checkpoint + daily rolling 7-day backup
- Hourly PASSIVE WAL checkpoint prevents unbounded WAL growth and
  ensures all writes are merged into the main .db file regularly.
  Previously the WAL was never checkpointed — all data was accumulating
  in stories.db-wal with no protection if that file was lost.
- Daily backup using better-sqlite3 .backup() writes a safe online
  snapshot to data/backups/stories-YYYY-MM-DD.db on startup and
  every 24 h; keeps last 7 days, pruning older ones automatically.
- busy_timeout = 5000 so concurrent requests wait briefly rather
  than failing with SQLITE_BUSY.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 21:15:15 -04:00
c6d1554215 Typewriter mode, SVG alignment icons, highlight readability fix
Typewriter mode (⌨ toolbar button):
- Registers TipTap update/selectionUpdate listeners via editor.on()
- On each change, uses coordsAtPos + window.scrollBy to keep cursor
  at 50% of viewport height — instant, no scroll-lag
- Adds 50vh padding-bottom so the last line can reach screen centre
- Toggles .typewriter class on <html>; cleans up on unmount

Alignment buttons:
- Replaced L/C/R text labels with inline SVG stacked-line icons
  (left, centre, right, justify) — standard text-editor appearance
- Added Justify as a fourth option

Highlight readability:
- Added color: #111 !important to .editor-body .ProseMirror mark
  so highlighted text is always dark-on-light regardless of theme

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 21:00:14 -04:00
1d00d86709 Add read-aloud: reads story from cursor to end using Web Speech API
- 🔊 button at far-right of toolbar; toggles to ⏹ while reading
- Extracts plain text from cursor position to end of document via
  editor.state.doc.textBetween — works with cursor or selection
- Splits into sentence chunks to work around Chrome's ~15s utterance
  cutoff bug; each chunk's onend fires the next
- isReadingRef guards the closure so Stop cancels mid-sentence cleanly
- Gentle pulse animation on the active button (reading-pulse keyframe)
- No server required — uses built-in browser speechSynthesis

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:55:50 -04:00
48d3533bfc Prompt queue: pre-fetch 5, instant local fallback, emoji source icon
- Refill a queue of 5 AI prompts in the background on page load
- Track in-flight fetches with pendingFetches ref so we never over-request
- fetchPrompt() is now synchronous: pops from queue or instantly returns a
  shuffled built-in (LOCAL_PROMPTS, 12 entries, no-repeat cycling)
- Source badge replaced with a small emoji in the top-right corner of the
  popover: 🤖 for AI prompts, 📚 for built-ins
- Removed promptLoading state and spinner entirely

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:48:31 -04:00
c5b7d7f774 Fix AI prompt URL: use /ollama/v1/chat/completions for Open WebUI
The server at ai.binarygnome.com is Open WebUI, not a bare Ollama
instance. Open WebUI proxies Ollama at /ollama/v1/... rather than /v1/...

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:35:11 -04:00
6f12efd3fb Populate .env.example with all configurable variables
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:31:36 -04:00
0cae6c6188 Fix AI prompts: switch to OpenAI-compatible /v1/chat/completions API
The server at ai.binarygnome.com returns 405 on POST /api/generate,
which means it speaks the OpenAI-compatible API rather than the native
Ollama format. Switch to /v1/chat/completions with messages[] payload
and data.choices[0].message.content response parsing.

Also add optional OLLAMA_API_KEY env var for servers that require a
Bearer token.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:27:31 -04:00
a4a982aed7 Fix /api/prompts/test — move before auth middleware so it's accessible without a login
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:25:17 -04:00
37448be5a8 Add notes panel, font picker, sticky toolbar, and prompt improvements
- Notes: per-story rich-text notes panel (right drawer) with TipTap
  editor, image support, autosave, and full CRUD API
- Font picker: 15 Google Fonts selectable from a floating Aa button,
  persisted to localStorage via --font-body CSS variable
- Sticky toolbar: pulled formatting bar out of overflow:hidden wrapper
  so it sticks below the topbar while scrolling
- Prompts: 100 additional built-in prompts (120 total) in a shuffled
  no-repeat queue; pre-fetch on page load so the AI has time to respond;
  timeout raised to 45s; error logging + /api/prompts/test debug endpoint;
  source badge shows whether prompt came from AI or built-in list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 20:21:26 -04:00
b7baf4fa15 Rename app to Grimoire, add book+crescent moon SVG icon
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:46 -04:00
88e4441222 Fix mobile topbar horizontal overflow
Wrap button labels in spans so the existing hide-on-mobile rule fires.
Hide all topbar text labels and save status on small screens, leaving
only icons. Add overflow-x: hidden to html as a safety net.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:48:41 -04:00
219fd87e5e Remove leftover pocketbase lib
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 12:11:41 -04:00
9afb1dd4c5 Switch from email to username, add password change and admin reset
- Login now uses username instead of email
- DB migration renames email -> username on existing databases
- Users can change their own password from the Stories page
- Admin can reset any user's password from the admin panel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 12:11:27 -04:00
635f56e44b Fix service DNS by using explicit named network
network_mode: bridge uses Docker's default bridge which has no
service-name DNS. An explicit named network gets its own resolver
so nginx can resolve "server" by name. Also adds a healthcheck so
app waits until server is actually ready before nginx starts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 11:59:53 -04:00
18d238a602 Fix nginx upstream resolution by removing broken Docker DNS resolver
The resolver 127.0.0.11 directive caused ECONNREFUSED errors in this
network setup. Direct proxy_pass resolves server at startup which is
sufficient since depends_on ensures server is running first.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 11:57:02 -04:00
1f51504de9 Add story writer app with editor, auth, export, and polish features
- React + TipTap editor with formatting toolbar (bold, italic, underline,
  strikethrough, alignment, highlight, scene breaks)
- Custom image node view with resize and alignment controls; server-side
  WebP conversion via sharp
- Express + SQLite backend with JWT auth and admin user management
- Export to PDF, EPUB, and ODT
- Five themes (Midnight, Gothic Night, Enchanted Forest, Aged Manuscript,
  Neon Noir); Lora body font for readability
- Writing streak, daily word goal, milestones, and Ollama writing prompts
- Docker Compose setup for self-hosted deployment behind NPMplus

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 11:47:55 -04:00
8777e30d86 first commit 2026-05-11 11:45:44 -04:00