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
139 lines
7.4 KiB
Markdown
139 lines
7.4 KiB
Markdown
# 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.
|