---
name: Frontend build config
type: architecture-map-page
slice: 05-core-storage-hotkey-build
last_verified: 2026/05/09
---

# Frontend build config

> **Where you are:** [Architecture map](../README.md) → [Core, Storage, Hotkey, Build](README.md) → Frontend build config

**Plain English summary.** Three small config files that pin how Vite, SvelteKit, and TypeScript-flavoured-jsconfig behave. The settings are deliberately minimal — the heavy lifting (routes, components, stores) lives in slice 1.

## At a glance

- Files: `vite.config.js` (612 bytes), `svelte.config.js` (214 bytes), `jsconfig.json` (366 bytes).
- External: Vite, SvelteKit, Tailwind v4 plugin.
- Consumers: every dev / build invocation.

## `vite.config.js`

```js
import { defineConfig } from "vite";
import { sveltekit } from "@sveltejs/kit/vite";
import tailwindcss from "@tailwindcss/vite";

const host = process.env.TAURI_DEV_HOST;

export default defineConfig(async () => ({
  plugins: [sveltekit(), tailwindcss()],
  clearScreen: false,
  server: {
    port: 1420,
    strictPort: true,
    host: host || false,
    hmr: host
      ? { protocol: "ws", host, port: 1421 }
      : undefined,
    watch: {
      ignored: ["**/src-tauri/**"],
    },
  },
}));
```

### Notable settings

- **`port: 1420 + strictPort: true`.** Tauri's `beforeDevCommand` and the `run.sh` poll both target port 1420. `strictPort` means Vite errors out instead of falling back to 1421+, so a stuck process is loud.
- **`clearScreen: false`.** Vite's default behaviour clears the terminal. Disabled here so the Tauri logs stay visible alongside Vite's.
- **`TAURI_DEV_HOST` env var.** When set, Vite binds to the network interface and runs HMR on port 1421. Used for testing on a real device while developing on the desktop. Unset on local dev.
- **`watch: { ignored: ["**/src-tauri/**"] }`.** Vite's file watcher ignores the Tauri directory; otherwise every Cargo build artefact change would trigger an HMR pass.

### Why Tailwind v4's Vite plugin

Tailwind 4 ships a `@tailwindcss/vite` plugin that replaces the v3 PostCSS pipeline. The plugin reads CSS-in-JS / `@import "tailwindcss"` directly from the source files. The repo has no `tailwind.config.js` or `postcss.config.cjs` because v4 derives configuration from the CSS itself.

## `svelte.config.js`

```js
import adapter from "@sveltejs/adapter-static";

const config = {
  kit: {
    adapter: adapter({
      fallback: "index.html",
    }),
  },
};
export default config;
```

### Notable settings

- **`adapter-static`** — Lumotia is a Tauri app, not a server-rendered web app. Static adapter outputs a fully pre-rendered HTML/JS bundle that Tauri serves from its embedded webview.
- **`fallback: "index.html"`** — every unknown route serves `index.html`, which lets the SvelteKit client router take over. Without this, `/history` typed directly into the URL bar would 404.

## `jsconfig.json`

```json
{
  "extends": "./.svelte-kit/tsconfig.json",
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "sourceMap": true,
    "strict": true,
    "moduleResolution": "bundler"
  }
}
```

### Notable settings

- **`extends ".svelte-kit/tsconfig.json"`** — SvelteKit generates a tsconfig at `npm run dev:frontend`'s `svelte-kit sync` step. We extend it to add our own strictness flags.
- **`allowJs: true + checkJs: true`** — the codebase is JavaScript with TypeScript-aware type checking via JSDoc. svelte-check enforces this.
- **`strict: true`** — full strict mode. Null safety, no implicit any, etc. svelte-check is the gate (CI runs `npm run check`).
- **`moduleResolution: "bundler"`** — TypeScript 5's bundler-aware resolution. Matches Vite's behaviour (no fake CommonJS round-trip).

## `static/`

The static folder maps 1:1 to the served root. Listed here for completeness:

- `favicon.png` — app icon (web).
- `pcm-processor.js` — the audio worklet (cross-link: [`static-assets.md`](static-assets.md)).
- `svelte.svg`, `tauri.svg`, `vite.svg` — placeholder logos.
- `fonts/` — `atkinson-hyperlegible-next.woff2`, `instrument-serif-italic.woff2`, `jetbrains-mono.woff2`, `lexend-variable.woff2`, `opendyslexic.woff2`.
- `textures/` — `grain.png`.

## Watch-outs

- **`vite.config.js` is async-returning a config but does no awaiting.** Worth the simplification to a synchronous `defineConfig({ ... })` if no async setup is added.
- **`svelte.config.js` does not use `vitePreprocess`.** SvelteKit 2 + Svelte 5 do not require it (Svelte 5's compiler reads JSDoc directly). Keeps the config minimal.
- **`jsconfig.json` extends a generated file.** Running `svelte-kit sync` is part of `npm run check` and `npm run dev:frontend`. CI runs it explicitly.
- **No `vitest` config.** Frontend unit tests are not part of the current workflow. Coverage is via `svelte-check` (types), e2e dogfooding, and Rust integration tests at the slice-2 boundary.

## See also

- [Dev launcher and scripts](dev-launcher-and-scripts.md)
- [Static assets](static-assets.md)
- [Slice 1 frontend](../01-frontend/README.md) — the routes / components this config builds.
