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
194 lines
9 KiB
Markdown
194 lines
9 KiB
Markdown
# 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](02-session-start.md) 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](04-agent-pane.md). It must not
|
|
copy conversations into a new local authority or become a second writer during
|
|
candidate testing.
|
|
|
|
Its lifecycle should be explicit:
|
|
|
|
1. an unlocked, admitted presentation request launches the version selected by
|
|
the installed surface package;
|
|
2. the worker authenticates to the local Souveraine endpoint and attaches only
|
|
after Casey selects or explicitly resumes a conversation;
|
|
3. close detaches presentation and gives the worker a short bounded grace for
|
|
non-authoritative UI state;
|
|
4. the supervisor ends the worker after that grace;
|
|
5. reopen reconstructs from substrate truth and may restore harmless viewport
|
|
state separately;
|
|
6. 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:
|
|
|
|
1. the resident and optional sets are explicit in source and packages;
|
|
2. closing the Panel ends its worker and the OS-visible memory leaves;
|
|
3. killing or wedging that worker leaves lock, heartbeat, power, and recovery
|
|
alive;
|
|
4. locked-state disclosure is exercised, not inferred from a conditional;
|
|
5. reopen restores the authoritative conversation without duplicating a turn;
|
|
6. Gitea emits the versioned core and surface packages used by both laptop and
|
|
phone;
|
|
7. 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.
|