Templates
Embedded scaffolding used by the CLI to create projects and generate files. Keeps new apps consistent and zero-config.
Overview
- Repo source of truth lives under
orchestrators/bun/resources/templates/**. - Generated package assets live under
orchestrators/bun/assets/templates/**and are embedded into the Bun CLI package. webstir initlays down a server-firstfullproject by default.- Generators add files in the right place with sensible defaults.
Layout
Created by webstir init according to workspace mode:
full: frontend, backend, shared, and typesspa: frontend, shared, and typesssg: frontend and typesapi: backend, shared, and types
Typical frontend scaffold:
src/frontend/app/app.htmlsrc/frontend/app/**src/frontend/pages/<page>/index.html|cssplusindex.tsfor standard pagessrc/frontend/{content,images,fonts,media}/**
Typical backend scaffold:
src/backend/index.tssrc/backend/module.tssrc/backend/jobs/**src/backend/tests/**
Conventions
- Base HTML requires a
<main>insrc/frontend/app/app.htmlfor page merge. - Page folder names must be one non-empty path segment without separators,
./.., NUL bytes, or platform-reserved names and characters. - Each page has
index.htmlandindex.css; standard pages also haveindex.ts, while SSG page scaffolds omit it by default. - Backend entry is
src/backend/index.ts. - Fresh
apiandfullscaffolds keepsrc/backend/index.tsthin and use it to boot the package-managed Bun runtime. - Manifest-backed route and demo logic lives in
src/backend/module.ts. - The default app primitives are documented in Primitives; treat that page as the naming contract for pages, forms, actions, fragment targets, request-time views, and auth-gated routes.
- For optional app features, prefer absolute app-asset imports such as
await import('/app/router.js')so dev and publish paths stay aligned. - Start with
fullwhen the app needs forms, redirects, auth, or server-rendered documents; opt intospaorssgonly when you need those delivery modes specifically.
TypeScript
- Uses an embedded
base.tsconfig.jsonreferenced by template tsconfigs. - ESM-only; compiled via the active provider packages.
- Shared code in
src/sharedis compiled for both frontend and backend. - Dev output keeps source maps for local debugging; publish strips them.
- Dynamic imports load at runtime. Keep
/app/...imports absolute for assets undersrc/frontend/app/.
CSS & Assets
- Plain CSS by default; optional CSS Modules in publish.
@importand asset URLs are resolved; files copied to outputs.- Place static app assets under
src/frontend/app/*. - Place Images, Fonts, and Media under
src/frontend/{images|fonts|media}/**.
Inline Scripts
- A script tag marked
data-webstir-inlinenames a TypeScript or JavaScript source that the build bundles and writes into the tag itself, so it runs before anything paints. Use it for the small things that must be right on the first frame, such as a theme or a computed backdrop:<script data-webstir-inline src="./scripts/first-paint.ts"></script>insrc/frontend/app/app.htmlruns on every page; the same tag in a page'sindex.htmlruns on that page.- A relative
srcresolves against the file that contains the tag; a leading/resolves againstsrc/frontend, the way/app/app.jsdoes.
- The bundle is an immediately-invoked script with its imports included, readable in development and minified on publish.
webstir watchrebuilds the page HTML when the source or anything it imports undersrc/frontend/appor the page changes. - The build keeps the source path in the attribute (
data-webstir-inline="src/frontend/app/scripts/first-paint.ts") and dropssrc; client-side navigation leaves inline head scripts in place, so they run on full loads only. - A missing source fails the build. A published (minified) bundle over 16 KB gets a
frontend.inlineScript.largewarning, because it travels with every page that includes it; the readable build output is not measured.
Client Error Reporting
- The SPA and full templates install a lightweight client error reporter:
src/frontend/app/app.tslistens forwindowerrorandunhandledrejection, loadssrc/frontend/app/error.tson the first one, and reports toPOST /client-errorsusingsendBeacon(fallback tofetch). - The SSG template does not include it: a static site has no server to report to.
- Behavior:
- Throttled: max 1 event/second; capped at 20 per page session.
- Deduped: repeats suppressed within 60s using a fingerprint of type|message|file:line:col|stack-hash.
- Correlation: includes a client correlation id; the server also accepts
X-Correlation-ID.
- Where reports go:
webstir watchprints each report in the terminal next to the build output; the Bun backend runtime logs it at error level, so full and API workspaces have a sink in production. - Opt-out: delete
src/frontend/app/error.tsand remove theloadErrorHandlersection fromsrc/frontend/app/app.ts.
Generators
add-page
- Command:
webstir add-page <name> --workspace <path> - Calls the canonical
@webstir-io/webstir-frontendhelper to scaffoldindex.html|cssplusindex.tsfor standard pages. - Does not modify existing pages or
app.html. - Name validation: rejects control characters, trims surrounding spacing, preserves case and internal spaces, and requires one portable non-empty path segment (not
.or.., a platform-reserved name, or a name containing reserved characters).
add-test
- Command:
webstir add-test <name-or-path> --workspace <path> - Uses the canonical
@webstir-io/webstir-testinghelper to create<name>.test.tsunder the nearesttests/directory; older published installs fall back towebstir-testing-add. - Validates every path segment for portable filenames before creating directories or files.
- Works for both frontend and backend tests.
Backend Template
- Thin Bun bootstrap at
src/backend/index.ts. - Manifest-backed route and demo logic at
src/backend/module.ts. - Exposes health endpoints (
GET /api/health+/healthz) and a readiness probe (/readyz) that returns the manifest summary. - Reads
PORTenv var; defaults handled by the CLI dev server proxy in dev. - Optional auth adapter: set
AUTH_JWT_SECRET,AUTH_JWT_PUBLIC_KEY,AUTH_JWT_PUBLIC_KEY_FILE, orAUTH_JWKS_URL(plusAUTH_JWT_ISSUER/AUTH_JWT_AUDIENCEandAUTH_SERVICE_TOKENSwhen needed) to enable bearer-token verification and populatectx.authin module routes. Unsupported algorithms, malformed compact JWT segments, bad signatures, wrong issuer/audience, invalid numeric-date claims, and invalidnbf/expwindows fail closed. - Session and form safety: stale or tampered session cookies clear on commit, CSRF tokens are single-use after successful verification, and malformed SQLite session rows fail with an explicit session-row diagnostic.
- Observability: install
pino, setLOG_LEVEL/LOG_SERVICE_NAME, and enable metrics viaMETRICS_ENABLED. Every request logs structured JSON and/metricsexposes rolling latency/error stats. - Database & migrations: set
DATABASE_URL(defaults to SQLite in./data/dev.sqlite) and manage schema changes viasrc/backend/db/migrate.ts+src/backend/db/migrations/*.ts. The scaffolded helper usesBun.SQLfor both SQLite (file:./data/dev.sqlite,sqlite:./data/dev.sqlite,:memory:) and Postgres (postgres://...), so the same Bun-native client works across both paths without an extrapginstall. The migration runner supports--list,--status,--down, and--steps, validatesDATABASE_MIGRATIONS_TABLE, rejects duplicate migration ids, and runs each migration plus history update in a transaction. - Jobs & scheduling:
src/backend/jobs/**plusbuild/backend/jobs/scheduler.jssupport one-off runs, manifest export, and local watch-mode execution. On Bun1.3.11+, the built-in scheduler usesBun.cron.parse(...)for real cron expressions and nicknames while still preservingrate(...)and@rebootschedules for local development loops. Local watch mode skips overlapping runs for the same job and disposes scheduled timers onSIGINT/SIGTERM.
Publish Outputs
- Per page:
dist/frontend/pages/<page>/index.html - Fingerprinted assets:
dist/frontend/pages/<page>/index.<timestamp>.{css|js} - Per-page
manifest.jsonlisting hashed asset names. - App assets copied to
dist/frontend/app/*.
Customizing Templates
- Edit templates under
orchestrators/bun/resources/templates/. - Treat
orchestrators/bun/resources/features/client_nav/**as the canonical source for the built-inclient-navfiles projected into thefulltemplate. - Regenerate the shipped package assets with
bun run --filter @webstir-io/webstir buildorcd orchestrators/bun && bun scripts/sync-assets.mjs. - Use
bun run --filter @webstir-io/webstir check:assetsto verify the generated tree is still in sync. - Use
bun run --filter @webstir-io/webstir check:feature-projectionsto verify the exactclient-navtemplate projections still match their shared feature sources. - Keep conventions intact (page structure, base HTML
<main>, server entry path). - After changes, rebuild the CLI to embed updated templates.
Related Docs
- Solution overview — solution
- Primitives — primitives
- CLI reference — cli
- Engine internals — engine
- Pipelines — pipelines
- Workspace and paths — workspace
The full template includes /lifecycle, demonstrating the optional page setup
export and cleanup scopes. After changing canonical client-nav feature sources,
run bun orchestrators/bun/scripts/sync-client-nav.mjs to refresh the full template
and first-party consumers, then bun orchestrators/bun/scripts/sync-assets.mjs
to regenerate packaged assets.