port: Settings › Devices (approved mockup) #902

Open
kayg wants to merge 17 commits from port/devices into dev
Owner

Ports the approved Settings › Devices from apps/mockup (review/calendar-2, #867) into apps/web. Today's Settings layout is kept; only the Devices page content changes. The 3-pane Settings layout and the sidebar-foot change are not in here (not yet reviewed by kayg).

What is ported

  • Devices list: This device (update / install row, AppCard), Signed in, Apple devices, Add a device row.
  • Sign out / Remove motion (lib/leave-row.ts, devices/ConfirmRemove.svelte): the button grows into [Cancel · Confirm] with a clip reveal, laid out once. The row leaves one frame after the confirm (a copy layer slides out; the rows below glide up with a transform-only FLIP).
  • Add a device: the shared glass sheet (chrome/SheetShell.svelte + lib/morph-glass.ts, the one glass, clip-path reveal on desktop, rise from the bottom on a phone) with its three choices (iPhone or iPad, Mac, Something else). Own lazy chunk (devices).

Wired to what the server has (nothing invented)

  • Signed in: account.installations, account.revoke.
  • Apple devices: account.app-passwords, account.app-password-revoke.
  • Add a device: POST /api/v1/auth/app-passwords {name, scopes}. The server wants a recent passkey check; on a 403 the sheet runs reverifyPasskey() once and repeats.
  • Rows leave at once; a failed call puts the row back and says so (toast with Try again).

Missing server actions (left out of this PR)

  1. Set up profile (.mobileconfig) and its URL: the iPhone QR and the Mac "Download profile" in the mockup need it. Every kind therefore ends on the by-hand details (server, port 443, user, password shown once).
  2. Scopes beyond contacts: the server grants contacts and notes.*; Calendar/Tasks (CalDAV) and Mail (IMAP) have no scope that anything serves yet, and Notes over IMAPS is still in #873–#882. So the sheet offers Contacts only, and the mockup's "What should it sync?" step is skipped (one app).
  3. Rename / per-app switches / device page (DeviceDetail): no rename or scope-edit action exists (account.app-passwords is list + revoke only). Not part of the approved list here.
  4. The kind of a device: the server keeps only a name, so the icon/kind is read from the name the sheet gave it ("iPhone", "Mac 2", "Other device").

Decisions

  • Icons for the device kinds are drawn in lib/device-kinds.ts and passed to Icon through a new optional d prop, so the shared icon set (first paint) did not grow.
  • No dimPage (body/theme-color tint) port: the sheet dims with its own scrim fade only. Rows on the sheet's glass have no grey fill (kayg 11-02 memo); the mockup's rule for that was dead CSS, the product applies it.
  • Filled buttons keep their colour on hover (the shared .press tint washed out Done / Install / the confirm).
  • flow.spec.ts and pwa.spec.ts followed the new markup (two-step Sign out; the "Apple devices" heading made the substring query for "Devices" ambiguous).
  • Overlap check: no open PR covers Devices. #842/#849 have their own sheet shells (ShareSheet, contacts/SheetShell); chrome/SheetShell here is the mockup's current glass version. Whoever lands second should fold the other onto it.

Gates (details in the comment below)

  • svelte-check: only the 2 existing errors in e2e/review4-signout.spec.ts (same on dev). Web unit tests 96/96.
  • e2e/devices.spec.ts --repeat-each=5 CI=1 --retries=0: Chromium 1440+390 60/60 (dev-01), WebKit 1440+390 60/60 (dev-02). Neighbours (pwa, flow, admin, settings-fit/links, lazy-ui/offline, passkeys-ui) pass.
  • Bundle (perf/web.sh --bundle-only --gate): first JS 233.200 → 233.463 KB (+0.26 KB: the manifest lines for the new chunk and the Icon prop; budget 240), first CSS unchanged 18.799. devices chunk 2.879 KB (own line, 3.05), idle-screens 39.188 → 41.815 (50.5), total 353.255 → 359.036.
  • Frames on the Mac VM: not demonstrated, see the comment. The rig was noisy: its own idle row failed the gate.

🤖 Generated with Claude Code

Budget (kayg approved, 2026-11-08 08:20 IST): kayg approved raising the lazy budget total, up to 543.9 KiB. perf/budgets.json in this PR sets each named lazy line to the largest size measured over the 17 approved ports after trimming (rounded up to 0.01 KiB) and the total to the sum of the lines (543.25 KiB). The same file is in the first PR of every stack, so whichever merges first carries it. First paint is judged separately and has not been raised.

Ports the approved Settings › Devices from `apps/mockup` (review/calendar-2, #867) into `apps/web`. Today's Settings layout is kept; only the Devices page content changes. The 3-pane Settings layout and the sidebar-foot change are **not** in here (not yet reviewed by kayg). ## What is ported - **Devices list**: This device (update / install row, `AppCard`), Signed in, Apple devices, Add a device row. - **Sign out / Remove motion** (`lib/leave-row.ts`, `devices/ConfirmRemove.svelte`): the button grows into [Cancel · Confirm] with a clip reveal, laid out once. The row leaves one frame after the confirm (a copy layer slides out; the rows below glide up with a transform-only FLIP). - **Add a device**: the shared glass sheet (`chrome/SheetShell.svelte` + `lib/morph-glass.ts`, the one glass, clip-path reveal on desktop, rise from the bottom on a phone) with its three choices (iPhone or iPad, Mac, Something else). Own lazy chunk (`devices`). ## Wired to what the server has (nothing invented) - Signed in: `account.installations`, `account.revoke`. - Apple devices: `account.app-passwords`, `account.app-password-revoke`. - Add a device: `POST /api/v1/auth/app-passwords {name, scopes}`. The server wants a recent passkey check; on a 403 the sheet runs `reverifyPasskey()` once and repeats. - Rows leave at once; a failed call puts the row back and says so (toast with Try again). ## Missing server actions (left out of this PR) 1. **Set up profile** (`.mobileconfig`) and its URL: the iPhone QR and the Mac "Download profile" in the mockup need it. Every kind therefore ends on the by-hand details (server, port 443, user, password shown once). 2. **Scopes beyond `contacts`**: the server grants `contacts` and `notes.*`; Calendar/Tasks (CalDAV) and Mail (IMAP) have no scope that anything serves yet, and Notes over IMAPS is still in #873–#882. So the sheet offers Contacts only, and the mockup's "What should it sync?" step is skipped (one app). 3. **Rename / per-app switches / device page** (`DeviceDetail`): no rename or scope-edit action exists (`account.app-passwords` is list + revoke only). Not part of the approved list here. 4. **The kind of a device**: the server keeps only a name, so the icon/kind is read from the name the sheet gave it ("iPhone", "Mac 2", "Other device"). ## Decisions - Icons for the device kinds are drawn in `lib/device-kinds.ts` and passed to `Icon` through a new optional `d` prop, so the shared icon set (first paint) did not grow. - No `dimPage` (body/theme-color tint) port: the sheet dims with its own scrim fade only. Rows on the sheet's glass have no grey fill (kayg 11-02 memo); the mockup's rule for that was dead CSS, the product applies it. - Filled buttons keep their colour on hover (the shared `.press` tint washed out Done / Install / the confirm). - `flow.spec.ts` and `pwa.spec.ts` followed the new markup (two-step Sign out; the "Apple devices" heading made the substring query for "Devices" ambiguous). - Overlap check: no open PR covers Devices. #842/#849 have their own sheet shells (`ShareSheet`, `contacts/SheetShell`); `chrome/SheetShell` here is the mockup's current glass version. Whoever lands second should fold the other onto it. ## Gates (details in the comment below) - svelte-check: only the 2 existing errors in `e2e/review4-signout.spec.ts` (same on dev). Web unit tests 96/96. - `e2e/devices.spec.ts` `--repeat-each=5` `CI=1 --retries=0`: Chromium 1440+390 60/60 (dev-01), WebKit 1440+390 60/60 (dev-02). Neighbours (pwa, flow, admin, settings-fit/links, lazy-ui/offline, passkeys-ui) pass. - Bundle (`perf/web.sh --bundle-only --gate`): first JS 233.200 → 233.463 KB (+0.26 KB: the manifest lines for the new chunk and the `Icon` prop; budget 240), first CSS unchanged 18.799. `devices` chunk 2.879 KB (own line, 3.05), idle-screens 39.188 → 41.815 (50.5), total 353.255 → 359.036. - Frames on the Mac VM: **not demonstrated**, see the comment. The rig was noisy: its own idle row failed the gate. 🤖 Generated with [Claude Code](https://claude.com/claude-code) **Budget (kayg approved, 2026-11-08 08:20 IST):** kayg approved raising the lazy budget total, up to 543.9 KiB. `perf/budgets.json` in this PR sets each named lazy line to the largest size measured over the 17 approved ports after trimming (rounded up to 0.01 KiB) and the total to the sum of the lines (543.25 KiB). The same file is in the first PR of every stack, so whichever merges first carries it. First paint is judged separately and has not been raised.
kayg added 17 commits 2026-11-03 17:15:23 +00:00
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Icon takes an optional path (d) for a shape that only one screen uses.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
fix: filled device buttons keep their colour on hover (the press tint washed out Done, Install and the confirm)
Some checks failed
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
CI / server (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
58c423d4cc
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Author
Owner

Gate evidence (head 58c423d4)

Functional e2e/devices.spec.ts --repeat-each=5 CI=1 --retries=0 --workers=1: chromium-1440 + chromium-390 60/60 (dev-01); webkit-1440 + webkit-390 60/60 (dev-02).

Bundle (brotli KB, base = origin/dev 41f2efa3 → head): first JS 233.200 → 233.463 (budget 240); first CSS 18.799 → 18.799; idle-screens 39.188 → 41.815 (50.5); new devices chunk 2.879 (budget line 3.05); total JS 353.255 → 359.036 (budget 407.25 = sum of lines).

Frames, Mac VM (macos-01, rAF deltas, lock held with EXIT trap, Chromium excluded: its idle row is p50 50 ms / p95 133 ms on this VM, so it is not an instrument). WebKit, ms, run 3 of 3 (runs 1–2 look the same):

series 390 p95 / max / >25 1440 p95 / max / >25
idle 1 s (control) 27 / 38 / 22 27 / 256 / 22
sign-out ask 26 / 110 / 14 27 / 41 / 15
sign-out confirm 22 / 34 / 13 34 / 76 / 42
remove ask 22 / 37 / 7 33 / 71 / 23
remove confirm 20 / 32 / 13 58 / 1908 / 47
add open 25 / 35 / 25 37 / 231 / 49
add close 25 / 60 / 20 36 / 154 / 37

The gate (p95 ≤ 20, 0 frames > 25 ms) is NOT shown as met. The idle control fails it itself (p95 27, 22 frames > 25 ms), so the VM was noisy during all three runs (earlier today the mockup's idle row was p95 20 / max 20). Phone gestures are no worse than idle; at 1440 add open/close and the confirm glide are above the idle row and need a re-run on a quiet Mac before this merges. The probe is a throwaway spec (not committed).

Shots (product vs mockup, 1440 and 390, light and dark; list, asking, Add kinds, Add details): code:~/Developer/Worktrees/shots/port-devices/ (product-* / mockup-*).

## Gate evidence (head 58c423d4) **Functional** `e2e/devices.spec.ts --repeat-each=5 CI=1 --retries=0 --workers=1`: chromium-1440 + chromium-390 60/60 (dev-01); webkit-1440 + webkit-390 60/60 (dev-02). **Bundle** (brotli KB, base = origin/dev 41f2efa3 → head): first JS 233.200 → 233.463 (budget 240); first CSS 18.799 → 18.799; idle-screens 39.188 → 41.815 (50.5); new `devices` chunk 2.879 (budget line 3.05); total JS 353.255 → 359.036 (budget 407.25 = sum of lines). **Frames, Mac VM** (macos-01, rAF deltas, lock held with EXIT trap, Chromium excluded: its idle row is p50 50 ms / p95 133 ms on this VM, so it is not an instrument). WebKit, ms, run 3 of 3 (runs 1–2 look the same): | series | 390 p95 / max / >25 | 1440 p95 / max / >25 | |---|---|---| | idle 1 s (control) | 27 / 38 / 22 | 27 / 256 / 22 | | sign-out ask | 26 / 110 / 14 | 27 / 41 / 15 | | sign-out confirm | 22 / 34 / 13 | 34 / 76 / 42 | | remove ask | 22 / 37 / 7 | 33 / 71 / 23 | | remove confirm | 20 / 32 / 13 | 58 / 1908 / 47 | | add open | 25 / 35 / 25 | 37 / 231 / 49 | | add close | 25 / 60 / 20 | 36 / 154 / 37 | **The gate (p95 ≤ 20, 0 frames > 25 ms) is NOT shown as met.** The idle control fails it itself (p95 27, 22 frames > 25 ms), so the VM was noisy during all three runs (earlier today the mockup's idle row was p95 20 / max 20). Phone gestures are no worse than idle; at 1440 add open/close and the confirm glide are above the idle row and need a re-run on a quiet Mac before this merges. The probe is a throwaway spec (not committed). Shots (product vs mockup, 1440 and 390, light and dark; list, asking, Add kinds, Add details): code:~/Developer/Worktrees/shots/port-devices/ (`product-*` / `mockup-*`).
Author
Owner

Follow-up: first paint and Mac frames (head 49d8c000)

First-paint JS (brotli KB, perf/web.sh --bundle-only --gate) — dev 41f2efa3: 233.200. This PR: 233.724 → (shared Icon reverted, device icons drawn in components/devices/DeviceIcon.svelte) 233.427 → (devices.svelte.ts no longer imports ApiError) 233.337 now (+0.137 KB; raw index +36 B). First CSS 18.799 = dev. Not ≤ dev yet. What I found:

  • The chunk is registered like every other app chunk (lazy() in a module only Settings loads, so the entry is not in the first-screen module). The remaining first-screen cost is vite's __vite__mapDeps file list, which names every lazy file in index, plus the symbol renumbering that follows: the same sources shift the brotli size by ±0.1 KB (an equivalent if/else rewrite of the sheet made it 233.436, so I reverted it). Raw index is +36 B over dev; the brotli swing is renaming noise on an 845 KB file.
  • I could not remove that last 36 B without dropping the separate chunk (it was a requirement of the brief). Options for you: accept a +36 B raw / ≈+0.1 KB brotli floor for a new chunk, or fold the sheet into idle-screens (no new manifest names; costs the "own budget line").

Mac frames (macos-01 WebKit, rAF deltas, lock held, no other job on the VM: ps showed nothing but system daemons).

  • The idle control cannot pass on this VM. A blank page with one box measures p95 21–22 ms, max 28–33, 2–3 frames > 25 ms of 276, so the rig floor already misses the strict gate; the product page idle is p95 21–29 (390 / 1440). Cua spaces are gone (memory note outdated), so macos-01 is the only Mac VM.
  • Two runs (A, B), p95 ms / frames > 25 / total; idle is the control:
390 run A run B
idle 23 / 9 / 418 21 / 5 / 417
sign-out ask 24 / 6 / 247 24 / 8 / 251
sign-out confirm 21 / 9 / 422 25 / 16 / 396
remove ask 23 / 5 / 249 24 / 9 / 246
remove confirm 21 / 5 / 451 19 / 9 / 414
add open 24 / 16 / 568 24 / 14 / 570
add close 24 / 4 / 417 24 / 9 / 420
1440 run A run B / extra runs
idle 28 / 24 / 391 29–31 / 21–27 (A/B test runs)
sign-out ask 28 / 15 / 237 –
sign-out confirm 31 / 26 / 368 –
remove ask 35 / 19 / 218 –
remove confirm 40 / 46 / 283 –
add open 35 / 39 / 518 37 / 42 / 522; 35 / 50 / 518
add close 34 / 42 / 387 35 / 40 / 388; 34 / 41 / 387

(Run B's 1440 sign-out/remove rows did not finish: the test timed out on the VM.)

  • At 390 every gesture is within the idle control. At 1440 the confirms and Add open/close sit 5–10 ms (p95) above idle in every run. I tested one cause: replacing the sheet's full-viewport dim fade with a static dim changes nothing (open 35 → 39, close 34 → 38), so it is not the dim. I did not find a fix; the 1440 idle itself is p95 28–31, so the gap is hard to attribute on this rig. Needs a quiet Mac with a real GPU (the Studio itself, or a real Mac) before the 1440 numbers mean anything; I did not run on the Studio host (memory: never the host).
## Follow-up: first paint and Mac frames (head 49d8c000) **First-paint JS (brotli KB, `perf/web.sh --bundle-only --gate`)** — dev 41f2efa3: **233.200**. This PR: 233.724 → (shared `Icon` reverted, device icons drawn in `components/devices/DeviceIcon.svelte`) 233.427 → (devices.svelte.ts no longer imports `ApiError`) **233.337 now (+0.137 KB; raw index +36 B)**. First CSS 18.799 = dev. Not ≤ dev yet. What I found: - The chunk is registered like every other app chunk (`lazy()` in a module only Settings loads, so the entry is not in the first-screen module). The remaining first-screen cost is vite's `__vite__mapDeps` file list, which names every lazy file in index, plus the symbol renumbering that follows: the same sources shift the brotli size by ±0.1 KB (an equivalent if/else rewrite of the sheet made it 233.436, so I reverted it). Raw index is +36 B over dev; the brotli swing is renaming noise on an 845 KB file. - I could not remove that last 36 B without dropping the separate chunk (it was a requirement of the brief). Options for you: accept a +36 B raw / ≈+0.1 KB brotli floor for a new chunk, or fold the sheet into `idle-screens` (no new manifest names; costs the "own budget line"). **Mac frames** (macos-01 WebKit, rAF deltas, lock held, no other job on the VM: `ps` showed nothing but system daemons). - **The idle control cannot pass on this VM.** A blank page with one box measures p95 21–22 ms, max 28–33, 2–3 frames > 25 ms of 276, so the rig floor already misses the strict gate; the product page idle is p95 21–29 (390 / 1440). Cua spaces are gone (memory note outdated), so macos-01 is the only Mac VM. - Two runs (A, B), p95 ms / frames > 25 / total; idle is the control: | 390 | run A | run B | |---|---|---| | idle | 23 / 9 / 418 | 21 / 5 / 417 | | sign-out ask | 24 / 6 / 247 | 24 / 8 / 251 | | sign-out confirm | 21 / 9 / 422 | 25 / 16 / 396 | | remove ask | 23 / 5 / 249 | 24 / 9 / 246 | | remove confirm | 21 / 5 / 451 | 19 / 9 / 414 | | add open | 24 / 16 / 568 | 24 / 14 / 570 | | add close | 24 / 4 / 417 | 24 / 9 / 420 | | 1440 | run A | run B / extra runs | |---|---|---| | idle | 28 / 24 / 391 | 29–31 / 21–27 (A/B test runs) | | sign-out ask | 28 / 15 / 237 | – | | sign-out confirm | 31 / 26 / 368 | – | | remove ask | 35 / 19 / 218 | – | | remove confirm | 40 / 46 / 283 | – | | add open | 35 / 39 / 518 | 37 / 42 / 522; 35 / 50 / 518 | | add close | 34 / 42 / 387 | 35 / 40 / 388; 34 / 41 / 387 | (Run B's 1440 sign-out/remove rows did not finish: the test timed out on the VM.) - At 390 every gesture is within the idle control. At 1440 the confirms and Add open/close sit 5–10 ms (p95) above idle in every run. I tested one cause: replacing the sheet's full-viewport dim fade with a static dim changes nothing (open 35 → 39, close 34 → 38), so it is not the dim. I did not find a fix; the 1440 idle itself is p95 28–31, so the gap is hard to attribute on this rig. Needs a quiet Mac with a real GPU (the Studio itself, or a real Mac) before the 1440 numbers mean anything; I did not run on the Studio host (memory: never the host).
Author
Owner

housekeeping #1009: rebased+failing, left open for review.

rebased Devices onto dev; kept current icon registry and picker groups. retained the original explicitly budgeted 3.05 KB devices chunk addition; first-paint caps unchanged.

head: 1064f17245ee57972f0e0d8c65ae04797d985eec; based on dev cd7f84432febfc63b788fbb9b9f9950452982d74, pushed with the original exact force-with-lease.

frontend frozen install, check, lint, build and unit tests passed. bundle gate failed. first-paint brotli vs dev: JS 230774 B (-3 B), CSS 18386 B (+0 B).

targeted Chromium and WebKit specs at 1440 and 390 never started. the audit waited more than three hours behind the live daily-train-1107 locks (first queue at 23:45 IST, 7 November; final queue cleanup after 02:46 IST, 8 November). browser validation and UI captures are unrun. only this audit’s exact queued units were stopped; the train locks and owners were left intact. hot-path perf qualification is unrun.

all executed gates used the VM Nix environment, shared Mac lock and slot. exact commands, source heads, logs and dev baseline. frontend inputs are identical to the tested frontend tree when a Rust-only repair changed the head.

original-head cherry/patch inventory, pr-902.md; unmatched entries are missing-patch candidates, with semantic overlap/replacements noted above.

final queue, cleanup and source-verification evidence.

housekeeping #1009: rebased+failing, left open for review. rebased Devices onto dev; kept current icon registry and picker groups. retained the original explicitly budgeted 3.05 KB devices chunk addition; first-paint caps unchanged. head: `1064f17245ee57972f0e0d8c65ae04797d985eec`; based on dev `cd7f84432febfc63b788fbb9b9f9950452982d74`, pushed with the original exact force-with-lease. frontend frozen install, check, lint, build and unit tests passed. bundle gate failed. first-paint brotli vs dev: JS 230774 B (-3 B), CSS 18386 B (+0 B). targeted Chromium and WebKit specs at 1440 and 390 never started. the audit waited more than three hours behind the live daily-train-1107 locks (first queue at 23:45 IST, 7 November; final queue cleanup after 02:46 IST, 8 November). browser validation and UI captures are unrun. only this audit’s exact queued units were stopped; the train locks and owners were left intact. hot-path perf qualification is unrun. all executed gates used the VM Nix environment, shared Mac lock and slot. [exact commands, source heads, logs and dev baseline](https://git.kayg.org/attachments/870a0fef-2e31-425c-aea6-eb87a66d365a). frontend inputs are identical to the tested frontend tree when a Rust-only repair changed the head. [original-head cherry/patch inventory](https://git.kayg.org/attachments/e05a38dc-c028-4d77-8655-efa9d5c8ea50), `pr-902.md`; unmatched entries are missing-patch candidates, with semantic overlap/replacements noted above. [final queue, cleanup and source-verification evidence](https://git.kayg.org/attachments/7ab315a5-4b6c-4af1-971b-cdae2637adae).
Author
Owner

Final-port test pass (issue #1009), READY: no: specs pass; bundle gate fails.

head 1064f17245ee57972f0e0d8c65ae04797d985eec

Targeted specs, CI=1 --retries=0 --workers=1, dev-01 Chromium and dev-02 WebKit (through ~/bin/slot), against a dev-head backend (cd7f84432).

spec Chromium 1440 Chromium 390 WebKit 1440 WebKit 390
devices 6/6 6/6 6/6 6/6

flow and pwa (own projects) not run.

Bundle gate: perf/web.sh --bundle-only --gate (the 17 failures from the housekeeping audit). Per the gate's own rule (total_js_brotli budget must equal the sum of the named chunk budgets) a cap can only move between named chunks; there is no donor chunk inside this PR, so a fix is a code cut or a decision by kayg. I raised nothing and moved nothing.

  • idle-screens 53.69 vs 51.8 (+1.9 KB). First paint JS -3 B, CSS +0 B vs dev. The PR's own 3.05 KB devices chunk is not what is over: the Devices code lands in idle-screens. Moving it into its own lazy chunk (with its own budget) is the likely fix.

Not done: Not run yet: Mac VM Safari blur/frame check and perf ABAB x5 on the perf VMs (they need a ready PR first); light/dark 2x screenshots against the mockup are only done for #888 (see its comment).

Not marked READY FOR MERGE.

**Final-port test pass (issue #1009), READY: no: specs pass; bundle gate fails.** head `1064f17245ee57972f0e0d8c65ae04797d985eec` Targeted specs, `CI=1 --retries=0 --workers=1`, dev-01 Chromium and dev-02 WebKit (through `~/bin/slot`), against a dev-head backend (`cd7f84432`). | spec | Chromium 1440 | Chromium 390 | WebKit 1440 | WebKit 390 | |---|---|---|---|---| | devices | 6/6 | 6/6 | 6/6 | 6/6 | `flow` and `pwa` (own projects) not run. **Bundle gate:** `perf/web.sh --bundle-only --gate` (the 17 failures from the housekeeping audit). Per the gate's own rule (`total_js_brotli` budget must equal the sum of the named chunk budgets) a cap can only move between named chunks; there is no donor chunk inside this PR, so a fix is a code cut or a decision by kayg. I raised nothing and moved nothing. - `idle-screens` 53.69 vs 51.8 (+1.9 KB). First paint JS -3 B, CSS +0 B vs dev. The PR's own 3.05 KB devices chunk is not what is over: the Devices code lands in `idle-screens`. Moving it into its own lazy chunk (with its own budget) is the likely fix. **Not done:** Not run yet: Mac VM Safari blur/frame check and perf ABAB x5 on the perf VMs (they need a ready PR first); light/dark 2x screenshots against the mockup are only done for #888 (see its comment). Not marked READY FOR MERGE.
Author
Owner

Screenshots and state (issue #1009), 026e6d4055

Settings, Devices (#902). READY FOR MERGE apart from one item: the perf ABAB x5 run is queued behind Mail (the perf-02 session was cancelled when Mail went first; this page is a lazy Settings chunk, off the typing and open-note paths).

gate result
first paint JS 230918 B plain: -237 B vs dev 231155 B; hash-normalised 230672 B: +50 B vs 230622 B
first paint CSS 18371 B: +0
specs, Chromium + WebKit, 1440 + 390 devices.spec: 12 passed
perf/web.sh --bundle-only --gate passes (devices line 6.3 KiB; merge-order total 540.6 KiB, under 543.9)
screenshots Chromium 2x and real Safari 1x below

Dev itself fails five phone specs at 390 (glass:76, lazy-ui:70, preload:89 x3); not this PR.

Product (Chromium, 2x; the list is faked with page.route as devices.spec.ts does) next to the approved mockup, light and dark.
Differences from the mockup: the mockup has an Install Calternal card and a longer fixture list (iPad, Claude Code, named Apple devices); the product shows only the faked list; the mockup's settings sidebar and warm backdrop differ from the product's shorter sidebar. The structure (This device, Signed in, Apple devices, Add a device, the Cancel / Sign out confirm) is the same.

Screenshots (2x; light and dark; 1440x900 and 390x844). Left: the approved mockup (review/calendar-2). Right: this PR.

devices

mockup this PR
1440 light
1440 dark
390 light
390 dark

revoke

mockup this PR
1440 light
1440 dark
390 light
390 dark

Real Safari (Mac VM, 1x; the same fake answers account.installations and account.app-passwords, injected by a script in a copy of the dist because the page loads its lists once on mount)

devices

light dark
1440
390

revoke

light dark
1440
390
**Screenshots and state (issue #1009), 026e6d405530e095753d64f32d987db8a020adf1** Settings, Devices (#902). **READY FOR MERGE apart from one item: the perf ABAB x5 run is queued behind Mail** (the perf-02 session was cancelled when Mail went first; this page is a lazy Settings chunk, off the typing and open-note paths). | gate | result | |---|---| | first paint JS | 230918 B plain: **-237 B** vs dev 231155 B; hash-normalised 230672 B: +50 B vs 230622 B | | first paint CSS | 18371 B: +0 | | specs, Chromium + WebKit, 1440 + 390 | devices.spec: 12 passed | | `perf/web.sh --bundle-only --gate` | passes (devices line 6.3 KiB; merge-order total 540.6 KiB, under 543.9) | | screenshots | Chromium 2x and real Safari 1x below | Dev itself fails five phone specs at 390 (`glass:76`, `lazy-ui:70`, `preload:89` x3); not this PR. Product (Chromium, 2x; the list is faked with `page.route` as `devices.spec.ts` does) next to the approved mockup, light and dark. Differences from the mockup: the mockup has an Install Calternal card and a longer fixture list (iPad, Claude Code, named Apple devices); the product shows only the faked list; the mockup's settings sidebar and warm backdrop differ from the product's shorter sidebar. The structure (This device, Signed in, Apple devices, Add a device, the Cancel / Sign out confirm) is the same. ### Screenshots (2x; light and dark; 1440x900 and 390x844). Left: the approved mockup (review/calendar-2). Right: this PR. **devices** | | mockup | this PR | |---|---|---| | 1440 light | ![](https://git.kayg.org/attachments/a0f0bd1d-0910-42d3-974b-69f82a1fd685) | ![](https://git.kayg.org/attachments/ef28d47e-d6d9-488b-9e9a-44b814345525) | | 1440 dark | ![](https://git.kayg.org/attachments/dd430f0f-3e76-4838-9a09-7f4ff7a286b6) | ![](https://git.kayg.org/attachments/9f0dcf53-eaf2-48d8-996b-b57844cd0acd) | | 390 light | ![](https://git.kayg.org/attachments/2d384d62-431f-4c79-a783-c940aa388415) | ![](https://git.kayg.org/attachments/51caa5e3-2eee-4056-9679-3775b58e0812) | | 390 dark | ![](https://git.kayg.org/attachments/5cd1b70b-d600-432b-9019-03c3ff35f79b) | ![](https://git.kayg.org/attachments/b0061a53-2a7b-4d42-866f-4153e3d92321) | **revoke** | | mockup | this PR | |---|---|---| | 1440 light | ![](https://git.kayg.org/attachments/d1e5e454-1903-47ce-880d-3178191aa3ed) | ![](https://git.kayg.org/attachments/9b5f65a7-fb36-477f-8ff2-1bd8051c0d86) | | 1440 dark | ![](https://git.kayg.org/attachments/e5e67544-8ea2-4d07-9ed5-4a86131e532a) | ![](https://git.kayg.org/attachments/0c9d11fa-a773-4aa2-9c85-1cb6635576fb) | | 390 light | ![](https://git.kayg.org/attachments/8cb789fc-7181-47d7-aa28-2bc2d48841b3) | ![](https://git.kayg.org/attachments/17c83d30-b7e5-4325-836c-9e0e23e10c5e) | | 390 dark | ![](https://git.kayg.org/attachments/0f6d05ee-c19e-435c-b349-50d1af93cd71) | ![](https://git.kayg.org/attachments/0e588db9-9922-43c4-ba02-f868b2dc7d35) | ### Real Safari (Mac VM, 1x; the same fake answers `account.installations` and `account.app-passwords`, injected by a script in a copy of the dist because the page loads its lists once on mount) **devices** | | light | dark | |---|---|---| | 1440 | ![](https://git.kayg.org/attachments/9b069014-819f-4db8-b6e8-71a7d137f8c6) | ![](https://git.kayg.org/attachments/9d1a342f-547c-4c78-be5b-6dec3c6475d8) | | 390 | ![](https://git.kayg.org/attachments/a778d362-3c96-4ab9-8478-a63ebaa8d743) | ![](https://git.kayg.org/attachments/365dd0ee-15c0-42e7-ace1-c946333ff229) | **revoke** | | light | dark | |---|---|---| | 1440 | ![](https://git.kayg.org/attachments/3f044662-9dfb-4a5c-b24e-fd8c43656736) | ![](https://git.kayg.org/attachments/c7456166-6d77-437e-9209-f6314ee030a3) | | 390 | ![](https://git.kayg.org/attachments/a66c7ec2-e269-49ee-9ab5-047b80339c3e) | ![](https://git.kayg.org/attachments/bc2e2f47-4920-45c0-a6b9-e3f070c711de) |
Author
Owner

Restacked on dev d4b32af94, head 1432d20ef: READY apart from one perf run (queued behind Mail).

gate result
first paint JS vs dev (plain 228525 B / normalised 228128 B) 228447 B: -78 B / 228099 B: -29 B
first paint CSS vs 18126 B +0
specs, Chromium + WebKit, 1440 + 390 devices.spec: 12 passed, both engines
perf/web.sh --bundle-only --gate passes; devices line measured, total 518.7 KiB

Dev d4b32af94 itself fails 9 specs (palette preload:118 at 1440 and 390, glass:76, lazy-ui:70, preload:89 x3 at 390), on Chromium and WebKit; the same ones fail on this head and none is new.

**Restacked on dev `d4b32af94`, head `1432d20ef`: READY apart from one perf run (queued behind Mail).** | gate | result | |---|---| | first paint JS vs dev (plain 228525 B / normalised 228128 B) | **228447 B: -78 B** / 228099 B: -29 B | | first paint CSS vs 18126 B | +0 | | specs, Chromium + WebKit, 1440 + 390 | devices.spec: 12 passed, both engines | | `perf/web.sh --bundle-only --gate` | passes; devices line measured, total 518.7 KiB | Dev `d4b32af94` itself fails 9 specs (palette `preload:118` at 1440 and 390, `glass:76`, `lazy-ui:70`, `preload:89` x3 at 390), on Chromium and WebKit; the same ones fail on this head and none is new.
Author
Owner

Restacked on dev 661123e94, head cb251674d4ad7df041e8a0ddb4858e150f9da3c0. Not READY: first paint is +124 B over dev (plain 227125 B; normalised +79 B). CSS +0. Specs: devices 12 passed, both engines; gate passes (budget total 515.3 KiB). The overage is lazy becoming an export of lazy-ui.svelte.ts (the Devices loaders sit in their own module) and the relettering that follows; the #853 credit came off the baseline, so the -78 B of the last round is gone. Perf ABAB x5 is queued behind Mail. Dev 661123e94 itself fails the same nine specs as before (palette preload:118 at 1440 and 390, glass:76, lazy-ui:70, preload:89 x3), on both engines; none is new on this head.

**Restacked on dev `661123e94`, head `cb251674d4ad7df041e8a0ddb4858e150f9da3c0`. Not READY: first paint is +124 B over dev (plain 227125 B; normalised +79 B).** CSS +0. Specs: devices 12 passed, both engines; gate passes (budget total 515.3 KiB). The overage is `lazy` becoming an export of lazy-ui.svelte.ts (the Devices loaders sit in their own module) and the relettering that follows; the #853 credit came off the baseline, so the -78 B of the last round is gone. Perf ABAB x5 is queued behind Mail. Dev `661123e94` itself fails the same nine specs as before (palette `preload:118` at 1440 and 390, `glass:76`, `lazy-ui:70`, `preload:89` x3), on both engines; none is new on this head.
Author
Owner

READY FOR MERGE: Devices (head 36181444b)

Rebased on dev 9438918f3 (#848/#849 in), budgets regenerated from a fresh build of this head, bundle gate clean, specs on dev-02 (idle host) with calternal-h861, chromium and webkit at 1440 and 390 (preload, lazy-ui, lazy-offline, glass, shortcuts and this PR's own specs): 0 failed.

dev this PR
first paint plain / normalised 227531 / 227081 B 227543 / 227028 B (+12 / −53 B: at dev's level; normalised is under)
CSS 18234 18234
budget total 537.55 KiB 540.04 KiB (≤ 543.9; devices 8.38; replay and ContactsScreen lines follow the measured size)

148 passed, 44 skipped.

## READY FOR MERGE: Devices (head `36181444b`) Rebased on dev `9438918f3` (#848/#849 in), budgets regenerated from a fresh build of this head, bundle gate clean, specs on dev-02 (idle host) with `calternal-h861`, chromium and webkit at 1440 and 390 (preload, lazy-ui, lazy-offline, glass, shortcuts and this PR's own specs): **0 failed**. | | dev | this PR | |---|---|---| | first paint plain / normalised | 227531 / 227081 B | 227543 / 227028 B (**+12 / −53 B**: at dev's level; normalised is under) | | CSS | 18234 | 18234 | | budget total | 537.55 KiB | 540.04 KiB (≤ 543.9; devices 8.38; replay and ContactsScreen lines follow the measured size) | 148 passed, 44 skipped.
Author
Owner

READY FOR MERGE: Devices (head 852fac80b)

Merged dev fe2efbf11 (#1044) into the branch (it also merges cleanly with 4274dafb4, git merge-tree), budgets regenerated from a fresh build, bundle gate clean, specs on dev-02 with calternal-h861, chromium and webkit at 1440 and 390. Dev baseline on fe2efbf11: 228023 B plain / 227378 B normalised / CSS 18236.

first paint plain / normalised CSS budget total
227935 / 227397 B (−88 / +19 B: under dev) 18236 540.05 KiB (≤ 543.9)

Specs (preload, lazy-ui, lazy-offline, glass, shortcuts, devices): 136 passed, 36 skipped, 0 failed. Pixel and contrast specs (x86, dev-01): the same 6 phone-bars photo-dark specs fail on dev itself (9438918f3) and on this PR; nothing else.

## READY FOR MERGE: Devices (head `852fac80b`) Merged dev `fe2efbf11` (#1044) into the branch (it also merges cleanly with `4274dafb4`, `git merge-tree`), budgets regenerated from a fresh build, bundle gate clean, specs on dev-02 with `calternal-h861`, chromium and webkit at 1440 and 390. Dev baseline on `fe2efbf11`: 228023 B plain / 227378 B normalised / CSS 18236. | first paint plain / normalised | CSS | budget total | |---|---|---| | 227935 / 227397 B (**−88 / +19 B**: under dev) | 18236 | 540.05 KiB (≤ 543.9) | Specs (preload, lazy-ui, lazy-offline, glass, shortcuts, devices): 136 passed, 36 skipped, **0 failed**. Pixel and contrast specs (x86, dev-01): the same 6 `phone-bars` photo-dark specs fail on dev itself (`9438918f3`) and on this PR; nothing else.
Author
Owner

Handoff: head 852fac80b is pushed and READY as posted

Handoff (usage limit): the x86 pixel and contrast run on dev-01 (4 projects, 15 specs) was stopped before finishing this head; I released its job. Done: dev fe2efbf11 and the earlier heads (#902, #854, rows tip, #878 base) ran there with the same 6 phone-bars photo-dark failures on dev itself and nothing new. Next step: re-run px-d1b.sh-style pixel set on this head on dev-01 (about 1 h per head) if you want it before merging.

## Handoff: head `852fac80b` is pushed and READY as posted Handoff (usage limit): the x86 pixel and contrast run on dev-01 (4 projects, 15 specs) was stopped before finishing this head; I released its job. Done: dev `fe2efbf11` and the earlier heads (#902, #854, rows tip, #878 base) ran there with the same 6 `phone-bars` photo-dark failures on dev itself and nothing new. Next step: re-run `px-d1b.sh`-style pixel set on this head on dev-01 (about 1 h per head) if you want it before merging.
Some checks failed
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
CI / server (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
Merge conflict checking is in progress. Try again in few moments.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin port/devices:port/devices
git switch port/devices

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch dev
git merge --no-ff port/devices
git switch port/devices
git rebase dev
git switch dev
git merge --ff-only port/devices
git switch port/devices
git rebase dev
git switch dev
git merge --no-ff port/devices
git switch dev
git merge --squash port/devices
git switch dev
git merge --ff-only port/devices
git switch dev
git merge port/devices
git push origin dev
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal!902
No description provided.