Watch
1
0
Fork
You've already forked SouveraineOS
0

tasks: log 07-21 findings (culver A/B/D/E, mic wedge, overview, keyboard); add 19-23

Records what the 2026-07-21/22 sessions actually found and shipped,
so the ledger matches reality:

- TASK-16: A, B, D landed; C (contact card) implemented, phone
  acceptance pending; new unit E (history-fill boot amnesia) done.
- TASK-08: shell-side state projection exists; add the maintenance-
  lease requirement so a deploy can never race the idle lock again
  (root cause of the 19:29 auto-lock-during-deploy crash).
- TASK-09: stale-lock incident — panel/IRQ alive, session-lock
  routing stale; capture both in the resume test.
- TASK-14: expand to the Auxo-style running-app surface and an
  unconditional Home tap; folds in the GLES OpacityMask and
  workspace-ratchet findings.
- TASK-17: pin the iOS-like keyboard target and the Hyprland
  key-safety hammer test.
- Mic trace path corrected to its new home.

New: TASK-19 (settings control center), TASK-20 (player video +
agent control), TASK-21 (reboot-to-Android confirmation), TASK-22
(settings/fullscreen tweaks), TASK-23 (culver locked-notification
nudge).
This commit is contained in:
Fimeg 2026-07-24 10:21:44 -04:00
commit adce4441cc
12 changed files with 406 additions and 19 deletions

View file

@ -84,7 +84,7 @@ No -110/NACK storm in dmesg across repeated open/close.
The bus-wedge diagnosis that got us here: every TX-path setup write
succeeded; the bus died on the first *read* after arming TX7/TX8
(`0x801` poll → 1.046 s timeout → NGD `TX_NACKED_2` forever). Full
trace: `linux-blueline-mic-order/MIC-WEDGE-TRACE-20260717.md`. Dead
trace: `linux-blueline-current/notes/MIC-WEDGE-TRACE-20260717.md`. Dead
ends already falsified — do not re-derive: 0x0c96 was innocent; codec
regs match Android ground truth bit-for-bit; q6afe cdc_codec_config
already fires (ret=0); AFE_SLIMBUS_SLAVE_PORT_CONFIG is SLIMBUS_5_TX

View file

