6.8 KiB
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:
- Workspace — from the workspace YAML files where workspaces are defined.
- Model & provider — chosen among those actually available to Pi, via a real query to Pi (not free text).
- 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 theSETTINGS_FILEenv 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:
{ "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 newalready 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 (defaultbackend/data/settings.json, overridable viaSETTINGS_FILE). - On read, if the file is absent, return effective defaults: env
PI_PROVIDER/PI_MODEL/PI_THINKINGplus the first workspace fromGET /workspaces.
Endpoints
GET /settings→ current effective settings{ workspace, provider, model, thinking }.PUT /settings→ persist new settings. Validatesmodelagainst 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 rpcwith the same cwd/env as session spawns (so API keys resolve), sendget_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 (
listModelsdep) so tests stub it.
POST /sessions
- No longer requires
workspace/model/provider/thinkingfrom the frontend. - Reads the settings file and applies them:
workspace→ selects the-c <config>fortht session new,provider/model→set_model,thinking→set_thinking_level.
- Request body is reduced to
{ question }(plus optionalname). 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 }toPOST /sessions.
Settings menu + dialog
- Add a "Settings" button in the
AppShellsidebar, directly under "Nuova domanda", styled as a secondary (variant="outline") button, matching the existing button conventions. - New
SettingsDialog(same Dialog/base-ui pattern asNewSessionDialog) with four controls:- Workspace —
<select>fromGET /workspaces. - Provider —
<select>derived from the available models; changing it filters the model list. - Model —
<select>filtered by the chosen provider (fromGET /models). - Thinking —
<select>fixedlow/medium/high.
- Workspace —
- Loads current values from
GET /settings; saves withPUT /settings. - Degradation: if
GET /modelsreturns 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.createSessionsimplified to send only{ question }(+ optionalname).
Error handling / edge cases
- Pi unreachable or zero models:
/models→[];SettingsDialogwarns and degrades to text inputs; nothing blocks. - Stale settings (model no longer available):
PUT /settingsrejects 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 /settingsreturns 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).