Browse all blog posts in chronological order.

Explore Specific Feeds

Selected Work & Writing

Interactive systems, tools, and experiments.

PDF, ready to download.

Want to see or tweak how this one was built?

Form flow (this submission)

Need the raw structured data (for bots/tools)?

JSON projection

Want your own version?

Build your own resume with this form

PDF, ready to download.

Want to see or tweak how this one was built?

JSON version (structured parsing)

Need the raw structured data (for bots/tools)?

Form flow (how this resume is populated)

Want your own version?

Build your own CV with this form

Please complete the payment:

Loading MDX...
Loading MDX...

Built a local-first Typst publishing platform where your source files stay on disk, compilation runs in a containerized service, and auth is enforced server-side.

The flow is simple on the surface and hard underneath: open a local folder in Chrome, bundle files, send to a compiler API, and preview the generated PDF instantly. Under the hood, this required solving CORS edges, Docker networking (localhost vs host.docker.internal), OAuth redirect behavior, JWT verification, and Terraform/GitHub OIDC deployment plumbing.

The key design decision was trust boundaries. The frontend handles UX (folder access, login redirects, preview), while the compiler enforces security (token validation, issuer/audience/scope checks). We intentionally moved auth policy out of the browser and into server config, then used discovery to reduce duplicated settings.

Deployment is fully infrastructure-as-code with Terraform: Cloud Run services for viewer and compiler, Artifact Registry, and GitHub Actions federation via Workload Identity (no long-lived keys). First bootstrap was painful; after that, pushes to main are the release mechanism.

This project is now a practical blueprint for local-first docs workflows: author locally, compile remotely, keep security centralized, and automate the platform lifecycle end to end.

Because of browser restrictions, this doesn't work on an IFrame like the one shown bellow, so instead try it here:

Scroll for more