# Development

How to run a local Stalwart test server and develop this WebUI against it.
This file is meant to be read by humans and AI coding agents alike — see
[AGENTS.md](AGENTS.md) for the rules that also apply while doing this.

## Prerequisites

- Node.js 18+
- Docker (with the `docker compose` CLI plugin)

## 1. Start a local test server

```bash
npm run dev:server
```

This runs `docker compose up -d`, which starts a disposable Stalwart
instance ([`docker-compose.yml`](docker-compose.yml)) with:

- The management/JMAP HTTP API on `http://localhost:8080` (the only port
  the WebUI dev proxy needs — see `server.proxy` in
  [`vite.config.ts`](vite.config.ts)).
- Mail protocol ports (SMTP/IMAP/POP3/ManageSieve) exposed too, only
  needed if you're testing actual mail flows, not just admin UI screens.
- A fixed admin account baked in via `STALWART_RECOVERY_ADMIN`:
  `admin@example.org` / `c8321iEscHDy0GWV`. **Disposable dev credentials
  only — never reuse them for anything real.**
- Named Docker volumes (`stalwart-etc`, `stalwart-data`) so data survives
  a restart. Data persists until you tear the volumes down.

Useful companions:

```bash
npm run dev:server:logs   # tail the container's logs
npm run dev:server:down   # stop it (add `-v` via `docker compose down -v` to also wipe data)
```

The server takes a couple of seconds to come up; `docker compose logs stalwart`
will show `Network listener started ... localPort = 8080` once it's ready.

## 2. Initialize the server (first time only)

The container starts in Stalwart's bootstrap mode, which only allows
signing in as the break-glass `STALWART_RECOVERY_ADMIN` account — real
accounts and most settings aren't usable yet. Run once per fresh volume:

```bash
# Windows / PowerShell
pwsh ./scripts/dev-server-init.ps1

# Linux / macOS / any POSIX shell (including most AI agent sandboxes)
bash ./scripts/dev-server-init.sh
```

This completes the bootstrap wizard (default domain `example.org`, no TLS
certificate request — safe for local/offline use), creates a real
`devadmin@example.org` admin account, sets the server's default OAuth
access token lifetime to 3 hours, and points the `x:Application` record
serving `/admin`/`/account` at this fork's description and release URL
(cosmetic — otherwise Settings > Web Applications' "Active WebUI" card
shows Stalwart's default "Stalwart Web Interface" / upstream release URL,
even though what's actually running is this fork served by Vite). It's
idempotent — safe to re-run, it no-ops once the server is already
bootstrapped. You only need to re-run it after `docker compose down -v`
(which wipes the volumes).

## 3. Seed sample data (optional)

```bash
# Windows / PowerShell
pwsh ./scripts/dev-seed.ps1

# Linux / macOS / any POSIX shell (including most AI agent sandboxes)
bash ./scripts/dev-seed.sh
```

Populates Users, Groups, Mailing Lists, and Roles with sample data so
every admin panel screen has something to test against:

- **Users**: `alice@example.org` / `AlicePass123!` (User role, in
  engineering, 1 alias), `bob@example.org` / `BobPass123!` (User role, in
  engineering + marketing), `carol@example.org` / `CarolPass123!` (Admin
  role, in marketing) — alongside the `devadmin`/`admin` accounts from
  step 2.
- **Groups**: `engineering`, `marketing`.
- **Mailing lists**: `newsletter@example.org`, `support@example.org`.
- **Roles**: `Support Agent`, `Read-only Auditor` (custom, alongside the
  built-in System Administrator/Tenant Administrator/Group/User roles).

Idempotent — does nothing if `alice` already exists. Only needed once per
fresh volume, same as step 2.

## 4. Get an access token

For local development it's simpler to skip interactive login and use a
bearer token directly via `VITE_ACCESS_TOKEN` (see `.env.development`).

```bash
# Windows / PowerShell
pwsh ./scripts/dev-token.ps1              # 3 hour token (server default)
pwsh ./scripts/dev-token.ps1 -DurationSeconds 1800   # custom duration (30 min)

# Linux / macOS / any POSIX shell (including most AI agent sandboxes)
bash ./scripts/dev-token.sh               # 3 hour token
bash ./scripts/dev-token.sh 1800          # custom duration (30 min)
```

Both scripts authenticate as the `devadmin` account created in step 2 and
create a Stalwart API key with the requested expiry (default 3 hours,
overridable per invocation — this is a genuine per-request duration, not
a global setting), then write its secret to `.env.development.local`
(gitignored, never committed) as `VITE_ACCESS_TOKEN`. Re-run the script
and restart `npm run dev` once the token expires (the UI starts returning
401s).

The `STALWART_RECOVERY_ADMIN` account is intentionally not used here: it's
a break-glass credential and its tokens always expire in a fixed 1 hour
regardless of server configuration, so it can't honor a custom duration.

## 5. Run the WebUI

```bash
npm install   # first time only
npm run dev
```

Open `http://localhost:5173`. You should land directly in the admin panel
(no login screen) since `VITE_ACCESS_TOKEN` is set.

## 6. Verify your change

```bash
npm run typecheck
npm run lint
npm test
npm run build
```

For UI changes, actually look at the running app (browser or a browser
automation tool) — passing typecheck/lint/tests proves the code compiles
and existing behavior didn't regress, it doesn't prove the new UI works.

## Resetting the test server

To start from a completely clean server (e.g. to re-test first-run
behavior):

```bash
npm run dev:server:down
docker compose down -v   # also removes the stalwart-etc/stalwart-data volumes
npm run dev:server
bash ./scripts/dev-server-init.sh   # re-run: fresh volume needs bootstrapping again
bash ./scripts/dev-seed.sh          # optional: re-seed sample data too
```

## Troubleshooting

- **UI shows a login screen instead of the admin panel, or the page fails
  to load data**: `npm run dev` only reads `.env.development.local` at
  startup. If you regenerate a token, or restart the Docker container,
  while `npm run dev` is already running, stop it (Ctrl+C) and start it
  again — it won't pick up the new token or reconnect on its own.
- **`Failed to fetch` / connection refused in the browser console**: the
  test server isn't running or isn't ready yet. Check with
  `docker ps --filter name=stalwart-webui-dev` and
  `npm run dev:server:logs`; wait for `Network listener started ...
  localPort = 8080` before retrying.
- **401s after everything was working**: your token expired. Re-run
  `scripts/dev-token.sh` (or `.ps1`) and restart `npm run dev`.
- **`scripts/dev-server-init.sh` fails with connection errors**: the
  container needs a few seconds after `npm run dev:server` before it
  accepts requests — the script retries for ~30s, but if your machine is
  slow, just re-run it.

## Notes for AI agents

- This whole workflow (steps 1–5) is scriptable end-to-end without a
  browser: `npm run dev:server`, then `bash scripts/dev-server-init.sh`
  and `bash scripts/dev-seed.sh` (first time only), then
  `bash scripts/dev-token.sh`, then the app is reachable at
  `http://localhost:5173` with `VITE_ACCESS_TOKEN` already set. Verify
  backend connectivity directly with `curl`, e.g.
  `curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/jmap/session`.
- Read [AGENTS.md](AGENTS.md) before touching anything under `src/` —
  the schema-fidelity rule applies to all development, local test server
  or not.
- Don't commit `.env.development.local` (it holds a live token) or leave
  the dev container running unexpectedly — `npm run dev:server:down` when
  you're done.
