Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/docs/tasks/19-settings-control-center.md
Fimeg bde961c6f2 saf: one spine — device, state, and work under the index
PAF becomes saf/device (history kept), STATE.md dissolves
into saf/state.md with the dated era archived, the substrate
SAF moves up from souveraine, and every agreement points at
saf/INDEX.md and nowhere else. one map, nothing to remember
2026-08-18 09:47:30 -04:00

7.4 KiB
Raw Blame History

TASK 19 — First-party Settings control center

Status: in progress (worktree, device acceptance blocked while phone is locked). Repo: ~/Projects/souveraine.

Goal

Replace the removed ii settings application with one Souveraine-owned control center for phone and desktop. One page model, responsive chrome: stack navigation on phone, side rail on desktop. Every control is a view over the owning Config/service/daemon; no success-shaped switches.

Current surface

Owned pages: Device, Lock, Wallpaper, Dock, Navigation, Idle, Keyboard, Speech. Worktree adds Network & internet (Network/Cellular/Bluetooth), Display (real Brightness), and Sound & microphone (PipeWire defaults).

Still required: app permissions/health, storage, accounts, updates, battery/ power profile, and explicit unavailable/error states.

Launch/fullscreen failure found 2026-07-21

  • Qt Window.FullScreen did not match the pill's compositor-owned fullscreen path and could hide the navigation rail. Start windowed, then target the Settings PID with the same Hyprland Lua fullscreen dispatcher the pill uses.
  • The launcher allowed duplicate qs -p instances. Repeated taps coincided with repeated full-shell run IDs, notification/polkit contention, lost sessiond heartbeat, and fail-closed locks. Launcher must resolve the standalone QML realpath and use qs --no-duplicate -p <file>.
  • Until a controlled launch proves the log says Launching config: .../ settings.qml, the phone desktop entry stays hidden. Never accept another shell.qml run ID from the Settings button.

Polish sweep — folded in from TASK-22

Scoped by Casey 2026-07-22 as "smaller individual tweaks to the souveraine-settings app and fullscreen apps in general". It was never a separate work unit: its own text said the candidates come from this task's "still required" list, and its status line read "items to enumerate with Casey at session start", which is a placeholder rather than a task.

A. Settings paper cuts. The small items only — unavailable/error states, battery/power profile stub, and whatever else Casey names. Not new pages. The idle page's seconds-spinners were one of these and are done (2026-07-26: presets, and the lock-screen timers now read live from sessiond). That work turned up the sharper version of the same rule: reading the owning daemon is only half of "no success-shaped switches". SetPolicy mutated memory and persisted nothing, so a timer set in Settings was live until the next restart and then silently back to the built-in 15 s. The page was honest; the daemon forgot. Fixed 2026-07-26 — the policy is written to $XDG_CONFIG_HOME/souveraine/device-state-policy.json and a failed save comes back as ok:false instead of a control that looks like it took.

Worth sweeping for elsewhere: every other control this page owns should be checked against the same two questions — does it read the authority, and does the authority remember?

B. Fullscreen apps in general. Qt Window.FullScreen and the pill's compositor-owned fullscreen path diverge; apps should start windowed and be fullscreened through the same Hyprland Lua dispatcher the pill uses, per-PID. Dock/ScreenCorners fullscreen detection needs the QML fallback for qs -p windows — applied hot on both devices but stranded per-device until the tree unification (unify-shell-trees-laptop-phone.md).

Ground rules: each tweak is its own small diff with its own device check, no batching. Anything touching the ii-base layer gets promoted to the laptop repo the same session (divergence doctrine).

The state machine needs a surface here (Casey, 2026-07-28)

"the souveraine-settings section needs to have all these state machine mechanics health checks built"

Everything the device state machine knows is currently legible only through forensic.jsonl and qs ipc call sessiond device_state. No screen shows any of it. Three separate gaps, already named elsewhere and converging here:

  • TASK-08(f) — sensors_degraded reaches no surface. §10 source health landed: a source that reported and then went silent for 90 s is Down, not quiet, and every forensic snapshot carries the flag. The trail knows; nothing else does. The crash reporter structurally cannot help — the SLPI outage exited status 0. Needs an ambient indicator saying which source and since when, and device_state already ships sensor_health over IPC, so the data is there.
  • TASK-08(g) — the confidence gates are computed, logged, and never branched on. A settings page over them would be honest only as a readout, not as controls, until (g)'s decision is made. Showing a slider for a threshold nothing consults is TASK-19's own "no success-shaped switches" rule broken.
  • Grip is now a fourth evidence source with no readout (2026-07-28, saf/device/edge-sense.md). A refused squeeze — the pocket veto — is recorded via record_decision and visible nowhere. "Why didn't my squeeze work" has no answer on the device today.

Ordering follows TASK-19's existing rule: expose what actually actuates. Health and evidence are observations and can be shown honestly right now — state, panel, evidence, confidence, per-source health and last-seen, the sensors_degraded flag, and the recent decision entries. Controls over the confidence bands wait on (g).

That split matters: a health readout that is merely accurate is useful the day it ships, whereas a controls page over a machine that ignores its own thresholds would be lying.

Built 2026-07-31 (souveraine 1c90d7f) — the readout half. DeviceEvidence was egress-only: every surface could report input to the machine and none could read it back. It now has an ingress that polls device_state and forensic_log, and the Device page renders state, lock phase, panel, dimmed, display-active, idle, observed + confidence, wake-suppression, per-source health with the sensors_degraded banner, and the last twelve decisions with their reasons. Strictly a projection — it holds nothing the protocol owns and branches on nothing, so it does not become §1's eighth actor; and it polls only while a surface is watching, so an open settings page is not a request per second all day. available: false renders as "sessiond is not answering on this device" rather than healthy-looking zeroes, which is §10's rule that "no evidence" and "evidence says nothing is happening" are different states.

Still owed here: last-seen per source (TASK-19 asked for it; device_state ships health and freshness but no timestamp, so it is not shown rather than faked), and grip as a fourth evidence source — it is absent from device_state entirely, so "why didn't my squeeze work" still has no answer on the device. Controls remain blocked on TASK-08(g).

Acceptance

  1. Hammer Settings icon ten times: exactly one standalone PID/window, zero new shell.qml run IDs, one notification owner, one polkit agent, sessiond heartbeat continuous.
  2. Settings log identifies settings.qml; close exits the process.
  3. Phone opens using the pill's fullscreen state; pill remains usable and its double tap returns the app to windowed.
  4. Every page parses and scrolls at 540×1080; controls change the real service and reflect failure/unavailability.
  5. Desktop uses the same page components in a normal resizable window.

Connects to

TASK-08 (power/update state), TASK-17 (keyboard/speech), TASK-07 (microphone), TASK-15 (idle), deploy maintenance lease in TASK-08.