@ -1,6 +1,7 @@
# TASK 08 — Device state manager / power profiles
**Status:** open. The "session state manager we dance around." Largest
**Status:** in progress. The shell-side state projection and reason-tracked
inhibitors exist; the durable manager and device tiers remain open. Largest
of the tasks; sub-divide before starting.
## Goal
@ -29,6 +30,11 @@ Wants (from handoff doc item 12):
toggle to fork — build fresh.
- **(d) Wifi handoff gating.** Radio transitions belong to the same
state model, not ad-hoc.
- **(e) Bounded maintenance/update leases.** A deploy or system update may
inhibit *automatic* idle lock while it mutates live session components,
but never bypass an existing lock or disable manual lock. The lease has an
owner/reason/deadline, survives a shell reload, expires on deploy failure,
disconnect or timeout, and restores the exact prior idle state.
## Context
@ -38,6 +44,16 @@ Wants (from handoff doc item 12):
(inhibit checked INSIDE handlers, monitor objects never destroyed).
- hypridle is the redundant fallback (300s lock, 600s screen-off, suspend
DISABLED per TASK-09 interim).
- `Session.qml` already implements `state()`, reason-tracked `inhibit()` /
`uninhibit()`, and the `Idle.inhibit` projection. A 2026-07-21 audit found
that no `IpcHandler` actually exposed those methods despite comments saying
it did; a worktree fix adds the missing local `session` IPC seam.
- **Measured maintenance failure (2026-07-21 19:29):** the phone auto-locked
during a live shell deploy. Quickshell reloaded while the compositor lock
was secure, hit `FATAL: Tried to show lockscreen surfaces without active
lock`, crashed, and sessiond correctly fail-closed. Repeated shell owners
then contended for notifications/polkit until cleanup. A deploy must never
race the idle lock again.
- The conclusions want to land in `power-profiles-daemon` as the hint layer.
## Sub-tasks (do separately)
@ -46,6 +62,10 @@ Wants (from handoff doc item 12):
2. Charge-aware profile (needs the PMIC investigation first).
3. USB role UI toggle.
4. Wifi handoff in the state model.
5. Maintenance/update lease in the durable manager. Interim `deploy.sh` may
use a systemd-timer-backed hypridle lease, but it must first refuse an
already locked session and schedule automatic recovery *before* stopping
idle handling.
Start with (1) — it's the spine the others hang off.
@ -55,6 +75,10 @@ One daemon owns device power state. `session state` IPC reports the
current tier. A plugged-in-at-100% phone doesn't float-charge blindly.
USB mode is visible and switchable from the shell.
An update lease is visible in the same state projection with owner, reason and
deadline. Manual lock during a lease wins immediately. Kill the deploy/SSH
mid-run: automatic idle handling returns within the bounded timeout.
## Connects to
TASK-09 (suspend must be safe before it's a tier), idle coordinator

View file

@ -31,6 +31,13 @@ worked once but did NOT hold on a re-wedge.
Gadget/ssh survive idle. Suspend is fully off the table until this closes.
- The "Android is the reference" rule (`android-is-the-reference-rule`):
mimic Android for the pacing; don't invent.
- 2026-07-21 stale-lock incident: Hyprland still listed `fts-1`; five harmless
taps produced five valid evdev down/up pairs and the FTS IRQ advanced, while
the secure lock surface received none. A cold reboot restored native PIN
touch immediately. Kernel logs still contained the known Force-calibration
warnings. This proves the panel/IRQ can be alive while the compositor →
session-lock routing is stale; capture both evdev and secure-surface delivery
in the resume test, not merely `hyprctl devices`.
## Acceptance

View file

@ -1,7 +1,8 @@
# TASK 14 — Overview pane → phone app grid
**Status:** open. Scoped 2026-07-20 from Casey's ask: make the overview
actually useful on a phone, appgrid-shaped.
**Status:** in progress. Scoped 2026-07-20 from Casey's ask: make the overview
actually useful on a phone, appgrid-shaped; expanded 2026-07-21 to include
Auxo-like running-app cards and an unconditional Home path.
## Goal
@ -11,7 +12,20 @@ for OSK behavior. On a 5.5" phone the workspace grid is dead weight. Replace
the pane's body with a paged app grid (the thing a phone home key opens):
icon grid from the desktop-entry list, alphabetical, paged horizontally,
long-press for app actions later. Search stays exactly as-is on top — it
already works and the OSK dance is already solved there.
already works on the laptop and the OSK dance is already solved there. The
phone regression still needs device acceptance.
The pill's second-stage swipe currently sets
`GlobalStates.missionControlOpen`, but **no surface consumes that state**.
Build the missing Auxo-like multitasking surface: horizontally swiped live app
cards from the foreign-toplevel list, tap to focus, close affordance, and a
Home card. This is separate from the app grid/search page.
Single-tapping the pill is Home after the existing 350ms double-tap window:
close overview/mission-control/OSK and focus workspace 1. Double-tap keeps its
fullscreen toggle. This is also the escape from Hyprland's internal special
workspace (`2147483646`, surfaced to Casey as workspace 48/50) after a failed
standalone window.
## Prior art
@ -37,12 +51,35 @@ already works and the OSK dance is already solved there.
- Keep the diff-against-upstream-ii discipline: new grid lives in its own
QML file(s); Overview.qml only swaps the body widget, so ii updates stay
mergeable.
- Desktop evidence 2026-07-21: `DesktopEntries=53`, `AppSearch=53`, a typed
query returned eight results and rendered them. Do not rewrite the search
backend to fix a phone-only/stale-shell symptom.
- Phone evidence 2026-07-21 after reboot: the live backend returned seven
results for `settings`, and `ScriptModel` instantiated all seven, but the
dynamic `Qt5Compat.GraphicalEffects.OpacityMask` layer around the results
column did not present them on the Adreno/GLES path. The mask was removed;
the containing rectangle already clips the list. After the controlled shell
restart the live list measured `count=7`, `height=368`, `contentHeight=348`
with one Quickshell process. Visual/touch confirmation remains the last
device acceptance step. Casey confirmed the search list renders on device.
- Related field finding 2026-07-21 ("overview page 100"): not an overview bug —
the overview shows the workspace *group* of the active Hyprland workspace,
and hyprgrass 1-finger swipe with the default
`gestures:workspace_swipe_create_new=true` had ratcheted the active
workspace id to 100 over days (each rightward swipe mints the next empty
workspace; empties behind evaporate, so no gesture leads home). Fixed on the
phone: `workspace_swipe_create_new = false` in `~/.config/hypr/hyprland.lua`
(gestures block, applied live via `hyprctl eval`); swipe now only moves
between existing workspaces. Recovery dispatch syntax on the phone's
Lua-eval hyprctl: `hyprctl dispatch "hl.dsp.focus({ workspace = 1 })"`.
## Acceptance
Overview open → app grid visible, correct icons, tap launches, search still
works with OSK, close behaves (Escape / toggle / launch). No regression in
the OSK-open dismissal behavior.
works with OSK, close behaves (Escape / toggle / launch). Second-stage pill
swipe → running-app cards; card focus/close works. Single pill tap always lands
on workspace 1; double tap still toggles fullscreen. No regression in the
OSK-open dismissal behavior.
## Connects to

View file

@ -1,7 +1,8 @@
# TASK 16 — culver: usable messenger + contacts as the Personal-class store
**Status:** open. Rooms sync today; the app is not yet usable and the
contacts store does not yet meet the security bar. Unblocks TASK-01.
**Status:** in progress. A, B and D landed 2026-07-21; C is implemented in the
local Culver worktree and passes the focused archdev build, with phone
acceptance/deploy still pending. Unblocks TASK-01.
**Size:** not single-session — four distinct work units (AD), each
session-sized. Do them in order; A is the smallest and most load-bearing.
**Repo:** `~/Projects/culver` (Gitea `Fimeg/culver`, CI on `primary`).
@ -20,9 +21,9 @@ session-sized. Do them in order; A is the smallest and most load-bearing.
export, number/MXID/email → identity resolver. The runtime already
uses it for name resolution via the `resolve_display` hook.
## The four problems (observed on device + read in tree)
## The four work units
### A. Tap a room → no messages render *(highest leverage)*
### A. Tap a room → no messages render — **landed `669f13a`**
The QML and the `ChatModel` bridge were written to **two different
contracts** and never reconciled. Same failure *class* as the
@ -52,7 +53,7 @@ tapping a room shows content with no backend change.
**Acceptance:** tap any synced room → its messages render; scroll up
paginates history; header shows title + protocol glyph.
### B. Contacts as the Personal-class store *(the security bar)*
### B. Contacts as the Personal-class store — **landed `57740b1`**
The store works but is **plaintext today**`Connection::open(path)`,
`PRAGMA journal_mode = WAL`, names/numbers/MXIDs/emails/inline photo
@ -83,7 +84,7 @@ without the machine identity (verify: move the file to a bare box → no
plaintext). A vCard export attempt raises stepUp and does nothing
without it. Round-trip still green for a legitimately-authorized share.
### C. Contact card UI *(built ON TOP of B, not before)*
### C. Contact card UI — **implemented locally; device acceptance pending**
The cards at the top of the inbox can't open because there is **no
contact-card bridge or view** — `crates/contacts` has no UI surface
@ -96,7 +97,44 @@ we are moving away from.
**Acceptance:** tapping a contact chip/avatar opens a card (name,
numbers, MXIDs, photo); "share" on the card triggers stepUp.
### D. Notifications: culver → NotifyEvents *(small)*
Implementation note (2026-07-21): `ContactModel` now projects the keyed store,
the chat-header avatar resolves SMS/Matrix participants into `ContactCard`, and
share verifies through `org.souveraine.Secrets.Manage.VerifyPassphrase` before
the runtime can mint `EgressAuth` and return a vCard. `cargo check -p culver`
passed on archdev and the touched QML passes `qmllint`; phone interaction is
not yet claimed.
Late 2026-07-21 additions (same worktree, single deploy with E):
`qml/Contacts.qml` — a contacts *list* page reached from a 👤 button in the
inbox title bar (narrow: StackView push; wide: modal popup like Accounts);
rows come from the existing `ContactModel`, tap → `selectContact` → the
shared `ContactCard`. Registered in `build.rs` views.
### E. History persistence: boot amnesia — **implemented 2026-07-21**
Casey's field report: every boot Culver "drops its message history and has
to regain it." Probe of the phone's keyed store (busctl plain-session key
fetch + `culver-core` example on archdev) showed the store persists fine —
75 conversations — but most rooms had **0 messages**: `LoadTimeline` only
ever read the local store and nothing ever asked a backend to fill gaps, so
what looked like "regaining" was sliding-sync re-delivery on every boot.
Fix: `CoreEvent::HistoryFetched { conversation, messages, before, limit }`
(core) + `spawn_history_fill` (runtime). A store page shorter than the
request spawns an async backend `messages()` fetch off the event loop;
the fetched page is persisted (idempotent upserts) and the open view
re-rendered from the store. Short *pagination* pages are WITHHELD until
the fill answers, because `ChatPrepend` has no dedup — the fill's event is
the single prepend for that request (falls back to exactly the withheld
page on backend failure). Telephony's `messages()` reads the same store,
so the fill is a harmless no-op there. Once fetched, history is on disk
(SQLCipher) and later boots serve it without the network.
Known-latent, NOT touched: ChatView's ListView is `BottomToTop` while
ChatModel rows are chronological — verify message order visually on device
before changing either side.
### D. Notifications: culver → NotifyEvents — **landed `d33d837`**
Notifications on the phone already work — the **shell owns the surface**
(`org.freedesktop.Notifications` is quickshell's; TASK archive/04 built
@ -122,6 +160,12 @@ filter/toggle later. Rail ordered by last-contacted, later honouring a
pinned state. New component, not a fix — do it after A lands so there
is content to open.
Full-app completion after C: wire the currently guarded/inert inbox mute and
delete actions, contact create/edit/import, attachment send progress, search,
delivery/read state, and account error/recovery surfaces. Treat each as a
separate acceptance-backed unit; “the backend has a method” is not a shipped
user feature until the QML bridge and phone path exercise it.
## Build / deploy
Per tree conventions: build on archdev, never the laptop. culver builds

View file

@ -47,6 +47,15 @@ one session. What shipped works; the follow-ons are UX/layout polish.
layout format (`src/layouts/*.json` in the stevia tree). Porting the
iOS layout to stevia is open design work — start from the squeekboard
header notes in `SouveraineOS/blueline/squeekboard/`.
The target is now explicit: iOS-like letter geometry, a permanent number
row, Hunspell completion retained, working language/layout switcher, mic
retained, and compact arrows available outside the terminal-only layout.
The parked `surfaces/quickshell/assets/keyboard/us.json` number-row draft
is input, not acceptance.
5. **Hyprland key safety**: Pixel3Arch `a5fdbf6` ships stevia 0.56.0-5 with
high virtual eventcodes plus zeroed fallback modifiers. Keep the
compositor-collision hammer test (bound letters/digits must never dispatch
Hyprland actions or shutdown) in every later layout patch.
4. **TTS**: Config.options.speech.tts + the Settings field exist but the
endpoint is empty — no TTS server on the VPN yet (VibeVoice runs on
10.10.20.19 but isn't wired to a REST route the shell can call). When
@ -68,8 +77,9 @@ one session. What shipped works; the follow-ons are UX/layout polish.
Keyboard usable in every app; mic key dictates real speech into the
focused field (verified 2026-07-21: "Hello, can you hear me?" end-to-end);
pill dismisses without eating key taps; a recording indicator makes the
mic key's state obvious.
pill dismisses without eating key taps; number row, layout switcher, Hunspell
suggestions and compact arrows work in the daily layout; a recording indicator
makes the mic key's state obvious.
## Connects to

View file

@ -0,0 +1,50 @@
# 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.
## 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.

View file

@ -0,0 +1,62 @@
# TASK 20 — Souveraine Player: video + first-class agent control
**Status:** open. **Repo:** `~/Projects/souveraine-player`.
## Goal
Grow the existing music/Navidrome player into a VLC-class local/network media
app without regressing its library, queue, downloads, MPRIS, equalizer or
session history. The same first-class app surface must let the agent read live
progress, issue guarded controls, and capture the exact decoded frame as an
explicit artifact.
## What exists
- GStreamer `playbin`, audio sink/filter chain, play/pause/seek/volume and a
250ms position/duration poll.
- QML now-playing bridge, queue/library/server browsing and MPRIS.
- `src/manifest.rs` maintains a useful in-process `ManifestState` with guarded
commands, but `main.rs` only constructs an `Arc`; it is **not published over
IPC or D-Bus**, so it is substrate, not an agent surface.
- No video sink/view, subtitles, chapters, stream selection or snapshots.
## Work units
### A. Video pipeline and QML surface
Give playbin a video sink integrated with Qt/Wayland, prefer hardware decode
when available, and keep one source model for local files and authenticated
HTTP streams. Handle orientation, aspect fit/fill, fullscreen and audio-only
background behavior.
### B. Media UX
Timeline/scrub preview, subtitle enable/track/file, audio-track selection,
chapters, playback speed, resume position, files/URLs/playlists, rotation and
picture modes. Surface buffering/errors instead of leaving the spinner alive.
### C. Real app/agent manifest
Publish the existing manifest through the ecosystem's explicit IPC/D-Bus seam.
Report current media, play state, position/duration, buffering, selected
audio/subtitle/chapter, capabilities and last command result. Commands are
guarded and auditable; an in-process `Arc` alone does not satisfy this unit.
### D. Exact frame snapshot
Use an appsink/tee or equivalent decoded-frame path, not screen scraping.
Snapshot records media identity, playback timestamp and output artifact path;
conversion/encoding must not stall playback. A locked session refuses personal
content egress, and agent-triggered capture follows step-up/consent policy.
### E. Acceptance matrix
Local + HTTP H.264/H.265 and device-supported codecs, audio-only, subtitle
file/embedded tracks, audio switching, chapter/seek, rotation/fullscreen,
resume and snapshot timestamp correctness. Verify live manifest cadence and
locked/refused behavior on the Pixel 3 as well as desktop.
## Connects to
`docs/FIRST_CLASS_APPS.md`, lock content policy/step-up, TASK-15 network fetcher
and idle behavior, Souveraine shell media card.

View file

@ -0,0 +1,44 @@
# TASK 21 — reboot-to-Android confirmation applet
**Status:** 2026-07-22 — scoped, not started.
**Size:** single-session.
## Why (it went off)
`/usr/share/applications/blueline-reboot-android.desktop` on the phone execs
`blueline-reboot-android` (slot switch to B + warm reboot) with ZERO
confirmation. On 2026-07-22 the overview OSK preedit bug made typed search
silently empty, Casey tapped blind, hit this entry, and the phone slot-
switched to Android mid-session. Recovery needed fastboot
(`fastboot set_active a; fastboot reboot`). A one-tap irreversible slot flip
must not sit bare in app search.
Also relevant: the warm reboot it performs is the cold-boot-doctrine
violation (TZ/remoteproc poison) — the applet should warn about that, or the
script should do the cleanest shutdown it can before switching.
## Do
1. Short term (one line): set `NoDisplay=true` in the .desktop so it leaves
app search; keep the script callable by hand.
2. Real fix: a confirmation surface in the shell —
- Dialog in souveraine style (the stepUp-card / sessionScreen pattern):
"Switch to Android? The phone reboots into slot B; getting back needs
Reboot-to-Linux from Android or fastboot."
- Confirm → run existing `blueline-reboot-android`; cancel → no-op.
- Point the .desktop `Exec` at the applet, e.g.
`qs -c souveraine ipc call session rebootAndroid` with the handler in
sessionScreen, which already owns power actions.
3. Verify the Android side has a symmetric "reboot to Linux" path so the
round trip never needs fastboot; name it in the applet text.
## Acceptance
Tapping the search result shows the confirmation; cancel is a no-op; confirm
lands in Android; slot a boots back to Linux afterward without fastboot.
## Connects to
TASK-14 (the search bug that pulled the pin), blueline-mark-boot-successful
(already guards retry exhaustion — this failure was a deliberate slot
switch, not retry burn), session authority / sessionScreen power actions.

View file

@ -0,0 +1,45 @@
# TASK 22 — souveraine-settings polish + fullscreen-app behavior sweep
**Status:** 2026-07-22 — scoped by Casey ("smaller individual tweaks to the
souveraine-settings app and fullscreen apps in general"), specifics to be
enumerated with Casey at session start. **Size:** a batch of small bounded
items, not one feature.
**Repo:** `~/Projects/souveraine` (settings), shell trees for fullscreen.
## Shape
Two lanes, each item independently landable:
### A. souveraine-settings tweaks
Individual paper cuts in the TASK-19 control center — visual, behavioral,
missing small controls. Enumerate with Casey; candidates already known from
TASK-19's "still required" list (unavailable/error states, battery/power
profile stub, etc.) but this task is for the SMALL items, not new pages.
### B. Fullscreen apps in general
Known context, all from TASK-19's launch/fullscreen findings and
fix-dock-fullscreen-detection.md (superseded doc, patch snippet still valid):
- Qt `Window.FullScreen` vs the pill's compositor-owned fullscreen path
diverge; apps should start windowed and be fullscreened via the same
Hyprland Lua dispatcher the pill uses (per-PID).
- Dock/ScreenCorners fullscreen detection needed 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`).
- Whatever additional fullscreen misbehavior Casey has been seeing across
apps (status bar overlap? corner/dock reveal while fullscreen? OSK over
fullscreen surfaces?) — capture concrete symptoms first, one item each.
## Ground rules
- Each tweak = its own small diff with its own device check; no batching
unrelated changes into one deploy.
- Anything touching the ii-base layer gets promoted to the laptop repo the
same session (divergence doctrine).
## Connects to
TASK-19 (the settings app itself), unify-shell-trees-laptop-phone.md (deploy
durability), TASK-14 (overview/pill interaction with fullscreen).

View file

@ -0,0 +1,62 @@
# TASK-23 — Culver locked-notification nudge (the push analogue)
Status: designed 2026-07-22, not started. Depends on culver's activity
orchestrator (landed 2026-07-22) and the shell's LockedHint write-back
(landed same day, souveraine Session.qml).
## Problem
Culver now stops its Matrix sliding sync while the session is locked
(battery: locked = zero network/CPU — verified on device). The trade: no
Matrix messages arrive while locked, so no lock-screen banners. SMS is
unaffected (the modem is the platform's real push channel and telephony is
exempt from `set_active`).
Android solves this with FCM push waking a brief sync
(`isSyncingNotificationEvent` in Element X's SyncOrchestrator). SouveraineOS
has no Google push — but it has something better positioned: the shell is
always awake, owns the lock surface, and already talks to culver's
notification pipe.
## Design
A periodic, shell-driven sync nudge while locked:
1. Culver exposes a D-Bus method on the session bus:
`net.wiuf.Culver1.SyncBurst()` — runs ONE sliding-sync round-trip
(SyncService start → first room-list response → stop, bounded ~10s
hard timeout) even while inactive, emitting notifications for anything
inbound. Idempotent; a burst during active sync is a no-op.
2. The shell's IdleCoordinator schedules the nudge while locked, riding the
idle stages it already manages:
- stage 01 (recently locked): every 2 min
- deeper stages: every 1015 min, aligned with any existing wake
(the point is coalescing with wakeups that happen anyway, not adding
timers)
- suspend pending/inhibited: no nudges (system asleep = truly quiet)
3. Notification path is unchanged: culver's inbound fold already posts
org.freedesktop.Notifications, which the shell renders as the lock card.
## Security notes (same doctrine as timeline-architecture.md §activity)
- SyncBurst grants nothing: it can only cause culver to do work it is
already authorized to do. Any same-user process could equally start the
app; the method is a battery lever, not an authority boundary.
- The nudge cadence is shell policy, not culver policy — the component
that owns idle/battery state owns the schedule. Culver stays policy-free.
- No content crosses the nudge interface — notifications flow through the
existing pipe with its existing redaction behavior on the lock card.
## Non-goals
- Real push (ntfy/UnifiedPush relay) — a later, separate task; this design
is forward-compatible (a push arrival would call the same SyncBurst).
- Per-room priority — the sliding-sync round already orders by recency.
## Acceptance
Lock the phone, have someone send a Matrix message, wait ≤2 min: the lock
card shows the banner while `journalctl --user -u culver-app` shows exactly
one burst (start → response → stop), then silence resumes. Battery
regression check: overnight locked drain within noise of the pre-nudge
build.

View file

@ -16,15 +16,17 @@ plus open threads from the 2026-07-17 session.
| 2 | [Lockscreen-as-Rust-system: glance + swipe](02-lockscreen-rust-system.md) | in progress | stopgap shipped 2026-07-17 |
| 3 | [Boot timing: fade splash → lock, no extra fixes](03-boot-timing-splash-to-lock.md) | diagnosed | EBUSY handoff race is the last timing bug; splash text killed, holder added |
| 7 | [WCD9340 mic: functional-pinned; unpin + fidelity](07-mic-wcd9340-slim-tx.md) | functional 07-20 | defect: power pin; kernel `mic-race-fix` |
| 8 | [Device state manager / power profiles](08-device-state-manager.md) | open | blocked-by TASK-09 suspend item |
| 8 | [Device state manager / power profiles](08-device-state-manager.md) | in progress | blocked-by TASK-09 suspend item; adds maintenance leases |
| 9 | [Suspend-resume FTS calibration race](09-suspend-resume-fts.md) | interim-fix | kernel-side; cold-boot only |
| 12 | [Face auth as a capability factor (Gaze reference)](12-gaze-reference-face-auth.md) | open | per-target: phone needs front-cam bring-up |
| 13 | [Active Edge (squeeze) as a sensor input](13-active-edge-squeeze.md) | open | substrate up; bring-up unit not drafted |
| 14 | [Overview pane → phone app grid](14-overview-appgrid.md) | open | shell-side; appgrid prior art linked in-file |
| 14 | [Overview/app grid + Auxo multitasking](14-overview-appgrid.md) | in progress | shell-side; Home path + app cards |
| 15 | [Idle coordinator: freeze, doze, scheduled wakeups](15-idle-coordinator.md) | open | design; blocked-by 09 for suspend tier; eats half of 08 |
| 16 | [culver: usable messenger + contacts as Personal-class store](16-culver-messenger-contacts.md) | open | unblocks TASK-01; uses TASK-11 (keying) + archive/04 (NotifyEvents) |
| 16 | [culver: usable messenger + contacts as Personal-class store](16-culver-messenger-contacts.md) | in progress | A/B/D landed; C contact card next; unblocks TASK-01 |
| 17 | [Keyboard (stevia) + dictation polish](17-keyboard-stevia-dictation.md) | shipped-partial | OSK + mic key + STT live 07-21; layout/UX follow-ons |
| 18 | [Selection/highlight action menu](18-selection-menu.md) | open | design captured 07-21; component-first, Read Aloud gated on TTS server |
| 19 | [First-party Settings control center](19-settings-control-center.md) | in progress | guarded standalone launch + owned pages |
| 20 | [Souveraine Player video + agent control](20-player-video-agent.md) | open | video/snapshot/manifest; preserve music |
Archived (see `archive/`): 05 crash reporter — done 2026-07-21,
CrashReporter.qml in-shell watcher, kill-test verified on device;