Locara

49 — Toolchain

The full set of build-and-test tools the Locara project uses. Per ADR 0013, we deliberately pick frontier-but-production-ready tools because build-loop speed matters disproportionately for the AI-author DX.

This document is the operational counterpart to the ADR: what specifically we install, configure, and pin.

TL;DR

JS/TS:        Bun (runtime + package mgr + bundler + tests)
Linting:      Biome (lint + format)
Styling:      Tailwind v4
Website:      Astro + Bun
Rust:         latest stable + mold linker + cargo-nextest + sccache
Monorepo:     Bun workspaces + Cargo workspace
Native:       Tauri 2.x

Pinned versions (.mise.toml or .tool-versions)

We use mise to pin tool versions. (Alternative: asdf, manually-installed tools; mise is preferred.)

# .mise.toml at repo root
[tools]
bun = "1.2"
rust = "1.85"
node = "22"          # for occasional Node-specific tooling
mold = "2.40"

[env]
RUST_BACKTRACE = "1"
SCCACHE_DIR = "~/.cache/sccache"

Cargo + bun pin themselves via lockfiles; mise pins the higher-level tools.

JavaScript / TypeScript layer

Bun (everywhere)

# Install dependencies
bun install

# Run a script
bun run dev
bun run build
bun run test

# Run a TS file directly (no compile step)
bun run src/main.ts

# Build for production
bun build src/main.ts --outdir dist

# Run tests
bun test

bun.lockb is the lockfile. Commit it. Do not use package-lock.json or pnpm-lock.yaml.

In package.json:

{
  "scripts": {
    "dev": "locara dev",
    "build": "locara build",
    "test": "bun test",
    "lint": "biome check .",
    "format": "biome format --write ."
  },
  "devDependencies": {
    "@biomejs/biome": "^1.9.0",
    "@types/bun": "latest"
  }
}

TypeScript

Bun handles TypeScript natively — no separate tsc step at runtime. Type-checking is still done via tsc --noEmit in CI:

bun run typecheck   # which calls: tsc --noEmit

tsconfig.json:

{
  "extends": "@locara/tsconfig/base.json",
  "compilerOptions": {
    "moduleResolution": "bundler",
    "module": "ESNext",
    "target": "ES2024",
    "strict": true,
    "noEmit": true,
    "types": ["bun"]
  }
}

Biome (lint + format)

biome.json:

{
  "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
  "formatter": {
    "enabled": true,
    "indentWidth": 2,
    "indentStyle": "space",
    "lineWidth": 100
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "correctness": { "noUnusedVariables": "error" },
      "style": { "useImportType": "warn" }
    }
  },
  "organizeImports": { "enabled": true }
}

Pre-commit hook (via Lefthook or a simple Husky-equivalent):

bun run lint
bun run format --write

CI fails if Biome flags issues.

Tailwind v4

Tailwind 4 uses CSS-based configuration:

/* src/styles.css */
@import "tailwindcss";

@theme {
  --color-primary: oklch(0.65 0.2 280);
  --font-sans: "Inter Variable", sans-serif;
}

No tailwind.config.js. Theming via CSS custom properties.

Tailwind 4’s Rust-based Oxide engine is the default in 2026.

Astro (website)

website/ uses Astro for the static locara.app site:

cd website/
bun install
bun run dev    # dev server
bun run build  # static build

Astro is configured to use Bun underneath.

Rust layer

Toolchain pinning (rust-toolchain.toml)

[toolchain]
channel = "stable"
components = ["rustfmt", "clippy"]
profile = "minimal"

We track latest stable; bump quarterly in lockstep with Rust releases. We don’t pin to a specific patch version — letting stable move forward keeps us getting compiler improvements.

Cargo configuration (.cargo/config.toml)

[target.aarch64-apple-darwin]
linker = "/opt/homebrew/bin/clang"
rustflags = ["-C", "link-arg=-fuse-ld=/opt/homebrew/bin/mold"]

[build]
rustc-wrapper = "sccache"

[profile.dev]
opt-level = 0
debug = "limited"  # smaller debug info; faster builds
incremental = true

[profile.dev.package."*"]
opt-level = 1  # mild optimization on dependencies; faster runtime in dev

[profile.release]
opt-level = 3
lto = "thin"
codegen-units = 1
strip = "symbols"

mold linker is required; CI installs it via Homebrew.

Tests via cargo-nextest

cargo install cargo-nextest --locked

# Run all tests
cargo nextest run

# Run a single test
cargo nextest run --test-name capability_check

# Filter by package
cargo nextest run -p locara-core

sccache for compilation cache

