Skip to content

ADR 0004: Template App Pattern for New Applications

Status: Accepted Date: 2026-04-29

Context

SCALR needs to be able to create new apps quickly and reliably, including via AI agents triggered by non-technical users. The two failure modes to avoid are:

  1. Skeletons that don't run. Many template generators produce code that requires manual wiring before it boots. Agents stumble in that gap.
  2. Drift between docs and reality. If "how to create an app" lives only in docs, the docs go stale and agents follow outdated steps.

Decision

template-app/ is a fully working, runnable application. Not a skeleton, not a code-gen template — a real app that says "Hello, {user.name}" after you log in. cd template-app && docker compose up and it works.

New apps are created by copying the template via a single deterministic script, infra/scripts/create-app-from-template.sh. The script:

  1. Validates inputs (slug, kind, uniqueness).
  2. Copies template-app/ to the right location under apps/<kind>s/<slug>/.
  3. Replaces placeholders ({{APP_NAME}}, {{APP_SLUG}}, etc.).
  4. Allocates ports, generates DB names and entitlement keys.
  5. Updates the root docker-compose.yml to include the new app.
  6. Boots the new app.
  7. Runs the new app's smoke test.
  8. Reports success or surfaces the failure.

Both humans (make new-app) and agents (via shell tool) call the same script.

Consequences

Positive: - The template is itself tested in CI: every push runs the scaffold script with test inputs and boots the result. If the template breaks, no new apps can be reliably created — so this CI job is treated as a release blocker. - One mechanism to maintain. Humans and agents share the same path. - The script does the mechanical work; agents focus on creative work (interpreting the user's intent into models and UI). This separation makes agents far more reliable. - Agents can't forget a step, can't typo a placeholder, can't allocate a duplicate port — the script does that.

Negative: - The template carries the cost of being kept current. If we change how apps wire into compose, the template (and the script) must be updated too. - A "demo" app in the repo has resource cost — its compose definition runs alongside everything else. Mitigated by keeping it minimal and not including it in the root compose by default.

Why one template, not many

See 0002-locked-stack.md. With one stack, one template is enough. If we ever need multiple stacks, we add template-app-<stack>/ rather than parameterizing the existing one.