docs: settings-menu design spec + implementation plan

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-28 15:59:47 +02:00
co-authored by Claude Opus 4.8
parent a9b9ce297a
commit 0cd14b5b55
2 changed files with 1647 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,141 @@
# 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 <config>` 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** — `<select>` from `GET /workspaces`.
- **Provider** — `<select>` derived from the available models; changing it filters
the model list.
- **Model** — `<select>` filtered by the chosen provider (from `GET /models`).
- **Thinking** — `<select>` 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).