Configured via RUSTC_WRAPPER env var. Caches build artifacts; speeds up rebuild after cargo clean or new clones.

CI uses sccache with GHA cache backend:

- name: Setup sccache
  uses: mozilla-actions/sccache-action@v0.0.4
  env:
    SCCACHE_GHA_ENABLED: "true"
    RUSTC_WRAPPER: sccache

Tauri 2.x

Per ADR 0001. Tauri’s CLI is Bun-compatible:

bunx tauri dev
bunx tauri build

Pinned via Cargo.toml workspace dependencies + package.json devDependency.

CI configuration (GitHub Actions)

# .github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: macos-14   # Apple Silicon runner
    steps:
      - uses: actions/checkout@v4

      - name: Install mise
        uses: jdx/mise-action@v2

      - name: Setup sccache
        uses: mozilla-actions/sccache-action@v0.0.4

      - name: Install Bun deps
        run: bun install --frozen-lockfile

      - name: Lint + format check
        run: |
          bun run lint
          bun run format --check

      - name: Type-check
        run: bun run typecheck

      - name: Test (TS)
        run: bun test

      - name: Build Rust workspace
        run: cargo build --workspace --release
        env:
          RUSTC_WRAPPER: sccache

      - name: Test (Rust)
        run: cargo nextest run --workspace

CI is fast: typical PR runs in <2 minutes thanks to sccache + parallel test runners.

Performance targets (build loop)

These are the goals the toolchain choices enable:

OperationTarget
bun install (warm cache)< 5s
bun install (cold)< 30s
bun run typecheck< 5s
bun test (small package)< 1s
cargo build (warm, single crate)< 3s
cargo build --release workspace (CI, sccache hit)< 2 min
cargo nextest run (small crate)< 1s
locara dev cold start< 1.5s
locara dev HMR refresh< 200ms
Biome lint full repo< 2s

If any target slips by 2x in CI, we investigate as a regression.

Fallback strategies

If a frontier tool blocks development:

ToolIssue scenarioFallback
Bunruntime crash / Node-incompatPin previous Bun; report upstream; if persistent → switch to pnpm + Node temporarily
Bun bundlerbundler issueSwitch to Vite or Rolldown for affected package
Biomerule we need missingAdd custom rule; or temporarily ESLint for that rule
Tailwind 4engine bugPin Tailwind 3 temporarily
moldlink failureDefault ld linker (slower but works)
cargo-nextesttest infra issuecargo test (slower but works)
sccachecache corruptionClear cache; rerun

Switching back is documented as 1-day work. We don’t accept tools that would take a week to switch off — that’s a sign of risky lock-in.

Adding a new tool

When a contributor proposes a new tool:

  1. Justification. Why is this better than what we have?
  2. Compatibility. Does it play with the rest of the stack?
  3. Bus factor. Who maintains it; how stable is the project?
  4. Fallback. Can we exit cleanly if it doesn’t work?
  5. ADR. If it’s a meaningful addition, write an ADR.

Casual tool changes (a new test helper, etc.) don’t need this rigmarole. Toolchain-level changes do.

Onboarding (developer install)

A new developer’s first-time setup:

# Install mise
curl https://mise.run | sh

# In the repo root
mise install                # installs Bun, Rust, mold, etc.
bun install                 # installs JS deps
cargo build --workspace     # builds Rust crates
bun test                    # runs TS tests
cargo nextest run           # runs Rust tests
locara doctor               # confirms environment

5–10 minutes start to finish on a clean Mac.

OS support

Toolchain works on:

  • ✅ macOS (Apple Silicon — primary target)
  • ✅ macOS (Intel — for older Macs; lower priority)
  • ✅ Linux (x86_64 — for CI + curious devs)
  • 🟡 Linux (arm64 — partial; some tools)
  • 🟡 Windows (Bun + Rust work; mold doesn’t; would need lld) — phase 4+ if we go cross-platform

For app distribution this is moot — Locara apps are macOS-only in v1. The toolchain just needs to work on developer machines, which are mostly Mac.

Open questions

  • (open) Should we adopt rolldown when stable? Probably yes, if it ships before phase 1 ends.
  • (open) Use Bun’s native test runner exclusively, or also Vitest for projects that want React-component-test ergonomics? Probably Bun-native; revisit if Vitest’s component-testing story is materially better.
  • (open) For the website, Astro vs raw Bun-bundled HTML? Astro for content management ease; revisit if it adds friction.
  • (open) Should we ship a locara doctor check that validates toolchain version compatibility? Yes — already partially in 06-cli.md; expand to check all the frontier tools.

Cross-references