Vellum/web
mithral 915f390156 Video player: load the file, don't let the browser skip it
Follow-up to 785d3ed. The committed version put the codec `type` on a <source>
child. That is the obvious thing to do and it is wrong: an explicit type lets
Chrome SKIP the resource during selection whenever canPlayType disagrees with
reality, and canPlayType is wrong in both directions.

The cost was measurable. mov+h264 has canPlayType('video/quicktime') === "no",
so the browser refused to try it — a file that PLAYS would have been reported as
unplayable. Verified now at 640x360, readyState 4.

Setting src on the element instead makes the browser actually load the file and
report videoWidth / error honestly, which is what playback.ts's measured notes
describe:
  mov+h264  -> loads, videoWidth 640   -> plays
  mp4+hevc  -> loads, videoWidth 0     -> detected by the decode check
  avi+mpeg4 -> error code 4            -> detected by the error path
The `type` is kept only as an ignored hint, and the load watchdog still covers
the case where the element says nothing at all. All three detection paths are
now reachable, which is the point: for HEVC, Chrome plays audio over a blank
frame with no error, and that must never be mistaken for working playback.

Process note, against the coordinator: I edited this file while its agent still
owned it, and the edit introduced the mov+h264 regression above. The agent
reverted it and kept the watchdog, which was the useful half. Do not edit a file
an agent owns mid-task; hand the finding over instead.
2026-10-11 14:20:15 -04:00
..
public Apply the brand: charcoal, blue-violet, purple, and the real logo 2026-10-10 17:41:49 -04:00
src Video player: load the file, don't let the browser skip it 2026-10-11 14:20:15 -04:00
.gitignore Web UI: Svelte app, embedded into the binary 2026-10-09 16:40:58 -04:00
embed.go Web UI: Svelte app, embedded into the binary 2026-10-09 16:40:58 -04:00
index.html Make the brand the only theme 2026-10-11 13:41:55 -04:00
package.json web: type process.env in vite.config (add @types/node) 2026-10-10 16:33:05 -04:00
pnpm-lock.yaml web: type process.env in vite.config (add @types/node) 2026-10-10 16:33:05 -04:00
pnpm-workspace.yaml Web UI: Svelte app, embedded into the binary 2026-10-09 16:40:58 -04:00
README.md Web UI: Svelte app, embedded into the binary 2026-10-09 16:40:58 -04:00
svelte.config.js Web UI: Svelte app, embedded into the binary 2026-10-09 16:40:58 -04:00
tsconfig.json web: type process.env in vite.config (add @types/node) 2026-10-10 16:33:05 -04:00
vite.config.ts dev: fix the Vite proxy and add a one-command dev runner 2026-10-10 16:32:32 -04:00

Vellum web

Svelte 5 + Vite + TypeScript single-page app. It is built to a static bundle in dist/ and embedded into the Go binary, so all asset paths are relative (base: './' in vite.config.ts).

Commands

pnpm install
pnpm dev      # dev server on :5173, proxying /api to :7788
pnpm build    # writes dist/
pnpm check    # svelte-check

Run the Go server on :7788 (make run from the repo root) and pnpm dev proxies API and health requests to it, so cookies and sessions behave exactly as they will in production.

Layout

src/
  main.ts                 entry point
  App.svelte              login gate -> shell
  app.css                 design tokens and base styles
  lib/
    api.ts                typed client; mirrors the Go handlers
    session.ts            auth + library stores
    nav.ts                view state
    components/
      Login.svelte
      Shell.svelte        nav + view switch
      Libraries.svelte    library list, create, scan, delete
      LibraryView.svelte  item grid with covers
      Reader.svelte       epub.js reader with progress sync
      Users.svelte        admin user management

The reader resumes from a spine index, which is why progress positions are integers: it round-trips through the polymorphic progress table without needing a text column.