Build & release

The promise is one binary. This page is how the promise is kept — from npm run build through cargo build --release to a signed GitHub release — and what to run locally when you want to check the chain yourself.

The pipeline at a glance

The frontend must exist on disk before Rust compiles — rust-embed reads frontend/dist/ at build time. If you skip step 2, the binary ships an empty UI.

Local builds

The Justfile in the repo root wraps the common flows. From a clean clone:

RecipeWhat it does
just devBoots cargo run and the Vite dev server side by side. Frontend hot-reloads; the backend proxies /api/*. Best for day-to-day work.
just dev-mockVite only, with VITE_MOCK=true. No database, no Rust — the UI runs against in-memory fixtures. Best for component work and styling passes.
just buildBuilds the frontend with VITE_MOCK=false, then cargo build --release. Produces the shippable binary at target/release/seeki.
just test-e2eRuns the Playwright suite against a freshly built release binary. Applies tests/fixtures/seed.sql unless SEEKI_SKIP_SEED=1.

How the UI ends up inside the binary

The embedding is declared in src/embed.rs:

#[derive(RustEmbed)]
#[folder = "frontend/dist/"]
struct Assets;

At cargo build time, rust-embed walks frontend/dist/, reads every file, and embeds the bytes into the compiled binary’s data segment. At runtime, the fallback handler in src/main.rs looks up request paths against that embedded set:

let app = Router::new()
    .nest("/api", api::router(mode.clone()))
    .layer(localhost_cors())
    .fallback(embed::handler);

Two consequences worth remembering:

  • Frontend changes require a full rebuild. Editing a Svelte file without running npm run build changes nothing inside the release binary.
  • Asset caching is fingerprinted. Files under assets/ are served with Cache-Control: public, max-age=31536000, immutable; everything else (index.html) is no-cache. The handler lives in src/embed.rs.

Testing the build chain

To confirm a production build works end-to-end without releasing:

just build
./target/release/seeki --help
# with a config in place:
./target/release/seeki

If the / route returns an empty page or 404, frontend/dist/ was empty when cargo ran. Rebuild the frontend and run just build again.

Release workflow

The release pipeline is GitHub Actions. The workflow file is .github/workflows/release.yml. Trigger and shape, in order:

  1. Trigger. A pull request into the release branch is merged. Non-merge events — closed without merging, drafts — are ignored.
  2. Read version. The workflow reads the single-line VERSION file at the repo root. The expected format is YY.MAJOR.MINOR.PATCH[suffix] (for example 26.1.0.0 or 26.0.2.0rc). The regex is enforced in the workflow.
  3. Sync with main. If main’s VERSION is newer than the one on release, the workflow rewrites VERSION (and package.json if present) to match.
  4. Build. Frontend first (npm ci && npm run build), then cargo build --release on the CI runner.
  5. Tag and publish. The workflow creates a git tag named after the version and publishes a GitHub release containing the compiled binary.

Versioning

SeeKi uses a year-anchored scheme:

YY . MAJOR . MINOR . PATCH [ suffix ]
  • YY — two-digit calendar year (26 for 2026).
  • MAJOR — incompatible change in config, CLI, or API shape.
  • MINOR — additive change: a new engine, a new field, a new page.
  • PATCH — bug fixes, copy, docs, dependency bumps.
  • suffix — optional, alphabetic (rc, beta). Absent for stable releases.

The CI workflow parses these four digits out of VERSION and exposes them as job outputs, so downstream steps can branch on the major/minor number without re-parsing.

CI on pull requests

.github/workflows/ci.yml runs on every pull request. It checks out the code, installs Node and Rust, builds the frontend and backend, runs the unit tests, and runs Clippy. It does not publish artifacts. Treat its green check as the minimum gate for any PR.

Troubleshooting the build

SymptomLikely causeFix
Release binary serves 404 on /.frontend/dist/ was empty when cargo ran.cd frontend && npm run build, then cargo build --release.
Build fails on Rust edition.Toolchain older than 1.85.rustup update stable.
UI shows stale assets after a rebuild.Browser cached index.html.Hard-reload. Fingerprinted assets are safe; only index.html is cacheable, and it is served no-cache.
Release workflow never triggers.The PR was merged into main, not release.Open a PR from main into release and merge that.
Version regex mismatch.VERSION missing the YY. prefix, or a stray newline.Rewrite the file as a single line matching YY.M.M.P[suffix].

Where to go from here

  • Architecture — what is inside the binary this page ships.
  • Frontend map — the source tree that vite build consumes.
  • Changelog — the releases produced by this workflow.