diff --git a/docs/superpowers/specs/2026-06-01-settings-page-design.md b/docs/superpowers/specs/2026-06-01-settings-page-design.md
new file mode 100644
index 0000000..ebf0474
--- /dev/null
+++ b/docs/superpowers/specs/2026-06-01-settings-page-design.md
@@ -0,0 +1,203 @@
+# Settings file + settings page — design
+
+**Date:** 2026-06-01
+**Status:** Approved (brainstorming), pending implementation plan
+
+## Problem
+
+The three configurable inputs — AI backend, Marvin tasks command, and knowledge
+profile path — are read once from `ANTIDRIFT_*` environment variables at daemon
+startup (`cmd/antidriftd/main.go`). Changing any of them means editing a launch
+command and restarting. The user wants to edit them at runtime from the web UI,
+backed by a persisted settings file, and to pick the knowledge profile with a
+proper file picker rather than typing an absolute path.
+
+A partial precedent already exists: `POST /knowledge/path` → `SetKnowledgePath`
+re-points the profile live while planning, but the choice is session-only and
+not persisted.
+
+## Decisions (locked during brainstorming)
+
+1. **Live apply** — saving a setting re-wires the running daemon immediately; no
+ restart. (Knowledge path already works this way.)
+2. **Server-side browse endpoint** for the file picker — a browser `` yields sandboxed file *contents*, not a server-side *path* the
+ daemon can re-read, so we expose a directory-listing endpoint and render our
+ own navigable picker.
+3. **Env seeds first run, then file wins** — on first start with no settings
+ file, seed it from current env vars (or built-in defaults); thereafter the
+ settings file is the sole source of truth and env vars are ignored.
+4. **Scope: just the three settings** — AI backend, Marvin command, knowledge
+ path. No port / default timebox / default enforcement in this version.
+5. **Gear toggle on the main card** — a header gear icon toggles a settings
+ overlay in the existing single-page, SSE-driven UI; no new route.
+
+## Architecture
+
+### Key layering move: inject an "applier" closure
+
+`web` must NOT import `ai` / `tasks` / `knowledge`. Re-wiring adapters is a
+composition concern, not session state, so it does not belong on the controller
+either. Instead `main.go` (the composition root) builds:
+
+```go
+applyFn := func(s settings.Settings) error { /* construct adapters, call ctrl setters */ }
+```
+
+and injects it into the server, exactly as `SetCoach` / `SetTasks` already inject
+behavior. The HTTP handler shrinks to: validate → save file → call applier →
+broadcast. `web` depends only on the `settings.Settings` type and the injected
+closure.
+
+Dependency direction: `web → settings` (type only) and `web → applyFn` (injected).
+`main → {settings, ai, tasks, knowledge, session, web}`. No cycles.
+
+### New package: `internal/settings`
+
+A leaf type package, importing nothing else in the app.
+
+```go
+type Settings struct {
+ AIBackend string `json:"ai_backend"`
+ MarvinCmd string `json:"marvin_cmd"`
+ KnowledgePath string `json:"knowledge_path"`
+}
+
+func DefaultPath() (string, error) // ~/.antidrift/settings.json
+func Load(path string) (Settings, error)
+func Save(path string, s Settings) error // atomic temp+rename, mirrors store
+func SeedFromEnv() Settings // reads ANTIDRIFT_AI_BACKEND/_MARVIN_CMD/_KNOWLEDGE_FILE
+```
+
+Settings file (`~/.antidrift/settings.json`, next to `state.json`):
+
+```json
+{
+ "ai_backend": "claude",
+ "marvin_cmd": "uv run --project /home/felixm/dev/ampy am --json --config /home/felixm/dev/ampy/config.json",
+ "knowledge_path": "/home/felixm/.antidrift/knowledge.md"
+}
+```
+
+### Startup wiring (`main.go`)
+
+Replace the three inline `os.Getenv(...)` wirings with:
+
+1. Resolve `settings.DefaultPath()`.
+2. If the file is absent: `s := settings.SeedFromEnv()` (filling built-in
+ defaults where env is unset), then `settings.Save(path, s)`.
+ If present: `s, _ := settings.Load(path)` and env is ignored.
+3. `applyFn(s)` performs the wiring main does today:
+ - `b, err := ai.NewBackend(s.AIBackend)`; on error log + skip AI (as today);
+ else `svc := ai.NewService(b)` and call `SetCoach` / `SetDriftJudge` /
+ `SetNudge` / `SetReviewer`.
+ - `ctrl.SetTasks(tasks.NewMarvin(s.MarvinCmd))`.
+ - Knowledge unified: base source `ctrl.SetKnowledge(knowledge.NewFileSource(""))`
+ once, then `ctrl.SetKnowledgePath(s.KnowledgePath)` to drive the actual
+ path. Startup and live-edit then take the identical code path.
+4. Hand `applyFn`, the loaded `s`, and the settings file path to the server.
+
+### HTTP endpoints (`web`)
+
+Added in a new `internal/web/settings_handlers.go` to keep `web.go` focused. The
+server gains mutex-guarded fields: current `settings.Settings`, the settings file
+path, and the injected `applyFn`.
+
+- `GET /settings` → current settings as JSON.
+- `POST /settings` → body is the full settings object. The handler calls
+ `applyFn(s)` to validate-and-apply atomically: `applyFn` checks the backend
+ first and returns `settings.ErrInvalidBackend` BEFORE mutating any controller
+ state on a bad value. On that error → 400, nothing saved, nothing applied,
+ prior wiring intact. On success the handler then persists: `settings.Save` →
+ update in-memory copy → broadcast → 200 with the saved settings. Marvin
+ command and knowledge path are free-form and always accepted (bad values
+ degrade gracefully exactly as today).
+- `GET /fs/browse?dir=` → `{ "dir": "...", "parent": "...",
+ "entries": [ {"name","path","is_dir"} ] }`. Lists subdirectories plus `.md`
+ files. If `dir` is empty/missing, open at the directory of the current
+ knowledge path, else `$HOME`. Rejects a nonexistent/unreadable dir with 400.
+ No filesystem jail beyond OS permissions — localhost-only, single-user daemon,
+ same trust boundary as the rest of the UI.
+
+**Validation placement (decided):** the backend-name check lives in the injected
+`applyFn`, which returns a typed `settings.ErrInvalidBackend` (or similar) BEFORE
+mutating any controller state. The handler maps that error to 400 and skips the
+save. This keeps `web` free of an `ai` import; `applyFn` (built in `main`) owns
+all adapter knowledge including what counts as a valid backend.
+
+### Redundant endpoint removed
+
+`POST /knowledge/path` becomes redundant (knowledge path now flows through
+`/settings`). Fold it in and delete the route + `handleKnowledgePath`. The
+existing `app.js` "change profile" affordance is replaced by the settings
+overlay's knowledge field.
+
+### UI (`app.js` / `app.css`)
+
+- A gear button in the card header toggles a settings overlay over the current
+ card. No routing; reachable from any runtime state.
+- On open, `GET /settings` populates three controls:
+ - AI backend: `