252 lines
13 KiB
Markdown
252 lines
13 KiB
Markdown
---
|
||
title: First-Class Souveraine Apps — the requirements for belonging to the family
|
||
status: draft — living standard
|
||
created: 2026-07-19
|
||
supersedes: nothing (this gap had no doc)
|
||
related:
|
||
- docs/tasks/souveraine-shell-ecosystem.md (layer registry, manifests, guarded methods)
|
||
- docs/tasks/quickshell-surface-package.md (how surfaces are packaged / adopted)
|
||
- docs/DECISIONS.md ("One family, two modes"; "Cut the cord from ii")
|
||
reference_on_disk:
|
||
- ~/Projects/sayonara-player (pulled 2026-07-19 as a real-app structural reference, NOT a dependency)
|
||
---
|
||
|
||
# First-Class Souveraine Apps
|
||
|
||
> The iOS line. The default built-in app line. There must be cohesion.
|
||
|
||
This is the missing layer. We had a doc for *packaging* a surface
|
||
(`quickshell-surface-package.md`) and a doc for the *framework* surfaces plug
|
||
into (`souveraine-shell-ecosystem.md`). We had no doc that says what an app
|
||
must *be* to count as one of ours. This is that doc.
|
||
|
||
An app is **first-class** when a person cannot tell, from feel alone, whether
|
||
it shipped in the base image or arrived later. Same skin, same gestures, same
|
||
manners, same relationship to the agent and to the lock. Cohesion is not a
|
||
coat of paint applied at the end — it is a set of contracts each app satisfies
|
||
by construction.
|
||
|
||
Think of Apple's built-in line: Phone, Messages, Notes, Settings. They differ
|
||
wildly in purpose and share almost nothing in code, yet nobody mistakes one
|
||
for a third-party bolt-on. That is the bar. Not "looks similar" — *belongs.*
|
||
|
||
---
|
||
|
||
## 0. The one law
|
||
|
||
**One family, two modes — never two apps.** (DECISIONS.md, Jul 13.) An app is a
|
||
single implementation that expresses itself on laptop and phone through
|
||
form-factor gates, exactly as `SouveraineFamily.qml` loads one panel set for
|
||
both and sheds `Config.options.souveraine.phone` gates as the modes converge.
|
||
|
||
A "phone version" and a "desktop version" as separate code is an automatic
|
||
disqualification. The differences are density and reach, declared inline, not
|
||
a fork.
|
||
|
||
---
|
||
|
||
## The nine requirements
|
||
|
||
A surface earns "first-class" only when it satisfies all nine. They are
|
||
listed in the order you'd build them.
|
||
|
||
### 1. Original, not borrowed
|
||
|
||
Every primitive an app is built from is **original Souveraine AGPL** or a
|
||
shared Souveraine widget — never a vendored `ii` file. (DECISIONS.md: *cut the
|
||
cord from ii by rewriting, not vendoring*; vendoring inherits GPLv3, which we
|
||
refuse.)
|
||
|
||
- A borrowed `ii` module is a *temporary* host, allowed only while its
|
||
original replacement is being built. It is a debt, tracked, not a resting
|
||
state. `SouveraineFamily.qml` is the scoreboard of that convergence — the
|
||
goal is fewer `qs.modules.ii.*` imports over time, each replaced by
|
||
`qs.modules.souveraine.*`.
|
||
- **Test:** grep the app's imports. Any `qs.modules.ii.*` is a listed debt
|
||
with a rewrite task, or the app is not first-class yet.
|
||
|
||
Today the shared original vocabulary is thin on purpose — `modules/common/widgets`
|
||
holds `ContentPage` and `StyledToolTip`; most primitives are still borrowed.
|
||
That thinness is the work, not a reason to keep borrowing. New apps grow the
|
||
shared vocabulary; they don't deepen the `ii` dependency.
|
||
|
||
### 2. The single skin
|
||
|
||
An app draws from **one** appearance source and threads **no** bespoke accent.
|
||
|
||
- Colors, spacing, radii, typography, elevation resolve through the shared
|
||
appearance system (`modules/common/Config.qml` + the atmosphere the agent
|
||
controls). This mirrors the TUI's law (`CLAUDE.md`, rule 3): *don't thread
|
||
`accent: Color` through constructors — `apply_atmosphere` feeds the palette
|
||
and the primary lands on the agent's color.* The QML surfaces obey the same
|
||
discipline: the atmosphere is the instrument; an app reads it, never
|
||
hardcodes around it.
|
||
- **Consequence — cohesion is automatic, not curated.** When the agent shifts
|
||
its atmosphere, every first-class app moves together, because none of them
|
||
pinned their own palette. An app with a hardcoded color is the one that
|
||
visibly falls out of the family the moment the mood changes.
|
||
- **Test:** grep for literal color values and per-component accent args. Both
|
||
are smells; the theme resolves them.
|
||
|
||
### 3. Native to both reaches — touch and pointer, close and far
|
||
|
||
The same surface must be **honestly usable** by thumb on a Pixel and by
|
||
pointer on a laptop — not merely *present* on both.
|
||
|
||
- Hit targets, gesture affordances, and reveal/dismiss must satisfy the phone;
|
||
information density and secondary controls may expand on the laptop. These
|
||
are `extraCondition` / mode gates on one surface (`Config.options.souveraine.phone`),
|
||
the same mechanism the family already uses, not a second layout file.
|
||
- The phone's system gestures (`SystemGestureRail`) are part of the contract:
|
||
an app must not fight the edge-swipe navigation the shell owns.
|
||
- **Test:** drive the app by thumb only, then by pointer only. If either reach
|
||
needs a different build to be usable, it isn't one app yet (violates §0).
|
||
|
||
### 4. State-gated, layer-honest
|
||
|
||
An app declares its layer and stack-order and lets **shell state gate its
|
||
visibility** — it does not decide on its own when it may appear.
|
||
|
||
- The canonical line is Phosh's `use_top_layer = !locked`: state drives layer
|
||
visibility, not the reverse. (`souveraine-shell-ecosystem.md` §1.) An app
|
||
that paints over a locked screen, or ignores OSK suppression, or fights
|
||
another surface for the top layer, is misbehaving regardless of how it looks.
|
||
- Until the `LayerRegistry` / formalized `ShellState` exists, an app still
|
||
reads the existing state bits (`GlobalStates`: barOpen, oskOpen,
|
||
screenLocked, overviewOpen…) rather than tracking visibility privately.
|
||
- **Test:** lock the screen, open the OSK, enter overview. A first-class app
|
||
responds correctly to each without being told per-case.
|
||
|
||
### 5. Lock-aware and trust-tiered
|
||
|
||
An app respects the **lock trust split** and the capability tiers (see the
|
||
lock memories / `souveraine-lock-primitives.md`): `screenLocked` (requested)
|
||
is not `screenLockSecure` (compositor-acknowledged), and personal data gates
|
||
on *secure*, never on the request.
|
||
|
||
- Ambient content may show on the lock surface (media card, agent card, the
|
||
glanceable line). Personal content and any mutation gate on secure + fresh
|
||
auth; step-up is a `PamContext` prompt, not a soft check the app rolls
|
||
itself.
|
||
- **Test:** what does the app show, and permit, on the lock surface before
|
||
authentication? If anything personal leaks or any mutation lands, it fails.
|
||
|
||
### 6. A live manifest — the app is legible to the system
|
||
|
||
A first-class app **projects its state** as a read-only, queryable structure
|
||
(`souveraine-shell-ecosystem.md` §2) — not because everything needs it today,
|
||
but because legibility is what lets the shell, the agent, and other apps relate
|
||
to it without parsing its QML.
|
||
|
||
- Shape follows the dock's model: `app.manifest → { …read-only snapshot… }`,
|
||
transported over the qs IPC surface. Build the projection when a real
|
||
consumer exists; don't speculatively manifest a surface nothing acts on yet.
|
||
- **Test:** can another surface (or the agent) learn this app's current state
|
||
without reaching into its internals? If the only way to know what it's doing
|
||
is to read its component tree, it isn't legible yet.
|
||
|
||
### 7. Agent-actionable through guarded methods — never a footgun
|
||
|
||
Where the agent (or any external actor) should be able to *act* on an app, the
|
||
app exposes a **guarded method surface**, not raw controls
|
||
(`souveraine-shell-ecosystem.md` §3).
|
||
|
||
- We do **not** build a new toolcall integration per app. Souveraine is
|
||
already a fully-fleshed harness; apps expose guarded methods
|
||
(`dock.pin(appId)`, `app.reveal()`, …) that the existing harness reaches.
|
||
- Every method: validates inputs; **refuses** mutation in states that forbid
|
||
it (locked, mid-drag, manifest stale) with a real *refused-with-reason*
|
||
result; is idempotent where it can be; never fire-and-forget. The agent
|
||
genuinely cannot break the app through the abstraction. That is the whole
|
||
point.
|
||
- The **teaching** of these methods lives in the Souveraine School, never
|
||
inlined into the method surface or an agent prompt. The app exposes; the
|
||
School teaches. Two concerns, kept apart.
|
||
- **Test:** call a mutating method while the screen is locked. A first-class
|
||
app returns a refusal with a reason. A footgun does the mutation.
|
||
|
||
### 8. Packaged reversibly — adoption, never ambush
|
||
|
||
An app ships through the **surface-package safety contract**
|
||
(`quickshell-surface-package.md`): it belongs to a device profile
|
||
(`souveraine-surface-laptop` / `-phone`) with shared QML in a common layer, and
|
||
installing or upgrading it **never silently replaces** a person's existing
|
||
shell.
|
||
|
||
- Adoption defaults to dry-run and names every managed / new / replacement
|
||
target. Replacements require explicit `--adopt` and back up each conflict to
|
||
a timestamped `*.pre-souveraine-*` file. Upgrades update assets but never
|
||
auto-adopt. Uninstall restores originals and removes only Souveraine-managed
|
||
symlinks — never user data or agent memory.
|
||
- **Idempotent by construction** (feedback: *db-changes-are-research*): the
|
||
same install must work on a fresh machine and as an upgrade. A first-class
|
||
app's package does not assume a clean slate and does not corrupt on re-run.
|
||
- **Test:** install the app twice, then uninstall. Nothing user-owned changed
|
||
without consent; the machine is left as it was found.
|
||
|
||
### 9. Provenance and license clean
|
||
|
||
An app is **AGPL, original, attribution-clean** end to end. No vendored GPLv3
|
||
`ii` source in its lineage (§1). Commits to the public branch carry no
|
||
`Co-Authored-By`, no Claude attribution, no third-party attribution, no PII in
|
||
source or history (public-branch memories). Behavioral reference from other
|
||
projects (ii, Phosh, sayonara) is *studied, not copied* — the C/C++ stays on
|
||
disk as a reference checkout, and our implementation is ours.
|
||
|
||
- **Test:** could this app's tree and history ship publicly, today, with no
|
||
redaction and no license contradiction? If not, it isn't first-class.
|
||
|
||
---
|
||
|
||
## The built-in line (the "iOS default apps" of Souveraine)
|
||
|
||
These are the apps that define the family — the ones a person meets first and
|
||
measures every later app against. Each must be exemplary on all nine
|
||
requirements, because they *are* the reference.
|
||
|
||
- **Settings** — the canonical "shows the standard" app. (The ecosystem doc
|
||
referenced a `SETTINGS-APP-PLAN.md` that isn't on disk yet; when Settings is
|
||
built native it becomes requirement §1–§2's showcase — one skin, original
|
||
widgets, both reaches.)
|
||
- **Lock surface** — already the strictest §4/§5 exemplar: `LockSurfaceHost`,
|
||
`LockAgentCard`, `LockMediaCard`. It is where trust-tiering is proven.
|
||
- **Dock** — the §6/§7 exemplar: the first surface to get a manifest + guarded
|
||
method surface, chosen for smallest blast radius.
|
||
- **The agent surfaces** (SidebarLeft / AiChat, the pill) — the app line's
|
||
relationship to the consciousness itself; these carry the atmosphere most
|
||
visibly and so hold §2 to the highest standard.
|
||
- **Phone / Calls, Messages, Media** — the device-native line. Where the phone
|
||
reach (§3) and the system-gesture contract are non-negotiable.
|
||
|
||
An app joining this line is a promotion, held to the reference standard. An app
|
||
outside it still must pass all nine to be called first-class — the line is just
|
||
where the bar is *demonstrated.*
|
||
|
||
---
|
||
|
||
## How to use this doc
|
||
|
||
- **Building a new app:** walk the nine in order. §1–§3 are structural (get
|
||
them wrong and no later fix helps). §4–§5 are behavioral (state and trust).
|
||
§6–§7 are legibility and agency (build when a consumer is real). §8–§9 are
|
||
shipping.
|
||
- **Reviewing an app:** each requirement has a **Test** — a concrete thing to
|
||
do, not a vibe. An app that passes all nine tests is first-class. Name which
|
||
it fails; that failure is the task.
|
||
- **Reference on disk:** `~/Projects/sayonara-player` is a mature real-world
|
||
Qt app pulled for structure only (how a serious app organizes
|
||
Components/Gui/DBus/Interfaces, how it separates engine from view). It is
|
||
**not** a dependency and **not** a thing we vendor — same rule as ii and
|
||
Phosh: study the shape, write our own.
|
||
|
||
## What this doc is not
|
||
|
||
- Not a widget catalog. The shared vocabulary (`modules/common/widgets`) is
|
||
thin today and grows as apps are rewritten off ii; this doc names the
|
||
*standard* those widgets serve, not the widgets themselves.
|
||
- Not a rewrite mandate. Borrowed `ii` modules keep working; §1 makes their
|
||
replacement a tracked debt, not an emergency.
|
||
- Not a policy engine. Like the rest of Souveraine, the substrate provides
|
||
contracts and guardrails; the app (and the agent) decide. Doctrinal
|
||
guarantees (lock trust split, adoption safety, forward-only provenance) are
|
||
**not** runtime knobs (feedback: *no-doctrinal-knobs*).
|