One build artifact serves both a self-hosted subpath deploy and GitHub
Pages. CI runs the tests, typechecks, builds, and smoke-tests the image.
server.ts's traversal guard uses a separator-aware check
(resolved === root || resolved.startsWith(root + path.sep)) instead of
a bare startsWith(root), which would let a request resolving to
"/app/dist-evil/x" pass a check against root "/app/dist".