feat(web): announce a pairing knock wherever you are #459

Merged
enricobuehler merged 2 commits from feat/console-pairing-toast into main 2026-08-30 23:36:06 +00:00
Owner

One item from #294 — luxus's suggestion on the issue:

maybe a simple toast on incoming pairing requests?

The problem

A device asking to pair appeared only on the Pairing page. Anywhere else in the console, nothing said it had happened, so a request sat until someone thought to go and look. Worst on a phone, which is what #294 is about: that page is several taps away.

Why it costs almost nothing

Both halves already existed and were not joined up:

  • The host publishes the knock as a pairing.pending SSE frame (events.rs, EventKind::PairingPending { device: DeviceRef }).
  • The console already subscribes to that exact kind in api/events.ts and already uses toast from @unom/ui/toast across eight sections.

So this hangs a toast off a frame that was arriving anyway. It names the device from the event's own payload and offers one button through to Pairing. recordActivity now returns what it parsed instead of discarding it, so the payload is read without parsing the same frame twice.

A related bug fixed in passing

keysFor("pairing.pending") invalidated the status card and the pairing status — not the pending list itself. The list an operator sits and waits on refreshed only on its own 10 s timer, despite an event saying it had just changed. Both pairing.pending and pairing.denied now invalidate getListPendingDevicesQueryKey().

The one compromise

The toast's action does window.location.assign("/pairing") rather than a router push. api/events.ts is a module-level singleton, not a component, and router.tsx keeps liveRouter private and typed as { latestLocation: { href: string } } — no navigate to reach. It costs a page load, on a button someone pressed on purpose. Commented as such.

Strings

Three keys added to both catalogues (en, de), inserted in place beside the existing pairing block rather than re-sorted, so the diff is 3 lines per file:

Key en de
pairing_knock_title A device wants to pair Ein Gerät möchte sich koppeln
pairing_knock_desc {name} is waiting for approval. {name} wartet auf Freigabe.
pairing_knock_action Review Ansehen

bun run codegen reports 759 compiled messages for en and de.

Verification

Check Result
bunx biome check on the changed files clean
bun test server/ 28 pass, 0 fail
tsc --noEmit 1 error, not from this branch — see below

The single tsc error is src/router.tsx(72,29): TS2554: Expected 0-1 arguments, but got 2, in a file this branch does not touch. Cause: the linked node_modules carries @unom/ui 0.8.16 where package.json requires ^0.10.0, and 0.8.16's reloadOnStaleChunk takes one argument. Zero errors in the changed files.

bun run build was not run locally — it is the first step of CI's web job, and it fails for the same stale-dependency reason. This PR's CI run is the first real pass.

Not in scope

The rest of #294 — navigation, margins, section reorganisation. #294 stays open.

Refs #294

One item from #294 — luxus's suggestion on the issue: > maybe a simple toast on incoming pairing requests? ## The problem A device asking to pair appeared only on the Pairing page. Anywhere else in the console, nothing said it had happened, so a request sat until someone thought to go and look. Worst on a phone, which is what #294 is about: that page is several taps away. ## Why it costs almost nothing Both halves already existed and were not joined up: - The host publishes the knock as a `pairing.pending` SSE frame (`events.rs`, `EventKind::PairingPending { device: DeviceRef }`). - The console already subscribes to that exact kind in `api/events.ts` and already uses `toast` from `@unom/ui/toast` across eight sections. So this hangs a toast off a frame that was arriving anyway. It names the device from the event's own payload and offers one button through to Pairing. `recordActivity` now returns what it parsed instead of discarding it, so the payload is read without parsing the same frame twice. ## A related bug fixed in passing `keysFor("pairing.pending")` invalidated the status card and the pairing status — **not the pending list itself**. The list an operator sits and waits on refreshed only on its own 10 s timer, despite an event saying it had just changed. Both `pairing.pending` and `pairing.denied` now invalidate `getListPendingDevicesQueryKey()`. ## The one compromise The toast's action does `window.location.assign("/pairing")` rather than a router push. `api/events.ts` is a module-level singleton, not a component, and `router.tsx` keeps `liveRouter` private and typed as `{ latestLocation: { href: string } }` — no `navigate` to reach. It costs a page load, on a button someone pressed on purpose. Commented as such. ## Strings Three keys added to **both** catalogues (`en`, `de`), inserted in place beside the existing pairing block rather than re-sorted, so the diff is 3 lines per file: | Key | en | de | | --- | --- | --- | | `pairing_knock_title` | A device wants to pair | Ein Gerät möchte sich koppeln | | `pairing_knock_desc` | {name} is waiting for approval. | {name} wartet auf Freigabe. | | `pairing_knock_action` | Review | Ansehen | `bun run codegen` reports 759 compiled messages for en and de. ## Verification | Check | Result | | --- | --- | | `bunx biome check` on the changed files | clean | | `bun test server/` | 28 pass, 0 fail | | `tsc --noEmit` | 1 error, **not from this branch** — see below | The single `tsc` error is `src/router.tsx(72,29): TS2554: Expected 0-1 arguments, but got 2`, in a file this branch does not touch. Cause: the linked `node_modules` carries `@unom/ui` **0.8.16** where `package.json` requires **^0.10.0**, and 0.8.16's `reloadOnStaleChunk` takes one argument. Zero errors in the changed files. ⚠ **`bun run build` was not run locally** — it is the first step of CI's `web` job, and it fails for the same stale-dependency reason. This PR's CI run is the first real pass. ## Not in scope The rest of #294 — navigation, margins, section reorganisation. #294 stays open. Refs #294
enricobuehler added 1 commit 2026-08-30 21:35:57 +00:00
feat(web): announce a pairing knock wherever you are
ci / bun-nix (pull_request) Successful in 1m21s
ci / web (pull_request) Successful in 1m58s
ci / docs-site (pull_request) Successful in 2m6s
ci / rust-arm64 (pull_request) Successful in 2m26s
ci / docs-drift (pull_request) Successful in 35s
ci / rust (pull_request) Failing after 5m20s
7004aee28f
A device asking to pair showed up only on the Pairing page. Anywhere
else in the console nothing said it had happened, so a request waited
until someone thought to go and look — worst on a phone, where that page
is several taps away.

The host already publishes the knock as a `pairing.pending` event and
the console already subscribes, so this hangs a toast off the frame
that was arriving anyway. It names the device from the event's own
payload and offers one button through to the Pairing page.

The same event now also invalidates the pending list itself, which it
did not: it refreshed the status card and the pairing status, and left
the list an operator actually sits and waits on to its own 10 s timer.

The action navigates rather than routing: this module is not a
component and has no router to reach. It costs a page load, on a button
someone pressed on purpose.

Refs #294
enricobuehler added 1 commit 2026-08-30 22:44:58 +00:00
Merge branch 'main' into feat/console-pairing-toast
ci / rust-arm64 (pull_request) Successful in 2m30s
ci / web (pull_request) Successful in 2m48s
ci / docs-site (pull_request) Successful in 2m35s
ci / docs-drift (pull_request) Canceled after 0s
ci / rust (pull_request) Canceled after 9m23s
ci / bun-nix (pull_request) Canceled after 7m34s
d2c2240d71
enricobuehler merged commit a7d9d29f4e into main 2026-08-30 23:36:06 +00:00
enricobuehler deleted branch feat/console-pairing-toast 2026-08-30 23:36:12 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#459