scp overwrites in place, which fails with ETXTBSY ("text file busy") on
repeat deploys because systemd is executing /usr/local/bin/share-svc.
Copy to a temp path, chmod, then mv it over the destination so rename(2)
swaps the dir entry without touching the busy inode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
navigator.clipboard is only available in a secure context (HTTPS or
localhost), so the Copy URL button threw over plain HTTP. Fall back to
the deprecated document.execCommand("copy") when the Clipboard API is
unavailable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Storing the raw PEM as a secret mangled it (CRLF/whitespace/newline),
causing ssh to fail with "error in libcrypto" at the copy step. Store the
key base64-encoded and decode it in the workflow so the PEM round-trips
intact.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Build the frontend and the Go share-service, rsync/scp both to the Caddy
host, and restart share-svc + reload caddy. Triggers on push to master
(and manual dispatch). Requires a DEPLOY_SSH_KEY secret; host/user/paths
default to the Justfile values and are overridable via repo variables.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a hardened systemd unit, a Caddy reverse-proxy snippet that maps
/fabula/api/* to the loopback service, and Justfile build-server/
deploy-server recipes that build a static binary and ship + restart it.
Includes server/README documenting the API, config, and deploy steps.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a small Go + SQLite service (server/) that stores character-sheet
JSON blobs and returns short, content-addressed IDs, so sheets can be
shared via a compact ?s=<id> link instead of an oversized inline payload.
CharacterSheet.tsx now POSTs to the service on share and fetches by ID on
load, falling back to the self-contained ?c= inline link when the backend
is unreachable. Legacy ?c= links still decode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Use URL-safe base64 (base64url) for the ?c= payload so it no longer
needs percent-encoding, and prune empty string/array fields before
encoding. Decoding accepts standard base64, so existing links still load.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert the React app from JS/JSX to TS/TSX and add type-checking:
- Rename sheet-main, book, BookIndex, CharacterSheet to .ts(x) and
add types (Fields, Bond, ClassEntry, Spell, CheckMap; loose
SavedData for abbreviated save/share payloads)
- Add globals.d.ts for CSS imports and the __BOOK_DATA__ global
- tsconfig.json (strict, noEmit) and a 'typecheck' npm script
- webpack: handle ts/tsx via @babel/preset-typescript
- Enforce types with a tracked pre-commit hook (core.hooksPath),
wired up automatically via the 'prepare' script
- Update stale Justfile format target for the src/ layout
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Relocates book.js, fabula-ultima-sheet.css, and fabula-ultima-sheet.html
into src/ to consolidate all source files under one directory. Updates
webpack entry points, HTML template path, and CSS import accordingly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the SSR + injected vanilla-JS navigation script with a proper
client-side React app. BookIndex now uses useState/useEffect/useCallback
for all navigation state, scroll tracking, keyboard shortcuts, and history
management. webpack.config.js switches from renderToStaticMarkup to
embedding page data as window.__BOOK_DATA__ JSON; book.js becomes a
createRoot entry point. Also adds babel-loader for JSX bundling, fixes
#root display:contents so the flex height chain is preserved, and
restores missing CSS for header, .logo, .toolbar, and .tab buttons.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Port the Python HTML-generation script to a React component (src/BookIndex.jsx)
rendered at build time via renderToStaticMarkup, removing the need to run
build.py separately before webpack.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use getBoundingClientRect() in the scroll listener so the active-page
threshold is measured relative to the content container rather than
the document body, fixing off-by-one page detection.
- Replace history.replaceState with pushState for user-initiated
navigation (sidebar, prev/next, keyboard) and add a popstate listener
so the browser back/forward buttons work correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the iframe-based viewer with a self-contained file where each
page is a <section id="page-N">. Navigation (sidebar, prev/next, arrow
keys, URL hash) scrolls to sections instead of loading separate files.
Added build.py to regenerate index.html from the individual page files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add html/index.html: book viewer with auto-discovering sidebar,
prev/next navigation, keyboard shortcuts, and URL hash persistence
- Add html/book-page.css: shared stylesheet for all book pages derived
from fabula-ultima-sheet.css (dark theme, CSS variables, Cinzel/
Crimson Text fonts, common class styles)
- Add book.js entry point so webpack injects the shared CSS into the
book viewer; update webpack.config.js for two entry points, split
CSS chunk, CopyWebpackPlugin for book pages, and /book dev server
rewrite rule
- Add scripts/strip_watermark.py: removes "Guest Customer (Order
#52072168)" watermark artifacts from all 210 book pages
- Add scripts/restyle_book.py: strips per-page <style> blocks and
injects <link rel="stylesheet" href="book-page.css"> into all pages
- Update Justfile deploy to scp -r dist/* for the new /book subtree
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace verbose key names with short identifiers in collectData/applyData
(e.g. name→n, zenit→z, heroicSkills→hs). Nested bond, class, and spell
objects are mapped at the boundary so in-memory state and rendering code
are unchanged. applyData uses ?? fallbacks to remain compatible with data
saved under the old key names.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move Save, Load, Export, Import buttons to a dedicated Manage page
- Add light theme with warm parchment palette; auto-detect via prefers-color-scheme, persisted to localStorage
- Add URL sharing: sheet state is deflate-raw compressed, base64-encoded, and stored in ?c= parameter; auto-save is suppressed when viewing a shared URL
- Extract JS to fabula-ultima-sheet.js
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>