DocsWorking with Rahti
Configuration & CLI
One JSON file for the project, one directory for static assets, three CSS engines, and a scaffolder whose upgrades leave your edits alone.
rahti.config.json
The project configuration and the scaffold ledger live in one file, validated by rahti.schema.json. All operational keys have defaults; keep the file valid JSON.
{
"$schema": "./rahti.schema.json",
"schema": 1,
"createdWith": "0.1.0",
"app": {
"dir": "src/app",
"public": "public"
},
"server": {
"host": "127.0.0.1",
"port": 3000
},
"css": {
"engine": "tailwind",
"entry": "src/app/globals.css",
"output": "public/css/styles.css"
},
"db": {
"backend": "sqlite",
"models": "src/models",
"migrations": "src/migrations"
},
"scaffold": {}
}| Key | Meaning |
|---|---|
| schema | The format version. A newer unsupported value stops the build. |
| createdWith | Scaffold provenance — not the active dependency version, which comes from Cargo.toml. |
| scaffold | Maps scaffold-owned files to hashes, which is what makes an upgrade safe. |
| db | Absent unless the project has a database. Carries no connection string: that is a credential, and this file is committed. |
server.host and server.port are where the server binds. In development a busy port is not fatal — the server increments until it finds a free one and prints the address it bound. A release build treats the same conflict as an error and stops, because a deployment's reverse proxy points at the configured port and moving off it silently would break the deployment without anything reporting it.
Static files
Contents of the configured public directory are served from the URL root.
public/css/styles.css -> /css/styles.css
public/js/main.js -> /js/main.js
public/favicon.ico -> /favicon.icoExplicit application routes win. Public files are the router fallback, and the root not-found.rs runs only when neither claims a path. RAHTI_PUBLIC_DIR overrides the configured directory at runtime.
Environment
| Variable | Read by | For |
|---|---|---|
| DATABASE_URL | src/db.rs | The connection string. See Database. |
| AUTH_SECRET | rahti::auth | Signing the session cookie. Generated per project. |
| AUTH_COOKIE_NAME | rahti::auth | The cookie's name. Generated per project. |
| SESSION_LIFETIME_HOURS | rahti::auth | Optional session lifetime. |
| RAHTI_PUBLIC_DIR | the runtime | Overrides the static directory. |
| RAHTI_DEV | the runtime | Forces development reload on or off. |
rahti::load_env() is the one .env reader for the process. It is idempotent and never overwrites a variable the environment already carries — a real environment variable always wins over the file. None of the auth values belong in rahti.config.json: the policy is code, and the secret is a credential.
PulsePoint assets
The application entry does three things, and none of them is a build step.
import "/js/pp-reactive-v2.min.js";
import { twMerge } from "/js/tailwind-merge.mjs";
const pp = globalThis.pp;
globalThis.twMerge = twMerge;
if (document.readyState !== "loading") {
pp?.mount?.();
} else {
document.addEventListener("DOMContentLoaded", () => pp?.mount?.(), { once: true });
}twMerge is then available to every mounted script as a global. Use it when a reactive class string can contain conflicting Tailwind utilities; it handles variants, !important, arbitrary values and overlapping utility groups.
CSS engines
| Engine | What the build does |
|---|---|
| tailwind | Compiles the entry with the pinned standalone Tailwind CLI. |
| plain | Copies the entry, and appends component-library styles. |
| none | Leaves the output untouched, for a stylesheet you manage yourself. |
The editable entry and the served output keep the same roles in all three modes, and the output is committed so builds remain usable without downloading a CLI. Tailwind settings: version pins the standalone release, download allows fetching a missing binary, minify controls output style, and sha256 maps platform keys such as windows-x64 to trusted hashes. A configured hash mismatch is fatal. Binary lookup covers .rahti/bin, RAHTI_TAILWIND, and tailwindcss on PATH.
The development runtime
Development reload is on in debug builds and off in release, unless RAHTI_DEV says otherwise. Rahti injects a dev client into page HTML, watches public files, swaps changed stylesheets, reloads for other changes, and forwards browser warnings and errors to .rahti/dev.log.
That covers everything read at runtime. A Rust edit needs a rebuild and a restart, which nothing inside the running process can do — so the scaffold writes a .cargo/config.toml whose dev alias hands that job to cargo-watch.
cargo run # regenerate routes and serve
cargo dev # cargo run, restarted on every edit (needs cargo-watch)
cargo check # verify without running
cargo test # this application's tests
cargo rahti upgrade # refresh unedited scaffold filespublic/ is deliberately not on the watch list — the running server handles it without a recompile. The .rahti directory is gitignored by both the repository and the new-project scaffold: generated manifests, CSS scratch files, and the development log. None of it is application input.
The scaffold CLI
cargo rahti new <name> [--tailwind] [--db [backend]] [--ws] [--local <path>]
cargo rahti upgrade [--dry-run] [--db [backend]] [--ws]Every feature flag adds something, and leaving it out is how you say no. One spelling per answer, so there are no opposing flags to reconcile. An interactive run asks about whatever was not named, defaulting to no; a run with nowhere to ask takes the flags at their word.
Upgrades that leave your work alone
upgraderewrites or removes only files whose current hash still matches the scaffold ledger. Edited files are preserved and reported.- It can add a database or WebSockets to a project that started without them. The files are written and the choice is recorded in the config.
- It never regenerates
Cargo.tomlor.envfrom a template — that would undo your dependencies, a path dependency, or a real credential — but it does amend them by the smallest edit that makes the files it just wrote compile and run. - The amendments are driven by
rahti.config.jsonrather than by what the run added, so a project that gained a feature by editing its config is repaired by runningupgradeagain. Every edit is skipped when it is already there, so running it any number of times is the same as running it once. - In the config file,
upgraderewrites only what it owns:createdWithand thescaffoldledger. The port, the app directory, and every CSS setting stay exactly as you wrote them. - A shape it cannot read — a
[dependencies.rahti]table, a dependency spread over several lines — is reported with the line to add rather than guessed at.
