|
|
@@ -1,9 +1,10 @@
|
|
|
-# Spry conversion conventions (goal 41, binding for T3–T10)
|
|
|
+# Spry conversion conventions
|
|
|
|
|
|
ClankerSaddle runs on the Spry CSS framework that ships inside
|
|
|
-libspry. This document is the binding contract for every page task in
|
|
|
-the UI/UX goal: what Spry provides, what `app.css` owns, how classes
|
|
|
-map from the old stylesheet, and the rules for new markup.
|
|
|
+libspry. This document is the binding contract for the UI/UX page
|
|
|
+tasks: what Spry provides, what `app.css` owns, how classes map from
|
|
|
+the old stylesheet, the rules for new markup — and the app-side
|
|
|
+contracts of the Spry services the app adopts (Web Push).
|
|
|
|
|
|
## How the framework is loaded
|
|
|
|
|
|
@@ -386,3 +387,61 @@ The audit is wired into the suite as the `t12mobile` bundle: the
|
|
|
server-side invariants run always; the browser audit runs when
|
|
|
chromium is present (CI without chromium skips it gracefully — pass
|
|
|
`T12MOBILE_BROWSER=1` to require it).
|
|
|
+
|
|
|
+## Web Push (the app side of `Spry.Push`)
|
|
|
+
|
|
|
+Web Push plumbing is Spry's, delivered by `SpryModule`
|
|
|
+(`application.add_module<SpryModule>()` in `main.vala` registers it):
|
|
|
+
|
|
|
+- **`Spry.Push.PushService`** — the subscription store over the
|
|
|
+ `spry_push_subscriptions` table (Spry's own migration creates it):
|
|
|
+ `subscribe` / `unsubscribe` / `subscriptions_for` / `forget`.
|
|
|
+- **`Spry.Push.PushSender`** — the singleton delivery engine:
|
|
|
+ Locksmith RFC 8291 payload encryption + RFC 8292 VAPID signing and
|
|
|
+ the libsoup POST, with inline bounded retry (two attempts, 250 ms
|
|
|
+ apart). Endpoints answering 404/410 (gone) or 401/403 (refused) are
|
|
|
+ swept from the store; other failures keep the row. Without keys
|
|
|
+ every send answers `SKIPPED` — loudly warned, never an error.
|
|
|
+
|
|
|
+Keys are operator-supplied (`spry keys` writes the statum pairs AND
|
|
|
+the `spry` section's `vapid_private_key` / `vapid_public_key`; see the
|
|
|
+README's operations section). The app never generates, stores or
|
|
|
+rotates key material.
|
|
|
+
|
|
|
+What OUR side owns (`src/Services/PushService.vala` — the thin app
|
|
|
+layer; it never touches a key or a socket):
|
|
|
+
|
|
|
+- **Payload builders** — `block_notification`,
|
|
|
+ `lifecycle_notification`, `goal_block_notification`,
|
|
|
+ `goal_terminal_notification`, `test_notification`: pure functions
|
|
|
+ producing the `{title, body, url}` wire payload.
|
|
|
+- **Audiences** — global (every push-subscribed user: block events,
|
|
|
+ goal blocks, goal terminals) vs opted-in (users holding a
|
|
|
+ `task_notify_prefs` row: a task's first start, failure, completion).
|
|
|
+ Dispatch is fire-and-forget at event time through a fresh container
|
|
|
+ scope — no queue, no dispatcher tick.
|
|
|
+- **Notify prefs** — the `task_notify_prefs` CRUD (pure app domain).
|
|
|
+- **Fingerprints** — `endpoint_fingerprint` (first 16 hex of SHA-256)
|
|
|
+ powers the per-browser bell without ever exposing an endpoint URL.
|
|
|
+- **`send_test`** — the bell's end-to-end probe, mapping Spry's
|
|
|
+ `DeliveryOutcome[]` to the toast's delivered/failed/pruned counts.
|
|
|
+
|
|
|
+The subscribe/unsubscribe actions (`src/actions/PushActions.vala`)
|
|
|
+keep our wording and the navigate-to-`/` refresh but call Spry's store
|
|
|
+underneath; they subclass nothing.
|
|
|
+
|
|
|
+The service worker `GET /sw.js` (`src/Api/PushApi.vala`) is
|
|
|
+deliberately **app-owned**: Spry's model leaves `sw.js` to the
|
|
|
+application because the click-through routing and the payload shape
|
|
|
+are the app's. It parses our `{title, body, url}` payload and focuses
|
|
|
+or opens the task/goal page on click.
|
|
|
+
|
|
|
+The bell UX (`initPushBell` in `src/pages/app.js`, rendered by the
|
|
|
+`.push-bell-card` on the dashboard rail and `/profile`) is OUR UI
|
|
|
+contract. Spry ships a client script (`spry-push.js` at
|
|
|
+`/_statum/resource/`) — it is deliberately NOT used: the bell's
|
|
|
+per-browser fingerprint matching, the enable/turn-off/test trio and
|
|
|
+the unkeyed state (no VAPID key in the page state => Enable hidden,
|
|
|
+helper text points the operator at `spry keys`) predate and differ
|
|
|
+from it. Do not "simplify" the bell onto `spry-push.js` without
|
|
|
+redesigning that contract.
|