# Settings Menu — Design **Date:** 2026-06-28 **Status:** Approved (design), pending implementation plan **Layers:** frontend, backend (harness involved only as data source) ## Problem When creating a new question, the `NewSessionDialog` form currently asks the user for four parameters inline: **workspace**, **model** (modello), **provider**, and **thinking**. Model/provider are free-text, thinking is free-text, and these choices are re-entered on every new question. These four parameters should instead be **global settings**, edited from a dedicated **"Settings"** menu item placed under "Nuova domanda" in the sidebar. The "Nuova domanda" form is reduced to just the question text and uses the current settings. The settings must be populated from real sources: 1. **Workspace** — from the workspace YAML files where workspaces are defined. 2. **Model & provider** — chosen among those actually available to Pi, via a real query to Pi (not free text). 3. **Thinking** — fixed list: `low`, `medium`, `high`. ## Decisions (from brainstorming) - **Settings scope:** GLOBAL. The "Nuova domanda" form contains only the question text and uses the current settings. There is no per-question override. - **Persistence:** a JSON file on the backend (survives restarts, backend can apply it as defaults). - **Workspace display name:** filename-derived (no new YAML field). YAGNI. - **Settings file location:** `backend/data/settings.json`, path overridable via the `SETTINGS_FILE` env var, gitignored. ## Sources of truth | Setting | Source | |-----------|------------------------------------------------------------------------| | Workspace | `harness/workspaces/*.yaml` (already exposed via `GET /workspaces`) | | Provider | Derived from Pi's available models (group by `model.provider`) | | Model | Pi RPC `get_available_models` → `{ models: [{ provider, id, name, reasoning }] }` | | Thinking | Fixed list `low` / `medium` / `high` | ### Pi model query — confirmed facts `pi --mode rpc` accepts the stdin command `get_available_models` and replies: ```json { "type": "response", "id": "...", "command": "get_available_models", "models": [ { "provider": "anthropic", "id": "claude-...", "name": "…", "reasoning": true, ... }, … ] } ``` `modelRegistry.getAvailable()` returns **only models with auth configured** (an API key present in the backend's environment), so every returned model is actually usable. `provider` is a field on each model; the provider list is derived by grouping. Pi supports thinking levels `off, minimal, low, medium, high, xhigh`; we expose only `low/medium/high`. ## Harness layer No code change required. The harness remains: - the **source** of workspace YAML files (read by the backend via `GET /workspaces`), - the tool whose `tht session new` already accepts `--provider/--model/--thinking`, - the wrapper around Pi, which answers `get_available_models`. ## Backend layer (Fastify) ### Settings store - New module persisting `{ workspace, provider, model, thinking }` to a JSON file (default `backend/data/settings.json`, overridable via `SETTINGS_FILE`). - On read, if the file is absent, return effective defaults: env `PI_PROVIDER/PI_MODEL/PI_THINKING` plus the first workspace from `GET /workspaces`. ### Endpoints - `GET /settings` → current effective settings `{ workspace, provider, model, thinking }`. - `PUT /settings` → persist new settings. Validates `model` against the available models from Pi; rejects unknown models with a clear error. (If Pi returns no models — unavailable — validation is skipped so settings can still be saved.) - `GET /models` → completes the existing stub: ephemeral Pi spawn + `get_available_models`, returns `{ models: [{ provider, id, name, reasoning }] }`. Result is cached in memory (Pi startup is slow); graceful fallback to `{ models: [] }` when Pi is unavailable. - `GET /workspaces` → unchanged. ### Ephemeral model listing - Spawn `pi --mode rpc` with the same cwd/env as session spawns (so API keys resolve), send `get_available_models`, read the response, kill the child. - Cache the result in memory for the process lifetime; expose an implicit refresh by letting the cache be re-queried (a query param or a short TTL — implementation detail for the plan). Keep the existing injectable seam (`listModels` dep) so tests stub it. ### `POST /sessions` - No longer requires `workspace/model/provider/thinking` from the frontend. - Reads the settings file and applies them: - `workspace` → selects the `-c ` for `tht session new`, - `provider`/`model` → `set_model`, - `thinking` → `set_thinking_level`. - Request body is reduced to `{ question }` (plus optional `name`). Any settings-like fields in the body are ignored (no per-question override). ## Frontend layer (React + base-ui + Tailwind + React Query) ### NewSessionDialog - Remove the workspace / model / thinking / provider fields. - Keep only the question textarea. Submit sends `{ question }` to `POST /sessions`. ### Settings menu + dialog - Add a **"Settings"** button in the `AppShell` sidebar, directly under "Nuova domanda", styled as a secondary (`variant="outline"`) button, matching the existing button conventions. - New `SettingsDialog` (same Dialog/base-ui pattern as `NewSessionDialog`) with four controls: - **Workspace** — `` derived from the available models; changing it filters the model list. - **Model** — `` fixed `low/medium/high`. - Loads current values from `GET /settings`; saves with `PUT /settings`. - **Degradation:** if `GET /models` returns an empty list (Pi unavailable / no keys), show a notice and fall back to free-text inputs for provider and model so settings remain editable. ### API client - `getSettings()` / `putSettings(s)` for `/settings`. - `listModels()` updated to the `{ models: [{provider, id, name, reasoning}] }` shape. - `createSession` simplified to send only `{ question }` (+ optional `name`). ## Error handling / edge cases - **Pi unreachable or zero models:** `/models` → `[]`; `SettingsDialog` warns and degrades to text inputs; nothing blocks. - **Stale settings (model no longer available):** `PUT /settings` rejects unknown models when a model list is available; at session-creation time stored values are passed through to Pi as-is (Pi reports if invalid). - **First run (no settings file):** `GET /settings` returns env/first-workspace defaults; the dialog is usable immediately. ## Out of scope - No new field in the workspace YAML schema. - No per-question override of settings. - No multi-user / per-user settings (single shared settings file).