Files
ThothII/docs/superpowers/specs/2026-06-28-settings-menu-design.md
T
2026-06-28 15:59:47 +02:00

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:

  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:

{ "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).