- ⚙ button opens a settings popover above the toolbar with:
- Voice dropdown: lists all voices the browser exposes, with ☁
suffix for remote/cloud voices vs local ones
- Speed slider: 0.5x–2.0x in 0.1 steps, labelled live
- On first load, auto-selects the first English non-eSpeak voice
(helps Linux/Firefox users who only have eSpeak installed by default
and haven't yet set a preference)
- Voice URI and rate persist in localStorage (sw-tts-voice, sw-tts-rate)
- Voice and rate applied per-utterance via voiceURIRef / ttsRateRef
so changes take effect on the next sentence without restarting
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- 🔊 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>
- 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>
- 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>
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>
- 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>
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>