Commit graph

5 commits

Author SHA1 Message Date
mithral
3415102d5a Keep web/dist/.gitkeep alive across a build
The build deletes it. vite clears outDir (emptyOutDir: true), and that file is
the only reason //go:embed all:dist compiles in a fresh clone - without it a
clone that has not run a frontend build cannot run go build at all.

Found because it happened: a pnpm build during the transcode work removed the
tracked file, and it showed up as a deletion waiting to be committed. A
developer who ran a build and then staged everything would have broken every
clone silently, and the symptom would appear on someone else's machine.

The build now regenerates it, with the explanation in the file itself so the
next person to see it knows what it is for.
2026-10-12 12:12:02 -04:00
mithral
5281a6690d Play HLS in the web player with hls.js
The server could transcode and serve HLS; nothing could play it. Chrome has no
native HLS support, so without this the feature existed only for curl.

hls.js is the only real option for HLS in Chrome, and it is MIT. It is loaded
with a dynamic import, so the ~185 KB gzip is paid only when a transcode
actually happens - a direct play, which is the common case, fetches none of it.

Safari and iOS play HLS natively, so those get the playlist URL directly and
never load the library at all.

Three failure paths all fall back to the direct file URL rather than leaving a
dead player: hls.js not supported, the dynamic import failing, and a fatal
playback error. Transcoding is an optimisation and must never be able to break
playback - a file that would have direct-played has to keep working when the
encoder or the library is unavailable.

The instance is destroyed on source change and on unmount, so seeking between
files does not leave encoders or MediaSource objects behind.

Verified: pnpm check 0/0, the production build succeeds, and hls.js is emitted
as its own lazy chunk rather than folded into the main bundle.

Not verified in a browser - there is no automated browser test in this repo, so
the actual playback of a transcoded stream is unproven end to end. The server
side of that path is verified separately against real hardware.
2026-10-12 12:07:36 -04:00
mithral
e21661460a web: type process.env in vite.config (add @types/node)
The previous commit's vite.config.ts used process.env without Node types, so
svelte-check failed. Adds @types/node and includes "node" in tsconfig types.
svelte-check is back to 0 errors, 0 warnings.
2026-10-10 16:33:05 -04:00
mithral
5a377a2198 PDF reading via pdf.js
- PdfReader renders PDFs to canvas with page navigation and page-number
  progress, reusing the same integer progress model as comics.
- Reader routes by file container, not media kind, so a PDF in a comics
  library and a PDF ebook both open in the PDF reader.
- pdf.js is dynamically imported: the main bundle stays 443 kB and the 474 kB
  pdf chunk plus 1.26 MB worker load only when a PDF is actually opened.
- comics cover extractor now only handles zip archives. A PDF in a comics
  library is a valid item but not a zip, so it returns no cover instead of
  logging an error on every request.
- fixed the page scale to leave slack for the vertical scrollbar, which was
  otherwise forcing a horizontal scrollbar on tall pages.

Verified live: a 3-page PDF renders, navigates, and reports 33% progress.
2026-10-09 21:43:43 -04:00
mithral
5283e073f5 Web UI: Svelte app, embedded into the binary
The frontend the app needs to be usable at all.

- web/: Svelte 5 + Vite + TypeScript SPA. Login, app shell with per-type nav,
  library list/create/scan/delete, item grid with covers, admin user
  management, and an epub.js reader that syncs progress.
- Reader resumes by spine index — an integer — which is why the polymorphic
  progress table needs no text column.
- Session state uses classic stores in a plain .ts module rather than runes in
  a .svelte.ts module, avoiding special module resolution.
- Embedding: web/embed.go embeds web/dist. A committed .gitkeep keeps the
  directive valid before the frontend is built, so `go build ./...` never
  fails. Unknown non-API paths serve index.html for client routes; /api/* and
  /healthz always return JSON and never fall back to HTML.
- Dockerfile builds the UI in a node stage and overlays dist into the Go stage.
- pnpm 12 needs allowBuilds in pnpm-workspace.yaml; esbuild approved, the two
  unused transitive scripts explicitly denied so install exits 0.

svelte-check: 0 errors, 0 warnings. Verified served: SPA shell at /, route
fallback, hashed asset, and JSON 404 on /api/*.
2026-10-09 16:40:58 -04:00