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
9 KiB
Surface lifetimes
The shell needs a memory budget and failure boundaries, not a superstition about which UI language is pure. Permanent state, optional presentation, and authority must have named owners and observable lifetimes. If closing a heavy surface leaves its objects and pages resident in the only process holding the lock screen, the surface did not meaningfully go away.
Evidence that opened this question
The daily-driver phone has 3.5 GB RAM. Three measurements from August 2026 frame the risk without pretending to be a fresh baseline:
- QuickShell reached about 610 MB RSS during the Live2D experiment;
- the summoned WebKit face added about 283 MB RSS in its own process;
- a separate dynamic-QML/per-stream rendering storm drove QuickShell to about 952 MB anonymous RSS before the kernel killed it.
These observations do not prove that ordinary QML costs 952 MB, that WebKit is always too expensive, or that a Rust surface is automatically small. They do prove that an optional renderer can consume a material fraction of the phone's memory and that a storm inside the resident QuickShell process shares a failure domain with essential session UI.
The numbers must be remeasured against exact package revisions and process trees before they become a budget.
The resident core
The resident shell keeps only surfaces and connections that must survive the loss of optional presentation:
- sessiond heartbeat and the secure session-lock handoff;
- the lock surface and lock-tier disclosure gate;
- display, idle, and device-state projections needed to remain safe;
- the small gesture and power paths required to recover or close a surface;
- a minimal system-status/recovery indication.
The resident core is not a second authority. Sessiond remains the decision owner for lock, power, and admitted device action; the shell renders its state and carries named requests. Session start owns the package and systemd graph that brings compositor, authority, and shell up in order.
The secure lock path must not be conditionally absent while a shell reload is in flight. Optional-surface work may not reopen the already-known race where a new shell requests a lock before the old holder has transferred or released it.
Optional means separately disposable
The agent Panel, Live2D presence, web content, large explorers, and future special renderers are optional surfaces. Each needs:
- a named package and executable or explicit in-process owner;
- one startup trigger and one authority that may request presentation;
- a shutdown rule after close, idle, lock transition, or resource pressure;
- a state restoration key whose authority lives outside the renderer;
- a disclosure class and locked-state behavior;
- a resource line measured while closed, opening, active, and closing;
- a supervisor policy and a visible failure event.
A QML Loader is useful for avoiding construction and destroying object
graphs. It is not proof that allocator pages returned to the operating system,
and it cannot stop a rendering storm from killing the process that owns it.
Call that path lazy only after object lifetime is verified; call memory
released only after the OS measurement falls.
For expensive surfaces, a supervised process is the preferred boundary. When the process exits, its private memory and faults leave with it. The resident shell may retain a small launch/status projection, never the heavy renderer's state graph.
The Panel is the first extraction
The agent Panel is the strongest first worker candidate because its authority already lives elsewhere:
- the Souveraine server owns agents, conversations, messages, itinerary, cancellation, interjection, and replay;
- sessiond owns session/lock/device authority;
- the Panel owns presentation, input collection, transient selection, and viewport state.
An extracted souveraine-agent-surface must therefore consume the same typed
server contract described in the agent panel. It must not
copy conversations into a new local authority or become a second writer during
candidate testing.
Its lifecycle should be explicit:
- an unlocked, admitted presentation request launches the version selected by the installed surface package;
- the worker authenticates to the local Souveraine endpoint and attaches only after Casey selects or explicitly resumes a conversation;
- close detaches presentation and gives the worker a short bounded grace for non-authoritative UI state;
- the supervisor ends the worker after that grace;
- reopen reconstructs from substrate truth and may restore harmless viewport state separately;
- lock immediately hides Personal-class content and follows the declared stop/blank policy even if the worker is stuck.
During an active stream, a killed worker must not cancel the server turn unless Casey issued cancel. The substrate continues or records its terminal state; reopening reattaches and backfills. Conversely, a worker must not remain alive forever merely because a server stream forgot to close. Presentation lifetime and inference lifetime are related state machines, not one process lifetime.
Fault containment
The resident shell needs a small supervisor projection for each optional surface: stopped, starting, ready, active, stopping, failed, and backoff. A crash becomes an event with the package version and last lifecycle edge. It must not silently relaunch in a tight loop or take the session lock with it.
The decisive fault tests are physical:
- kill the Panel during streamed text and during a large tool return;
- feed a candidate QML version that fails to load;
- wedge the worker so it ignores close;
- lock while Personal-class content is visible;
- exhaust the worker's memory budget without exhausting the resident core.
In every case the lock surface, power path, sessiond heartbeat, and recovery control must remain available. A screenshot after restart is not fault proof.
Versioned delivery on every body
The laptop and phone receive published packages from Gitea. A surface split is complete only when the package graph installs compatible versions and the systemd/session start path selects them consistently. Hand-placing a worker on one device proves a diagnosis at most; it is not the fix future installs will receive.
The core and optional-surface protocol must be versioned. Compatibility needs an explicit range or handshake so a newer Panel cannot silently interpret an older substrate event shape. Package upgrade stages a candidate; adoption and restart remain explicit where a live conversation or lock handoff could be disturbed. The last working surface stays available until the candidate reaches ready state.
Gitea Actions and ArchDev own compilation and package artifacts. Local checks may parse QML, format Rust, validate manifests, and exercise fixtures; they do not replace the package result.
Native Rust is a measured option
A Rust Wayland client could replace some or all of QuickShell, but it still has to own layer-shell/session-lock protocols, rendering, text, input, animation, accessibility, service integration, and recovery. Rust does not remove the need for resident/optional process boundaries, and a native screenshot says nothing about steady memory or fault behavior.
The useful spike is deliberately small: one native layer-shell card, one typed sessiond-fed state, one package, and measurements for RSS/PSS, launch latency, and failure. It does not take the lock, replace the shell, or grow a parallel settings authority. Compare it against the extracted QuickShell worker under the same workload before choosing a rewrite.
Measurement record
Every budget line should name:
- device and available RAM;
- source commit, package version, and protocol version;
- process tree and which surfaces are constructed;
- RSS and PSS after a defined settle time;
- peak during a defined action, including a representative streamed tool turn;
- memory after close and after worker exit;
- launch-to-ready latency;
- result of the relevant kill/lock/reopen test.
Measure at least resident idle, Panel closed, Panel open and idle, active text
stream, large tool payload, Live2D closed/open, and post-close. A single ps
line without surface state is anecdote, not a budget.
Proof and present boundary
The path is complete only when:
- the resident and optional sets are explicit in source and packages;
- closing the Panel ends its worker and the OS-visible memory leaves;
- killing or wedging that worker leaves lock, heartbeat, power, and recovery alive;
- locked-state disclosure is exercised, not inferred from a conditional;
- reopen restores the authoritative conversation without duplicating a turn;
- Gitea emits the versioned core and surface packages used by both laptop and phone;
- a Rust rewrite decision, if made, cites comparable measurements.
None of that extraction is true yet. The current Panel remains inside the resident QuickShell process; the August numbers are historical observations; and the Rust path remains a bounded experiment, not an adopted shell plan.