Mockup: the person's card (D283) #224

Closed
opened 2026-10-15 12:16:54 +00:00 by kayg · 23 comments
Owner

Mockup of D283 (the person's card), first round. Flag: ?mock=1&proto=clusters&account=1 (works with &version=1).

Scope: pressing the person's pill in the sidebar footer opens the card inside the sidebar (shared sidebar card: one open at a time, Escape, close button, the pill again). All D283 fields laid out for the owner to judge: name (edit in place), username, email (optional), picture, organisation and role (EE), "Logs in as", sign-in health, sign out of this device. One amber mark on the pill when something needs attention (Q6). Username change (Q3) as a first sketch of a careful flow. Palette entries "Mockup: account – ..." open each state at once.

Branch mockup-account-card, based on mockup-version-pill e1665a9e5. Test double only; nothing is real. Evidence is attached below as it is produced.

Mockup of D283 (the person's card), first round. Flag: `?mock=1&proto=clusters&account=1` (works with `&version=1`). Scope: pressing the person's pill in the sidebar footer opens the card inside the sidebar (shared sidebar card: one open at a time, Escape, close button, the pill again). All D283 fields laid out for the owner to judge: name (edit in place), username, email (optional), picture, organisation and role (EE), "Logs in as", sign-in health, sign out of this device. One amber mark on the pill when something needs attention (Q6). Username change (Q3) as a first sketch of a careful flow. Palette entries "Mockup: account – ..." open each state at once. Branch `mockup-account-card`, based on mockup-version-pill e1665a9e5. Test double only; nothing is real. Evidence is attached below as it is produced.
Author
Owner

Finished first round. Branch mockup-account-card, based on mockup-version-pill e1665a9e5, head fe11ca505 (pushed).

Commits:

  • 585929ebb ui: the footer's avatar helpers are exported for the person's card
  • 8a89a5152 mockup(ui): the test double and the wording of the person's card
  • e86512ec1 ui: the window menu can be bounded by the screen
  • fe11ca505 mockup(ui): the person's card behind the person's pill

Flag: ?mock=1&proto=clusters&account=1 (also with &version=1).

Gates (TS half of scripts/verify iterate; the Rust half and the protocol compiler were not run, the checkout has no protocol node_modules and builds on the shared slots were starved):

  • tsc (apps/tracer): clean.
  • bun test (the gate's list): 270 pass, 1 fail. The failure, "an empty graph rests on its centre ... (#212 gap 4)", fails the same on the base commit e1665a9e5.
  • account wording tests: 10 pass.
  • Playwright test/account-card.spec.ts: 39 passed (6 size/theme combinations x 4 matrix tests, plus 15 behaviour tests).
  • bun run build + transfer-budget.ts: rc 0. JavaScript transfer, critical: identity=959522 gzip=297380/307200 (identical bytes to the base commit; production leaves the mockup out). Preview build: account module is on-demand (about 19 KB identity, 7 KB gzip, only with the flag).
  • Card open: first 9.6 to 11.6 ms, warm median 6.2 to 6.5 ms (limit 16 ms). Version panel in the same run: 7.0 ms.

Screenshots (1440x900, 393x852, 375x667; ink and paper): below.
card-community-desktop-ink.png

card-admin-desktop-paper.png

card-recovery-phone-paper.png

card-one-passkey-phone-ink.png

card-long-small-ink.png

pill-recovery-desktop-ink.png

username-3-valid-phone-ink.png

username-4-working-desktop-ink.png

username-5-done-small-paper.png

logs-in-as-open-desktop-ink.png

picture-menu-phone-ink.png

All 123 captures are attached to this issue (card-, pill-, username-1..5, logs-in-as-open, email-editing, email-set, name-editing, picture-menu; each at desktop, phone, small in ink and paper).

Finished first round. Branch `mockup-account-card`, based on mockup-version-pill e1665a9e5, head fe11ca505 (pushed). Commits: - 585929ebb ui: the footer's avatar helpers are exported for the person's card - 8a89a5152 mockup(ui): the test double and the wording of the person's card - e86512ec1 ui: the window menu can be bounded by the screen - fe11ca505 mockup(ui): the person's card behind the person's pill Flag: `?mock=1&proto=clusters&account=1` (also with `&version=1`). Gates (TS half of `scripts/verify iterate`; the Rust half and the protocol compiler were not run, the checkout has no protocol node_modules and builds on the shared slots were starved): - tsc (apps/tracer): clean. - `bun test` (the gate's list): 270 pass, 1 fail. The failure, "an empty graph rests on its centre ... (#212 gap 4)", fails the same on the base commit e1665a9e5. - account wording tests: 10 pass. - Playwright `test/account-card.spec.ts`: 39 passed (6 size/theme combinations x 4 matrix tests, plus 15 behaviour tests). - `bun run build` + `transfer-budget.ts`: rc 0. JavaScript transfer, critical: identity=959522 gzip=297380/307200 (identical bytes to the base commit; production leaves the mockup out). Preview build: account module is on-demand (about 19 KB identity, 7 KB gzip, only with the flag). - Card open: first 9.6 to 11.6 ms, warm median 6.2 to 6.5 ms (limit 16 ms). Version panel in the same run: 7.0 ms. Screenshots (1440x900, 393x852, 375x667; ink and paper): below. ![card-community-desktop-ink.png](https://git.kayg.org/attachments/28131f69-d931-4eee-94fc-66f7c12e722e) ![card-admin-desktop-paper.png](https://git.kayg.org/attachments/78a8a7d5-af31-449e-b208-a850c1613093) ![card-recovery-phone-paper.png](https://git.kayg.org/attachments/01d90840-aced-4c96-8290-7ac309d5c0a9) ![card-one-passkey-phone-ink.png](https://git.kayg.org/attachments/a4615b3c-3241-45ee-8c12-09f2b40edf7a) ![card-long-small-ink.png](https://git.kayg.org/attachments/5ec09154-d051-472e-b94e-7aca5bdcc82f) ![pill-recovery-desktop-ink.png](https://git.kayg.org/attachments/baa8e54d-06d4-48aa-adb1-39313cf9e16f) ![username-3-valid-phone-ink.png](https://git.kayg.org/attachments/dc0730a8-847e-498f-8d99-8488ed79c9e4) ![username-4-working-desktop-ink.png](https://git.kayg.org/attachments/d0cc73b0-12e7-44a2-b1c2-5ae5da733aca) ![username-5-done-small-paper.png](https://git.kayg.org/attachments/b5e28396-abb1-45b7-a917-828913449538) ![logs-in-as-open-desktop-ink.png](https://git.kayg.org/attachments/cfc18515-8626-4201-8448-241af3f13ef4) ![picture-menu-phone-ink.png](https://git.kayg.org/attachments/c08dde3e-e767-49ce-a516-bd7f2848397d) All 123 captures are attached to this issue (card-<scenario>, pill-<state>, username-1..5, logs-in-as-open, email-editing, email-set, name-editing, picture-menu; each at desktop, phone, small in ink and paper).
Author
Owner

Round 2: commits, break-ui report, numbers

Commits on mockup-account-card (all pushed; base fe11ca505):
c77c5c952 section headers breathe (token) / bb4e92894 pill grows into card (shared) / 73f23575d resizable sidebar (shared) / a94108152 kv fixes / 697390105 the card round 2 (username to Settings, name-only pill, Company row, Logs-in-as A/B/C, data toggle) / 61b31c8ef break-ui fixes and specs / 825040245 0 bytes critical / fc7674826 spec timing / c8315fbfb hint balance and group line height.

break-ui, part 1: what broke (all fixed, kept as the regression fixture)

# Severity Field Worst-case value What happened Fix
1 Broken host chip in the Logs-in-as pane build-agent-eu-central-1a-ephemeral-0042.internal.example.com pane 525 px wide in a 292 px sidebar (grid column auto) minmax(0,1fr) in account.css .cl-al-group; chips ellipsize with tooltip
2 Broken hint under a key checked 14 months ago inherited the key's no-wrap, starved the value: "Saved" became "Sa / ve / d" at 200 px packages/ui/styles.css .ui-kv-row column fit-content(55%), hint wraps and balances
3 Broken value cell a nowrap chip cell grew past its column and covered the key "Logs in as" .ui-kv-cell { max-width: 100% }
4 Broken name, at the old 200 px floor Aleksandra Wiśniewska-Kowalczyk heading column under 70 px, name in five fragments floor is 248 px (sidebar-resize.ts)
5 Ugly person's pill and version pill name + 0.12.104-rc.3 text spilled over the neighbouring pill pill has a minimum and clips; version ends in an ellipsis (version.css)
6 Ugly card body 12 accounts, 74-char company ended at a hard line mid-row scroll fade on the edge with more behind it (scroll-fade.ts)
7 Ugly licence section title 74-char holder wrapped lines touching (line-height set before the font shorthand) order fixed in .ui-kv-group
8 Ugly chips 32-char account name chip name collapsed to one letter ("k… 12") name min 4ch, then wraps to its own line
9 Ugly avatar and pill, empty name "" "?" avatar; pill empty shownName(): the username, once
10 Ugly devices 0 green tick on zero no mark for zero
11 Ugly people 1284 of 1500 no separator toLocaleString
12 Fragile hosts pane 40 hosts, one 63 characters forty rows; name squeezed to 40 px by its versions first 8 and "and 32 more"; versions drop below a long name
13 Fragile Logs-in-as pane 1,202 hosts on one account one chip per host 24 chips, then "+1,178 more"
14 Fragile name, email, company, version no limit found in the double or the contract unbounded tested at 31, 95, 74 and 13 characters; limit left to the server (username 32 is the rule in checkUsername)
15 Fragile dragged sidebar custom property on the stage, then on the sidebar restyled every window (24 ms per frame), then the whole sidebar plain width on the sidebar; the property only on the toggle

Part 2: decisions for you

  • Floor 248 px, ceiling half the screen up to 560 px. The footer alone fits in 200; the card sets the floor (a readable heading column and a key beside its value). Say if you want 200 back and a stacked card below 248.
  • Names and emails wrap in full (they identify); account chips, host chips and the version in the pill truncate with the full text on hover.
  • Zero hosts reads "No hosts yet" and is not a link; zero devices has no mark; an empty name shows the username.
  • The username is not a row in the card (D283 Q2 says rows jump to Settings; you asked for it only under the name). Say if you want a "Username" row that jumps to Settings, Account.
  • Logs in as: I recommend variant A (chips). It is the only one that says which accounts at a glance, it is one line in every ordinary case (1 to 3 accounts), it never wraps inside an item, and named things are chips. Its cost is height in the worst case (four chips stacked at 292 px). B ("Accounts 12 hosts", each host counted once) is the most stable fallback; C ("kayg and 1 more") wraps badly when the main account name is long.

Part 3: what held up

The pill's heights (40 and 44 px, three equal) in every data set and width; the card inside the sidebar and above the footer at 248, 292, 424, 560, at 200% zoom and on 393, 375 and 320 px phones, ink and paper (64 cases in footer-cards-break.spec.ts); email and company wrapping right-aligned; Escape and rings; the amber mark; the morph retargeting when a press arrives mid-flight; the username flow at a 32-character name; one key per card in every state.

Numbers

  • Production build, this branch against fe11ca505: critical identity 959,522 and 959,522 (0 bytes; the two chunks that differ are the same length: embedded build commit and date). Gzip 297,375 against 297,368 and brotli 247,552 against 247,614 are build-hash noise. Deferred gzip 49,521 against 49,519 (limit 51,200). On demand, largest chunk 15,951 gzip (limit 20,480). The mockup's modules are left out of the production bundle. ui.css +322 B (85,177 to 85,499: the header token and three key/value fixes); the proposed pieces' styles (4.4 kB) are in proposed.css, loaded with the mockup only.
  • Card open, warm: median 9.2 ms (account card, min 6.5, max 10.1), 7.5 ms (version panel); first open 12.7 and 15.5 ms; load average 6 to 13 (shared host, headless software rendering).
  • Pill morph settles in 193 ms (account) and 196 ms (version), transform and opacity only; keyboard: no motion; reduced motion: no transform in any frame.
  • Sidebar drag, 210 frames, load average 13: frame p50 16.8 ms, p95 47.6 ms, max 160.7 ms. Per frame on the main thread: 7.4 ms, of which style 2.7, layout 0.6, script 1.0. The p95 of 16.7 ms is not met on this host: the idle page measures p95 25.8 ms at load 7, and the headless run draws the sidebar's glass blur in software (without the blur, p95 24 to 28). A real GPU, on a quiet host, is the measurement that counts; I could not take it here.
  • Gates: tsc clean; unit tests 88 in the mock and ui packages; the TS half of scripts/verify iterate (checksums, protocol tsc, tracer tsc, 274 bun tests, production build, transfer budget): all pass except one test that fails the same way on fe11ca505 ("an empty graph rests on its centre", #212 gap 4). I did not run the Rust half or the protocol differential test. Specs: account-card (48 cases), footer-cards-break (64), sidebar-resize (8), settings-section-spacing (2), version-pill (50), inbox: all pass in the last runs (two timing races were fixed in the specs, see fc7674826).

Not verified

Real iPhone and real GPU frame times; Safari and Firefox; RTL (not checked: the product has none); a real server. The 1,202-host account is a double, not a measured server.

## Round 2: commits, break-ui report, numbers Commits on `mockup-account-card` (all pushed; base fe11ca505): c77c5c952 section headers breathe (token) / bb4e92894 pill grows into card (shared) / 73f23575d resizable sidebar (shared) / a94108152 kv fixes / 697390105 the card round 2 (username to Settings, name-only pill, Company row, Logs-in-as A/B/C, data toggle) / 61b31c8ef break-ui fixes and specs / 825040245 0 bytes critical / fc7674826 spec timing / c8315fbfb hint balance and group line height. ### break-ui, part 1: what broke (all fixed, kept as the regression fixture) | # | Severity | Field | Worst-case value | What happened | Fix | |---|---|---|---|---|---| | 1 | Broken | host chip in the Logs-in-as pane | `build-agent-eu-central-1a-ephemeral-0042.internal.example.com` | pane 525 px wide in a 292 px sidebar (grid column auto) | `minmax(0,1fr)` in `account.css` `.cl-al-group`; chips ellipsize with tooltip | | 2 | Broken | hint under a key | `checked 14 months ago` | inherited the key's no-wrap, starved the value: "Saved" became "Sa / ve / d" at 200 px | `packages/ui/styles.css` `.ui-kv-row` column `fit-content(55%)`, hint wraps and balances | | 3 | Broken | value cell | a nowrap chip | cell grew past its column and covered the key "Logs in as" | `.ui-kv-cell { max-width: 100% }` | | 4 | Broken | name, at the old 200 px floor | `Aleksandra Wiśniewska-Kowalczyk` | heading column under 70 px, name in five fragments | floor is 248 px (`sidebar-resize.ts`) | | 5 | Ugly | person's pill and version pill | name + `0.12.104-rc.3` | text spilled over the neighbouring pill | pill has a minimum and clips; version ends in an ellipsis (`version.css`) | | 6 | Ugly | card body | 12 accounts, 74-char company | ended at a hard line mid-row | scroll fade on the edge with more behind it (`scroll-fade.ts`) | | 7 | Ugly | licence section title | 74-char holder | wrapped lines touching (line-height set before the `font` shorthand) | order fixed in `.ui-kv-group` | | 8 | Ugly | chips | 32-char account name | chip name collapsed to one letter ("k… 12") | name min 4ch, then wraps to its own line | | 9 | Ugly | avatar and pill, empty name | `""` | "?" avatar; pill empty | `shownName()`: the username, once | | 10 | Ugly | devices | 0 | green tick on zero | no mark for zero | | 11 | Ugly | people | 1284 of 1500 | no separator | `toLocaleString` | | 12 | Fragile | hosts pane | 40 hosts, one 63 characters | forty rows; name squeezed to 40 px by its versions | first 8 and "and 32 more"; versions drop below a long name | | 13 | Fragile | Logs-in-as pane | 1,202 hosts on one account | one chip per host | 24 chips, then "+1,178 more" | | 14 | Fragile | name, email, company, version | no limit found in the double or the contract | unbounded | tested at 31, 95, 74 and 13 characters; limit left to the server (username 32 is the rule in `checkUsername`) | | 15 | Fragile | dragged sidebar | custom property on the stage, then on the sidebar | restyled every window (24 ms per frame), then the whole sidebar | plain `width` on the sidebar; the property only on the toggle | ### Part 2: decisions for you - Floor 248 px, ceiling half the screen up to 560 px. The footer alone fits in 200; the card sets the floor (a readable heading column and a key beside its value). Say if you want 200 back and a stacked card below 248. - Names and emails wrap in full (they identify); account chips, host chips and the version in the pill truncate with the full text on hover. - Zero hosts reads "No hosts yet" and is not a link; zero devices has no mark; an empty name shows the username. - The username is not a row in the card (D283 Q2 says rows jump to Settings; you asked for it only under the name). Say if you want a "Username" row that jumps to Settings, Account. - Logs in as: I recommend **variant A** (chips). It is the only one that says which accounts at a glance, it is one line in every ordinary case (1 to 3 accounts), it never wraps inside an item, and named things are chips. Its cost is height in the worst case (four chips stacked at 292 px). B ("Accounts 12 hosts", each host counted once) is the most stable fallback; C ("kayg and 1 more") wraps badly when the main account name is long. ### Part 3: what held up The pill's heights (40 and 44 px, three equal) in every data set and width; the card inside the sidebar and above the footer at 248, 292, 424, 560, at 200% zoom and on 393, 375 and 320 px phones, ink and paper (64 cases in `footer-cards-break.spec.ts`); email and company wrapping right-aligned; Escape and rings; the amber mark; the morph retargeting when a press arrives mid-flight; the username flow at a 32-character name; one key per card in every state. ### Numbers - Production build, this branch against fe11ca505: critical identity 959,522 and 959,522 (0 bytes; the two chunks that differ are the same length: embedded build commit and date). Gzip 297,375 against 297,368 and brotli 247,552 against 247,614 are build-hash noise. Deferred gzip 49,521 against 49,519 (limit 51,200). On demand, largest chunk 15,951 gzip (limit 20,480). The mockup's modules are left out of the production bundle. `ui.css` +322 B (85,177 to 85,499: the header token and three key/value fixes); the proposed pieces' styles (4.4 kB) are in `proposed.css`, loaded with the mockup only. - Card open, warm: median 9.2 ms (account card, min 6.5, max 10.1), 7.5 ms (version panel); first open 12.7 and 15.5 ms; load average 6 to 13 (shared host, headless software rendering). - Pill morph settles in 193 ms (account) and 196 ms (version), transform and opacity only; keyboard: no motion; reduced motion: no transform in any frame. - Sidebar drag, 210 frames, load average 13: frame p50 16.8 ms, p95 47.6 ms, max 160.7 ms. Per frame on the main thread: 7.4 ms, of which style 2.7, layout 0.6, script 1.0. **The p95 of 16.7 ms is not met on this host**: the idle page measures p95 25.8 ms at load 7, and the headless run draws the sidebar's glass blur in software (without the blur, p95 24 to 28). A real GPU, on a quiet host, is the measurement that counts; I could not take it here. - Gates: `tsc` clean; unit tests 88 in the mock and ui packages; the TS half of `scripts/verify iterate` (checksums, protocol tsc, tracer tsc, 274 bun tests, production build, transfer budget): all pass except one test that fails the same way on fe11ca505 ("an empty graph rests on its centre", #212 gap 4). I did not run the Rust half or the protocol differential test. Specs: account-card (48 cases), footer-cards-break (64), sidebar-resize (8), settings-section-spacing (2), version-pill (50), inbox: all pass in the last runs (two timing races were fixed in the specs, see fc7674826). ### Not verified Real iPhone and real GPU frame times; Safari and Firefox; RTL (not checked: the product has none); a real server. The 1,202-host account is a double, not a measured server.
Author
Owner

Round 2 of the person's card: what changed, what broke, what holds

Branch mockup-account-card, head c8315fbfb. Mockup only (?mock=1&proto=clusters&account=1&version=1; ?data=worst|empty|one, ?logins=a|b|c, ?resize=0). Nothing deployed, nothing merged.

Videos

Settings: section headers breathe (before / after)

settings-spacing-before-desktop-ink.png
settings-spacing-before-desktop-paper.png
settings-spacing-after-ink.png
settings-spacing-after-paper.png

Sidebar widths (floor 248, default 292, ceiling 560)

sidebar-width-account-default-desktop-ink.png
sidebar-width-account-default-desktop-paper.png
sidebar-width-account-min-desktop-ink.png
sidebar-width-account-min-desktop-paper.png
sidebar-width-account-wide-desktop-ink.png
sidebar-width-account-wide-desktop-paper.png

Logs in as, variants A / B / C (left to right), per data set

Sheets for desktop, 393x852 and 375x667, ink and paper: logins-variants-demo-desktop-ink.png, logins-variants-demo-desktop-paper.png, logins-variants-demo-phone-ink.png, logins-variants-demo-phone-paper.png, logins-variants-demo-small-ink.png, logins-variants-demo-small-paper.png, logins-variants-empty-desktop-ink.png, logins-variants-empty-desktop-paper.png, logins-variants-empty-phone-ink.png, logins-variants-empty-phone-paper.png, logins-variants-empty-small-ink.png, logins-variants-empty-small-paper.png, logins-variants-one-desktop-ink.png, logins-variants-one-desktop-paper.png, logins-variants-one-phone-ink.png, logins-variants-one-phone-paper.png, logins-variants-one-small-ink.png, logins-variants-one-small-paper.png, logins-variants-worst-desktop-ink.png, logins-variants-worst-desktop-paper.png, logins-variants-worst-phone-ink.png, logins-variants-worst-phone-paper.png, logins-variants-worst-small-ink.png, logins-variants-worst-small-paper.png

The data toggle at the real widths (Demo / Worst case / Empty / One, left to right)

sheet-account-ceiling-560-ink.png
sheet-account-ceiling-560-paper.png
sheet-account-default-292-ink.png
sheet-account-default-292-paper.png
sheet-account-floor-248-ink.png
sheet-account-floor-248-paper.png
sheet-version-ceiling-560-ink.png
sheet-version-ceiling-560-paper.png
sheet-version-default-292-ink.png
sheet-version-default-292-paper.png
sheet-version-floor-248-ink.png
sheet-version-floor-248-paper.png

Pill-to-card morph frames (0, 40, 80, 130, 200 ms)

morph-account-open-0ms-desktop-ink.png, morph-account-open-130ms-desktop-ink.png, morph-account-open-200ms-desktop-ink.png, morph-account-open-40ms-desktop-ink.png, morph-account-open-80ms-desktop-ink.png, morph-account-open-0ms-desktop-paper.png, morph-account-open-130ms-desktop-paper.png, morph-account-open-200ms-desktop-paper.png, morph-account-open-40ms-desktop-paper.png, morph-account-open-80ms-desktop-paper.png, morph-version-open-0ms-desktop-ink.png, morph-version-open-130ms-desktop-ink.png, morph-version-open-200ms-desktop-ink.png, morph-version-open-40ms-desktop-ink.png, morph-version-open-80ms-desktop-ink.png, morph-account-open-0ms-phone-ink.png, morph-account-open-130ms-phone-ink.png, morph-account-open-200ms-phone-ink.png, morph-account-open-40ms-phone-ink.png, morph-account-open-80ms-phone-ink.png

Every spec capture (3 sizes, ink and paper): cards, Settings, Account, the username flow
## Round 2 of the person's card: what changed, what broke, what holds Branch `mockup-account-card`, head c8315fbfb. Mockup only (`?mock=1&proto=clusters&account=1&version=1`; `?data=worst|empty|one`, `?logins=a|b|c`, `?resize=0`). Nothing deployed, nothing merged. ### Videos - [pill-to-card-morph-real-speed.webm](https://git.kayg.org/attachments/3fd06ebf-af1e-480d-bc79-49d1aae97fb1) - [pill-to-card-morph-slow-0.2x.webm](https://git.kayg.org/attachments/49c9d754-0a8d-469f-b44b-0441e5f678b0) - [sidebar-drag.webm](https://git.kayg.org/attachments/199e4fd9-ceb2-4f12-86a8-4e49ba478657) ### Settings: section headers breathe (before / after) ![settings-spacing-before-desktop-ink.png](https://git.kayg.org/attachments/39b67943-3545-49a8-9ba2-53377d73b1d6) ![settings-spacing-before-desktop-paper.png](https://git.kayg.org/attachments/244a945c-719a-44c4-a113-9fddb24f9694) ![settings-spacing-after-ink.png](https://git.kayg.org/attachments/fdbc9d32-4dab-4aca-a815-c244e54f17fd) ![settings-spacing-after-paper.png](https://git.kayg.org/attachments/a5180d29-732b-468b-9c34-0f49b4417ed4) ### Sidebar widths (floor 248, default 292, ceiling 560) ![sidebar-width-account-default-desktop-ink.png](https://git.kayg.org/attachments/831fb690-ebf8-492f-9af6-acd8cf38365b) ![sidebar-width-account-default-desktop-paper.png](https://git.kayg.org/attachments/02a820b1-251f-493a-a344-8e4b63988c6b) ![sidebar-width-account-min-desktop-ink.png](https://git.kayg.org/attachments/653c1c14-4dc4-4c28-95af-b93cd04ef37f) ![sidebar-width-account-min-desktop-paper.png](https://git.kayg.org/attachments/9c76c5af-28e6-4cf8-a4f6-7dd7e070c0fe) ![sidebar-width-account-wide-desktop-ink.png](https://git.kayg.org/attachments/eaa65c01-5a23-4b40-b3d3-ab0d8d1aa1d9) ![sidebar-width-account-wide-desktop-paper.png](https://git.kayg.org/attachments/fb6ed98c-f868-4a7c-b5fe-0935432e26dc) ### Logs in as, variants A / B / C (left to right), per data set Sheets for desktop, 393x852 and 375x667, ink and paper: [logins-variants-demo-desktop-ink.png](https://git.kayg.org/attachments/c0a72c6d-b333-4800-b6a7-5a0df8510091), [logins-variants-demo-desktop-paper.png](https://git.kayg.org/attachments/25014802-37c6-4274-b8e9-2d4c23c5791e), [logins-variants-demo-phone-ink.png](https://git.kayg.org/attachments/35155a88-3671-4a49-ad8e-9eb3882c066f), [logins-variants-demo-phone-paper.png](https://git.kayg.org/attachments/5ea6349f-6bd0-4e67-8d5a-791e93bf6b3b), [logins-variants-demo-small-ink.png](https://git.kayg.org/attachments/f31eb36e-d334-47ac-a8e4-9afaa96b5d31), [logins-variants-demo-small-paper.png](https://git.kayg.org/attachments/65d861e6-24a2-4d1d-a586-a3232f0b531b), [logins-variants-empty-desktop-ink.png](https://git.kayg.org/attachments/da3ff32f-4b77-46c3-b119-aa3b01f81773), [logins-variants-empty-desktop-paper.png](https://git.kayg.org/attachments/04f1a6dc-945f-485f-9561-bfb44740d57e), [logins-variants-empty-phone-ink.png](https://git.kayg.org/attachments/240a0497-c5d6-48be-add8-bc3341c84397), [logins-variants-empty-phone-paper.png](https://git.kayg.org/attachments/6880d31e-df62-4ce0-a469-a0ab1ae42751), [logins-variants-empty-small-ink.png](https://git.kayg.org/attachments/c869d6fb-3451-4045-9187-7734fba82119), [logins-variants-empty-small-paper.png](https://git.kayg.org/attachments/e168124a-3edf-4e7e-b82c-a319eeeda35f), [logins-variants-one-desktop-ink.png](https://git.kayg.org/attachments/8ec69e0e-c3ad-4796-820a-4c53ffa9ebe5), [logins-variants-one-desktop-paper.png](https://git.kayg.org/attachments/c75f6bff-e815-4396-b5e2-ce61c50546a9), [logins-variants-one-phone-ink.png](https://git.kayg.org/attachments/3f4b33b2-79a1-4272-b6a0-a18924ca4d4a), [logins-variants-one-phone-paper.png](https://git.kayg.org/attachments/b2083d7e-7009-45b2-ab6f-f73d88b4eddf), [logins-variants-one-small-ink.png](https://git.kayg.org/attachments/6467fd59-da04-419e-ba8e-631f93a70d34), [logins-variants-one-small-paper.png](https://git.kayg.org/attachments/cdae7e9f-42c9-42af-87b0-166f2bfb853c), [logins-variants-worst-desktop-ink.png](https://git.kayg.org/attachments/a9edcb5d-f86e-4abd-9486-400e998a6bf4), [logins-variants-worst-desktop-paper.png](https://git.kayg.org/attachments/1dd1b1c9-6441-437b-87c5-6d99f01c0692), [logins-variants-worst-phone-ink.png](https://git.kayg.org/attachments/5b33a08f-0703-4679-b711-767d620f3c7e), [logins-variants-worst-phone-paper.png](https://git.kayg.org/attachments/52ceff7f-c5fd-4612-a3cd-cd5d49277127), [logins-variants-worst-small-ink.png](https://git.kayg.org/attachments/b5d947bf-d136-46a0-bafd-74ccd8e355ec), [logins-variants-worst-small-paper.png](https://git.kayg.org/attachments/89358ed3-3399-4fb0-ad80-da09cbec13da) ### The data toggle at the real widths (Demo / Worst case / Empty / One, left to right) [sheet-account-ceiling-560-ink.png](https://git.kayg.org/attachments/a7487dca-082a-457b-83f0-91dd14c9426a) [sheet-account-ceiling-560-paper.png](https://git.kayg.org/attachments/fd539351-7564-424e-9a3f-4820769108c3) [sheet-account-default-292-ink.png](https://git.kayg.org/attachments/93c02d9f-e154-4ca0-aab1-8c3b1422ba0c) [sheet-account-default-292-paper.png](https://git.kayg.org/attachments/7cf4d6e6-4c07-4ac8-b758-f3bd75440652) [sheet-account-floor-248-ink.png](https://git.kayg.org/attachments/5918e779-7d9b-4885-bd2a-fe25c24e5436) [sheet-account-floor-248-paper.png](https://git.kayg.org/attachments/c1beaf34-c207-4af7-92fe-11c3c415c64d) [sheet-version-ceiling-560-ink.png](https://git.kayg.org/attachments/ab7395d1-2bcc-419e-828c-9ef74b8d5cbe) [sheet-version-ceiling-560-paper.png](https://git.kayg.org/attachments/889c3dd9-4bba-4454-a1de-7ceeae79f1db) [sheet-version-default-292-ink.png](https://git.kayg.org/attachments/bc963b3d-77e1-4282-bb07-defc07812b6d) [sheet-version-default-292-paper.png](https://git.kayg.org/attachments/64765c9e-884e-4e65-b8a0-374d9ac82733) [sheet-version-floor-248-ink.png](https://git.kayg.org/attachments/4ba2494d-9be1-495c-8a89-2c66d2010e34) [sheet-version-floor-248-paper.png](https://git.kayg.org/attachments/3d3f495b-5a3b-462a-8339-8e1240dc119b) ### Pill-to-card morph frames (0, 40, 80, 130, 200 ms) [morph-account-open-0ms-desktop-ink.png](https://git.kayg.org/attachments/35fd6fee-212a-4b2e-a9e4-2966ea73f004), [morph-account-open-130ms-desktop-ink.png](https://git.kayg.org/attachments/f317e461-fb0e-4864-8c40-bec166d63957), [morph-account-open-200ms-desktop-ink.png](https://git.kayg.org/attachments/7cd9cffe-1675-450e-b4dc-d8b2b3c1d259), [morph-account-open-40ms-desktop-ink.png](https://git.kayg.org/attachments/4f24fe00-03f5-4cf8-b079-c47c8bb79c63), [morph-account-open-80ms-desktop-ink.png](https://git.kayg.org/attachments/bb3e19b9-c6f4-437a-b959-eb930bd0eb69), [morph-account-open-0ms-desktop-paper.png](https://git.kayg.org/attachments/410cb6e4-ed3d-4a2e-b1aa-15fc24480a5f), [morph-account-open-130ms-desktop-paper.png](https://git.kayg.org/attachments/31760e1e-534c-462e-8910-5626cae536e9), [morph-account-open-200ms-desktop-paper.png](https://git.kayg.org/attachments/fb7518cf-817b-4a4b-8c3b-8e59862390f6), [morph-account-open-40ms-desktop-paper.png](https://git.kayg.org/attachments/93c34680-a28e-4c60-aa2d-d9a4f5784d1c), [morph-account-open-80ms-desktop-paper.png](https://git.kayg.org/attachments/b9662ae2-3703-4d01-82c3-e8a32c0af262), [morph-version-open-0ms-desktop-ink.png](https://git.kayg.org/attachments/92a39852-e4de-4891-ab3e-ddfe180b7b12), [morph-version-open-130ms-desktop-ink.png](https://git.kayg.org/attachments/5d8ee417-ee2c-4e8f-9c36-f73ae8f72a35), [morph-version-open-200ms-desktop-ink.png](https://git.kayg.org/attachments/c0815bd4-4f2c-4fcd-a1c1-5b5060645a39), [morph-version-open-40ms-desktop-ink.png](https://git.kayg.org/attachments/ae69436e-ee94-49c5-a1db-4da738726fb4), [morph-version-open-80ms-desktop-ink.png](https://git.kayg.org/attachments/e263f1b4-0eaa-4871-b14b-0ebba7ac09ea), [morph-account-open-0ms-phone-ink.png](https://git.kayg.org/attachments/ea06f9aa-6808-4712-b884-c3d650c95057), [morph-account-open-130ms-phone-ink.png](https://git.kayg.org/attachments/c37fc3de-d25a-4039-916b-b2b15373f1a0), [morph-account-open-200ms-phone-ink.png](https://git.kayg.org/attachments/adc8153b-7dc9-4867-a48f-42119f229ff4), [morph-account-open-40ms-phone-ink.png](https://git.kayg.org/attachments/848f91de-3f05-4d13-b7b4-271c63d6ceb4), [morph-account-open-80ms-phone-ink.png](https://git.kayg.org/attachments/da9b803b-1aa8-4617-b969-8e522372ff35) <details><summary>Every spec capture (3 sizes, ink and paper): cards, Settings, Account, the username flow</summary> - [foot-worst-248.png](https://git.kayg.org/attachments/fa2c400d-7e1f-4e1d-aba6-07e4e11abc28) - [card-admin-desktop-ink.png](https://git.kayg.org/attachments/3976495f-f3ea-40e1-8ad0-956b2aa35f4e) - [card-admin-desktop-paper.png](https://git.kayg.org/attachments/cdb99cc1-a1b2-4f43-ba31-117c7cefcff1) - [card-admin-phone-ink.png](https://git.kayg.org/attachments/c05c2841-c48a-4539-ad00-822dbba32aba) - [card-admin-phone-paper.png](https://git.kayg.org/attachments/ea9ed264-1b55-4596-904b-7ef06b955220) - [card-admin-small-ink.png](https://git.kayg.org/attachments/948ed85a-f2c9-4162-9214-80cefe2cd1c0) - [card-admin-small-paper.png](https://git.kayg.org/attachments/92b1f53d-a037-4ca8-9e56-d6e751c85c7c) - [card-community-desktop-ink.png](https://git.kayg.org/attachments/2ab31bbc-bad1-4cf8-8bdd-5ac5cd6d23db) - [card-community-desktop-paper.png](https://git.kayg.org/attachments/bf9d1f79-939b-4e52-8887-2aabc46c3c7c) - [card-community-phone-ink.png](https://git.kayg.org/attachments/6541cf40-39cb-40cf-b102-9c316928cc41) - [card-community-phone-paper.png](https://git.kayg.org/attachments/222890e0-a9be-47bc-82fd-66439ba84bf1) - [card-community-small-ink.png](https://git.kayg.org/attachments/66d32e25-cd52-4013-81fd-597c35d2d355) - [card-community-small-paper.png](https://git.kayg.org/attachments/f3d169c7-128b-44d6-b3de-405ed53afdfd) - [card-long-desktop-ink.png](https://git.kayg.org/attachments/b6878f65-3576-4bfd-ae9b-0cd20b89d2e0) - [card-long-desktop-paper.png](https://git.kayg.org/attachments/54847537-612a-406f-b05d-02204d28961d) - [card-long-phone-ink.png](https://git.kayg.org/attachments/d1658cc1-8daf-4ed2-b3c1-015b720b097d) - [card-long-phone-paper.png](https://git.kayg.org/attachments/ba8e8c3b-7e64-4b32-baf9-196cadd41f97) - [card-long-small-ink.png](https://git.kayg.org/attachments/8fc8bd53-8d6e-4a61-8344-24deec7b3fda) - [card-long-small-paper.png](https://git.kayg.org/attachments/581b39c3-32e6-45b4-864b-456118fa3d70) - [card-member-desktop-ink.png](https://git.kayg.org/attachments/92111765-9493-443f-9a6b-a165976fda32) - [card-member-desktop-paper.png](https://git.kayg.org/attachments/f0d260b2-b373-4427-b0d8-700c157a6667) - [card-member-phone-ink.png](https://git.kayg.org/attachments/ff231123-364b-435b-a7ec-67aa3890f2ac) - [card-member-phone-paper.png](https://git.kayg.org/attachments/07090aa0-0655-4eb4-8e14-53ee7758d794) - [card-member-small-ink.png](https://git.kayg.org/attachments/e229b34f-b754-410a-9c9a-aca681f12e33) - [card-member-small-paper.png](https://git.kayg.org/attachments/c6224412-f9b8-43fb-844e-194e982cecbf) - [card-one-passkey-desktop-ink.png](https://git.kayg.org/attachments/44633002-46f6-440d-95de-8c0693c3c2f8) - [card-one-passkey-desktop-paper.png](https://git.kayg.org/attachments/b5e3f5f5-3dc9-4b73-83f4-c9aff0d136dc) - [card-one-passkey-phone-ink.png](https://git.kayg.org/attachments/314e1e3a-7849-44cb-8fad-581b4e163f48) - [card-one-passkey-phone-paper.png](https://git.kayg.org/attachments/5818abe5-cd4d-46b4-84da-9a794f55690c) - [card-one-passkey-small-ink.png](https://git.kayg.org/attachments/c8d3697d-d9d1-4f04-9699-f848d28c4d4e) - [card-one-passkey-small-paper.png](https://git.kayg.org/attachments/8abd4bb1-5d46-453c-8a5b-3f0505f5f301) - [card-recovery-desktop-ink.png](https://git.kayg.org/attachments/09363ad0-f189-4034-8a4d-8f5b608c746b) - [card-recovery-desktop-paper.png](https://git.kayg.org/attachments/8496c9b9-3d65-402b-86ec-8740904bea48) - [card-recovery-phone-ink.png](https://git.kayg.org/attachments/4f5c11fe-14de-4faa-9b3a-9e63e4a83e2c) - [card-recovery-phone-paper.png](https://git.kayg.org/attachments/ad7efba9-2fb6-479e-8f69-6a7714bfcee7) - [card-recovery-small-ink.png](https://git.kayg.org/attachments/48e42754-c987-4bc7-8c54-d39865895f23) - [card-recovery-small-paper.png](https://git.kayg.org/attachments/c3f63c70-cbee-4140-a6cd-f71f253b4782) - [email-editing-desktop-ink.png](https://git.kayg.org/attachments/a2f90782-1e81-4db9-a49d-b45dcf37e7c8) - [email-editing-desktop-paper.png](https://git.kayg.org/attachments/ca5a7934-f80b-4458-a3f2-a21fc4717312) - [email-editing-phone-ink.png](https://git.kayg.org/attachments/30606201-f453-4215-95d5-bb475b30c295) - [email-editing-phone-paper.png](https://git.kayg.org/attachments/e2037465-0168-4859-880b-a418f448d4e6) - [email-editing-small-ink.png](https://git.kayg.org/attachments/07eefdc6-a5f9-4535-b26a-426d8c2f3315) - [email-editing-small-paper.png](https://git.kayg.org/attachments/3267e9bc-3a8e-44a9-95f0-e13e111d1610) - [email-set-desktop-ink.png](https://git.kayg.org/attachments/74cf0436-ee59-45ae-aa77-0a759c33aa60) - [email-set-desktop-paper.png](https://git.kayg.org/attachments/54f63515-a8a7-4082-b826-4a3f3e83c8f0) - [email-set-phone-ink.png](https://git.kayg.org/attachments/43d81e8c-d7e6-4099-b813-4ebecfefec35) - [email-set-phone-paper.png](https://git.kayg.org/attachments/7d311c18-8c18-45ac-8c91-50a6ce9f686b) - [email-set-small-ink.png](https://git.kayg.org/attachments/9442c230-0f02-41a9-9aba-53de5da7862d) - [email-set-small-paper.png](https://git.kayg.org/attachments/b1564b22-292f-412c-863d-1b2caf2d2c75) - [logs-in-as-open-desktop-ink.png](https://git.kayg.org/attachments/03d82189-223b-4948-8a2f-29f67242c6e1) - [logs-in-as-open-desktop-paper.png](https://git.kayg.org/attachments/f265ba10-e995-4380-b5b5-0e8050c60647) - [logs-in-as-open-phone-ink.png](https://git.kayg.org/attachments/949401fa-1796-4400-a054-5c9a7e2ef685) - [logs-in-as-open-phone-paper.png](https://git.kayg.org/attachments/3ffff0c7-3fc8-4996-be80-aaefbc5e799f) - [logs-in-as-open-small-ink.png](https://git.kayg.org/attachments/a7a97102-9663-426e-9888-1e0888d8dbe3) - [logs-in-as-open-small-paper.png](https://git.kayg.org/attachments/da83cd4b-22db-464c-b979-b6fad1f489a0) - [name-editing-desktop-ink.png](https://git.kayg.org/attachments/b5554a1b-6d6d-40ad-a02b-fe839a007af1) - [name-editing-desktop-paper.png](https://git.kayg.org/attachments/9d56d190-83aa-4c25-8745-c35d7f795446) - [name-editing-phone-ink.png](https://git.kayg.org/attachments/01cae534-0001-4de1-a212-d38286559c6e) - [name-editing-phone-paper.png](https://git.kayg.org/attachments/66646aee-92a0-48ed-b16e-bae1c4252c60) - [name-editing-small-ink.png](https://git.kayg.org/attachments/cd0dd153-4689-4a6e-8bb3-77b69ad94568) - [name-editing-small-paper.png](https://git.kayg.org/attachments/6f83b581-6322-4b86-b3e9-9eae1b791c93) - [picture-menu-desktop-ink.png](https://git.kayg.org/attachments/3c5f373f-dd3a-45e6-a529-c00009525df9) - [picture-menu-desktop-paper.png](https://git.kayg.org/attachments/0b5c9a07-4b72-4eb5-bb3c-4ab02c4049c6) - [picture-menu-phone-ink.png](https://git.kayg.org/attachments/0cac9249-7c0b-4337-bec9-9528b445b6ad) - [picture-menu-phone-paper.png](https://git.kayg.org/attachments/12114ce8-1645-4903-9c83-06a499d2f289) - [picture-menu-small-ink.png](https://git.kayg.org/attachments/ccd3a0ff-5661-468d-84c4-1f5301feed4a) - [picture-menu-small-paper.png](https://git.kayg.org/attachments/ab33f0a4-4c9e-428e-b5f6-49bb2cb3d3d6) - [pill-none-desktop-ink.png](https://git.kayg.org/attachments/6eab5e00-b7a4-4bd5-8cce-167945ad0844) - [pill-none-desktop-paper.png](https://git.kayg.org/attachments/ec850168-6feb-4685-ab30-782fe6e6bd6b) - [pill-none-phone-ink.png](https://git.kayg.org/attachments/e94c58c9-da83-48a6-b9b2-e9a7063a0ddb) - [pill-none-phone-paper.png](https://git.kayg.org/attachments/6c948fd2-f90b-4e64-bb24-93aa9fa4f5ae) - [pill-none-small-ink.png](https://git.kayg.org/attachments/bd5961c9-5558-4522-86e2-a9776e50fa93) - [pill-none-small-paper.png](https://git.kayg.org/attachments/9848540a-0c70-4dab-af7a-4fe9a3380d44) - [pill-one-passkey-desktop-ink.png](https://git.kayg.org/attachments/646dce7c-8f5a-4143-ad79-5be448ae512c) - [pill-one-passkey-desktop-paper.png](https://git.kayg.org/attachments/be5756bc-c1f8-48f1-a409-08abbb4e6646) - [pill-one-passkey-phone-ink.png](https://git.kayg.org/attachments/b1e53b31-ee92-48e9-8493-1c10e48ae8b1) - [pill-one-passkey-phone-paper.png](https://git.kayg.org/attachments/08de54df-219d-4f35-afab-39cfeb3580e7) - [pill-one-passkey-small-ink.png](https://git.kayg.org/attachments/11d0d885-ac10-4085-8fd5-8cbe561e954a) - [pill-one-passkey-small-paper.png](https://git.kayg.org/attachments/90366a4e-1928-43c4-88ea-f15778d5a9ad) - [pill-recovery-desktop-ink.png](https://git.kayg.org/attachments/a03f75b6-cbee-4207-9243-698b505cd177) - [pill-recovery-desktop-paper.png](https://git.kayg.org/attachments/74fcecba-0a34-4425-b9d2-c48c2221d894) - [pill-recovery-phone-ink.png](https://git.kayg.org/attachments/28fb4af5-a786-490c-b53c-27c850e8494b) - [pill-recovery-phone-paper.png](https://git.kayg.org/attachments/51d45841-a334-41e9-a220-343f33d76ee2) - [pill-recovery-small-ink.png](https://git.kayg.org/attachments/42366cbd-e250-4d55-ba70-00ec266c2776) - [pill-recovery-small-paper.png](https://git.kayg.org/attachments/59c855bf-d5f6-46f3-9599-ac8ec1776554) - [settings-account-desktop-ink.png](https://git.kayg.org/attachments/0a229052-9ea8-4bfb-bdcd-d1728cc8648a) - [settings-account-desktop-paper.png](https://git.kayg.org/attachments/ce17a0a0-a647-4b6f-bb7d-42bfb42bb394) - [settings-account-phone-ink.png](https://git.kayg.org/attachments/bf07ec8c-c541-4fa7-8774-8f1cf8c48bb6) - [settings-account-phone-paper.png](https://git.kayg.org/attachments/132eec72-bed2-449a-a984-2d5c50c7fb57) - [settings-account-small-ink.png](https://git.kayg.org/attachments/faf8249d-e153-4793-a087-e6148674c042) - [settings-account-small-paper.png](https://git.kayg.org/attachments/ca864ade-783e-4c2e-bbd8-1e0189de672f) - [username-1-ask-desktop-ink.png](https://git.kayg.org/attachments/8ffd5f53-6c04-4a25-be07-4e6f66d20e16) - [username-1-ask-desktop-paper.png](https://git.kayg.org/attachments/5daa97e6-e609-4cc0-8e19-d0952fe20e34) - [username-1-ask-phone-ink.png](https://git.kayg.org/attachments/3d316463-f911-4986-8d89-dddad6bb17fd) - [username-1-ask-phone-paper.png](https://git.kayg.org/attachments/d972458c-3669-47f0-b899-8d169cf9d9e5) - [username-1-ask-small-ink.png](https://git.kayg.org/attachments/5c9cb3a7-12fe-4eac-8355-e5f33aa3ce43) - [username-1-ask-small-paper.png](https://git.kayg.org/attachments/4ea86cff-c93d-4189-b309-171caf2a02ab) - [username-2-invalid-desktop-ink.png](https://git.kayg.org/attachments/57cb1035-e1bf-4612-a5b2-195828e9505d) - [username-2-invalid-desktop-paper.png](https://git.kayg.org/attachments/adf24e38-d8c4-49dd-a916-78a16f957789) - [username-2-invalid-phone-ink.png](https://git.kayg.org/attachments/5413f5e6-ffb1-467a-906e-3225c6a5f5e1) - [username-2-invalid-phone-paper.png](https://git.kayg.org/attachments/489657fc-36e8-4be2-a5f4-ebd1fab0496d) - [username-2-invalid-small-ink.png](https://git.kayg.org/attachments/785bf05b-f976-48dc-84a6-7eaa0c201803) - [username-2-invalid-small-paper.png](https://git.kayg.org/attachments/f7086c24-2901-4faa-a82b-8350a9458b70) - [username-3-valid-desktop-ink.png](https://git.kayg.org/attachments/eba8fe79-a6ae-4b49-8b65-bfbf05a1e3e0) - [username-3-valid-desktop-paper.png](https://git.kayg.org/attachments/8a216ab1-6cfa-4813-a088-f0c495ce9255) - [username-3-valid-phone-ink.png](https://git.kayg.org/attachments/28ea50c9-dc05-4c3d-bd5e-a0855fc74931) - [username-3-valid-phone-paper.png](https://git.kayg.org/attachments/b3fe6eec-b855-44b8-8851-313140f4b9de) - [username-3-valid-small-ink.png](https://git.kayg.org/attachments/75fda5b9-e8e1-4d8d-9c6a-d161adeb067e) - [username-3-valid-small-paper.png](https://git.kayg.org/attachments/609098c5-affc-4803-b5a1-a565ceae3294) - [username-4-working-desktop-ink.png](https://git.kayg.org/attachments/f306354c-a180-49dd-b643-64996fe726b1) - [username-4-working-desktop-paper.png](https://git.kayg.org/attachments/28d789eb-e028-48ac-a02d-593b7d7d4c6d) - [username-4-working-phone-ink.png](https://git.kayg.org/attachments/e7beaf9b-04b5-47aa-a8c0-cfa474b71400) - [username-4-working-phone-paper.png](https://git.kayg.org/attachments/0c1ba0b8-d806-4074-8a97-5be06a0e944a) - [username-4-working-small-ink.png](https://git.kayg.org/attachments/15ee825e-3dfe-44a0-a0e8-c097d8515460) - [username-4-working-small-paper.png](https://git.kayg.org/attachments/2a123c43-1ec9-4165-9846-76350d124868) - [username-5-done-desktop-ink.png](https://git.kayg.org/attachments/2c5e31a6-1d13-4d27-821d-d64c1654f5e8) - [username-5-done-desktop-paper.png](https://git.kayg.org/attachments/c47d766f-7403-4582-b219-65c3dd0d4aef) - [username-5-done-phone-ink.png](https://git.kayg.org/attachments/7320b815-67ee-4581-baec-1ea528c21a48) - [username-5-done-phone-paper.png](https://git.kayg.org/attachments/8f4c3cf0-9293-44be-83cb-59355d633360) - [username-5-done-small-ink.png](https://git.kayg.org/attachments/88149b3f-3213-4336-ba09-6939708fba1b) - [username-5-done-small-paper.png](https://git.kayg.org/attachments/c301e65b-934d-4690-bdd2-e66cc589f04e) - [username-ask-desktop-ink.png](https://git.kayg.org/attachments/7a05af2b-f0f3-4f27-b4b6-1f85338f3673) - [username-done-desktop-ink.png](https://git.kayg.org/attachments/9ce72834-83ba-4098-b27f-f97df14bf509) - [username-working-desktop-ink.png](https://git.kayg.org/attachments/0c278218-bc8e-43bf-9f04-22c9db71bc5e) </details>
Author
Owner

Rebuilt on current dev for the owner's review. Branch mockups-2026-10-31, head 586cd2b13 (base dev 42ab33035, 2026-10-31). Test double only; nothing is real or saved. Not merged, not deployed. Old previews are gone; this replaces them.

Preview (NetBird, bound to infraternal-dev-02-std; index of every mockup: http://10.69.69.117:47650/mockups.html):

How: Press the person's pill at the bottom of the sidebar. Palette (Ctrl+Shift+K): "Mockup: account" rows for each state.

Note: Rebased only. The data switch (Demo data / Worst case / Empty / One) is the mockup's own review scaffold; on a phone it overlaps the top pill (seen in the 393 px captures of the version panel).

Captures are Chromium on the production build of the mockup, taken by apps/tracer/test/capture-mockups.ts; I have read them for wrong states, not judged them as final.

Desktop 1440×900 @2x, ink

card
long-name
needs-attention
sidebar-footer
team-member
username-change

Desktop 1440×900 @2x, paper

card
long-name
needs-attention
sidebar-footer
team-member
username-change

Phone 393×852 @3x, ink

card
long-name
needs-attention
sidebar-footer
team-member
username-change

Phone 393×852 @3x, paper (first state only)

sidebar-footer

Rebuilt on current dev for the owner's review. Branch `mockups-2026-10-31`, head `586cd2b13` (base dev `42ab33035`, 2026-10-31). Test double only; nothing is real or saved. Not merged, not deployed. Old previews are gone; this replaces them. **Preview** (NetBird, bound to infraternal-dev-02-std; index of every mockup: http://10.69.69.117:47650/mockups.html): - http://10.69.69.117:47650/?mock=1&proto=clusters&scene=3&account=1&version=1 **How:** Press the person's pill at the bottom of the sidebar. Palette (Ctrl+Shift+K): "Mockup: account" rows for each state. **Note:** Rebased only. The data switch (Demo data / Worst case / Empty / One) is the mockup's own review scaffold; on a phone it overlaps the top pill (seen in the 393 px captures of the version panel). Captures are Chromium on the production build of the mockup, taken by `apps/tracer/test/capture-mockups.ts`; I have read them for wrong states, not judged them as final. **Desktop 1440×900 @2x, ink** ![card](https://git.kayg.org/attachments/848c5ee5-1e2f-491b-bc4d-9ef55e67730f) ![long-name](https://git.kayg.org/attachments/6e291d90-cfb7-48e0-a5ea-5bf7cfecd506) ![needs-attention](https://git.kayg.org/attachments/587312fc-f52a-416f-af4d-243915cc96a1) ![sidebar-footer](https://git.kayg.org/attachments/9bab7fe3-f168-4963-9af2-9ab166e24af6) ![team-member](https://git.kayg.org/attachments/5238f42d-2b8f-4316-a3eb-5e6459172b37) ![username-change](https://git.kayg.org/attachments/d0527c5a-15db-479b-b6b0-26fb4391d0a0) **Desktop 1440×900 @2x, paper** ![card](https://git.kayg.org/attachments/4aa0b1fb-a981-4bc6-84a2-455bf1903a9d) ![long-name](https://git.kayg.org/attachments/39be2038-a4ae-4ada-996c-7b64bc964ee1) ![needs-attention](https://git.kayg.org/attachments/867ca52b-34ba-40b1-8905-f26413f8b963) ![sidebar-footer](https://git.kayg.org/attachments/1fb37ebe-90bb-43b3-bcbf-7d7ce1df267c) ![team-member](https://git.kayg.org/attachments/5774cae9-1ffe-40f0-af3d-9244a12c3061) ![username-change](https://git.kayg.org/attachments/7a054e20-94a0-4b1b-b391-35090a4b377d) **Phone 393×852 @3x, ink** ![card](https://git.kayg.org/attachments/57ee2493-7548-4e74-a10d-16e509c2fc48) ![long-name](https://git.kayg.org/attachments/0bf7c219-1f20-4546-9377-368287f094e6) ![needs-attention](https://git.kayg.org/attachments/0cce13fd-e957-4647-84c9-a5b25dc9d133) ![sidebar-footer](https://git.kayg.org/attachments/d8c6f5bb-515d-4a35-88a4-41540ed3c86a) ![team-member](https://git.kayg.org/attachments/268bc59b-9101-429b-9de0-bad55930da95) ![username-change](https://git.kayg.org/attachments/da88a326-4a6f-4e88-b650-5aee0d080839) **Phone 393×852 @3x, paper (first state only)** ![sidebar-footer](https://git.kayg.org/attachments/10082efe-7d6e-414b-90b5-a2b1800988dc)
Author
Owner

Update to the comment above: the branch head is now 57871651f (the preview at http://10.69.69.117:47650/mockups.html runs it). The captures above were taken one build earlier; the only difference is that the window terminals there hide their cursor until attached, a production change that was since removed from the branch. Gate output, what is left out, and the budget finding: #272.

Update to the comment above: the branch head is now `57871651f` (the preview at http://10.69.69.117:47650/mockups.html runs it). The captures above were taken one build earlier; the only difference is that the window terminals there hide their cursor until attached, a production change that was since removed from the branch. Gate output, what is left out, and the budget finding: #272.
Author
Owner

J57 start: branch ui-approved-splitopt-224, base origin/dev f4b61cd6c. Owner 2026-11-01: split buttons with Option approved (must work with Alt on Windows/Linux); account card mostly approved with changes (tag cards + gradient machine tints, concerns shown in the value field). First commit 49fbf2312 (the Option/Alt split buttons, product). Account card follows; it shares sidebar-card/kv-list with J56, so I rebase on its push.

J57 start: branch `ui-approved-splitopt-224`, base origin/dev `f4b61cd6c`. Owner 2026-11-01: split buttons with Option approved (must work with Alt on Windows/Linux); account card mostly approved with changes (tag cards + gradient machine tints, concerns shown in the value field). First commit `49fbf2312` (the Option/Alt split buttons, product). Account card follows; it shares sidebar-card/kv-list with J56, so I rebase on its push.
Author
Owner

J57 progress (branch ui-approved-splitopt-224, pushed; base ui-approved-189-212-216 @ 995d28722, which is J56's work on the shared sidebar card and key/value list; origin/dev is f4b61cd6c).

Done and pushed:

  • 51d39758b Option (macOS) / Alt (Windows, Linux) on the split buttons, in the product (#280).
  • f25ade825 the shared pieces the card stands on (card morph, scroll fade, inline rename, value runs; entity palette tokens for tag cards).
  • dcfa9692f the card from the person's pill, concerns in the value, a card per tag in "Logs in as".
  • 6222dd1c5 the mock double with the worst-case fixture (14 tags, 1,202 machines, 12 accounts) + account-card.spec.ts.
  • 425a3aa00 TASTE: new row (concern stated in the value; no "checked N days ago") and row 71 reaffirmed for this card.

Findings (evidence):

  1. The product has no data for most of the card. identity carries only display_name and username; the server has no endpoint for passkey count, recovery-key state, devices, email, picture, or "logs in as" (grep of crates/infraternal-server routes, and settings-body.on-demand.ts says "the product has no facts to show yet"). So the product card shows name, username and sign out only, and draws no row or control it cannot fill (identityModel in canvas/account/index.ts). The full card is reachable on the mock double (?mock=1&proto=clusters&account=1, &data=worst|empty|one). Needs a backend grill before the rows can be real.
  2. Why every machine was one colour: the mockup's fixtures had no tags at all, so every host chip drew the neutral fill. TASTE row 71 already says host = linear gradient of its tags. The fixtures now carry tags.
  3. Overlap with J56: both of us port the sidebar card / key-value list. I rebased on J56's pushed commits and copied only files J56 does not have (identical copies of the mockup's files), so they should merge cleanly; the orchestrator may see small styles.css conflicts.

Choices for the owner (not decided by me): recovery key counts as stale after 90 days (RECOVERY_STALE_DAYS); only variant A (chips) of "Logs in as" ships, B and C were scaffolding; the username-change flow and Settings, Account stay in the mockup (D283 Q3 needs a server design).

Next: specs green, captures at 1440x900 DPR 2 and 393x852 DPR 3, scripts/verify fast on compute01. Logs: ~/j57-pw2.log on infraternal-dev-02-std; evidence in ~/.cache/codex-scratch/j57-evidence.

**J57 progress** (branch `ui-approved-splitopt-224`, pushed; base `ui-approved-189-212-216` @ `995d28722`, which is J56's work on the shared sidebar card and key/value list; origin/dev is `f4b61cd6c`). Done and pushed: - `51d39758b` Option (macOS) / Alt (Windows, Linux) on the split buttons, in the product (#280). - `f25ade825` the shared pieces the card stands on (card morph, scroll fade, inline rename, value runs; entity palette tokens for tag cards). - `dcfa9692f` the card from the person's pill, concerns in the value, a card per tag in "Logs in as". - `6222dd1c5` the mock double with the worst-case fixture (14 tags, 1,202 machines, 12 accounts) + `account-card.spec.ts`. - `425a3aa00` TASTE: new row (concern stated in the value; no "checked N days ago") and row 71 reaffirmed for this card. **Findings (evidence):** 1. The product has no data for most of the card. `identity` carries only `display_name` and `username`; the server has no endpoint for passkey count, recovery-key state, devices, email, picture, or "logs in as" (grep of `crates/infraternal-server` routes, and `settings-body.on-demand.ts` says "the product has no facts to show yet"). So the product card shows name, username and sign out only, and draws no row or control it cannot fill (`identityModel` in `canvas/account/index.ts`). The full card is reachable on the mock double (`?mock=1&proto=clusters&account=1`, `&data=worst|empty|one`). **Needs a backend grill** before the rows can be real. 2. Why every machine was one colour: the mockup's fixtures had no tags at all, so every host chip drew the neutral fill. TASTE row 71 already says host = linear gradient of its tags. The fixtures now carry tags. 3. Overlap with J56: both of us port the sidebar card / key-value list. I rebased on J56's pushed commits and copied only files J56 does not have (identical copies of the mockup's files), so they should merge cleanly; the orchestrator may see small `styles.css` conflicts. **Choices for the owner** (not decided by me): recovery key counts as stale after 90 days (`RECOVERY_STALE_DAYS`); only variant A (chips) of "Logs in as" ships, B and C were scaffolding; the username-change flow and Settings, Account stay in the mockup (D283 Q3 needs a server design). Next: specs green, captures at 1440x900 DPR 2 and 393x852 DPR 3, `scripts/verify fast` on compute01. Logs: `~/j57-pw2.log` on infraternal-dev-02-std; evidence in `~/.cache/codex-scratch/j57-evidence`.
Author
Owner

J57 evidence: the person's card (branch ui-approved-splitopt-224, production build of the mock double, Chromium; read for wrong states, not judged as final). 1440x900 DPR 2 and 393x852 DPR 3, ink and paper: healthy account, recovery key not verified since a date, one passkey, Logs in as (demo and worst case: 12 accounts, 1,202 machines, 14 tags), and videos of the card growing out of the pill and the pane opening.

**J57 evidence: the person's card** (branch `ui-approved-splitopt-224`, production build of the mock double, Chromium; read for wrong states, not judged as final). 1440x900 DPR 2 and 393x852 DPR 3, ink and paper: healthy account, recovery key not verified since a date, one passkey, Logs in as (demo and worst case: 12 accounts, 1,202 machines, 14 tags), and videos of the card growing out of the pill and the pane opening.
Author
Owner

J57 evidence, refreshed on the final head (fbc39ec60, rebased on J56's ui-approved-189-212-216; supersedes the images above). Fixed since the first captures: the long account chip no longer covers the key "Logs in as", the account chips are no longer repeated on redraw, counts keep a space ("root 41"), quiet text clears 4.5:1 on a tag's card. Worst case (12 accounts, 1,202 machines, 14 tags) and the stale recovery key, desktop 1440x900 DPR 2 and phone 393x852 DPR 3, ink and paper.

**J57 evidence, refreshed on the final head** (`fbc39ec60`, rebased on J56's `ui-approved-189-212-216`; supersedes the images above). Fixed since the first captures: the long account chip no longer covers the key "Logs in as", the account chips are no longer repeated on redraw, counts keep a space ("root 41"), quiet text clears 4.5:1 on a tag's card. Worst case (12 accounts, 1,202 machines, 14 tags) and the stale recovery key, desktop 1440x900 DPR 2 and phone 393x852 DPR 3, ink and paper.
Author
Owner

J57 finished. Branch ui-approved-splitopt-224, head fbc39ec60 (stacked on J56's ui-approved-189-212-216, which is on dev f4b61cd6c; J56's version pill is in the base, so I made the two cards one-at-a-time). Branch ui-approved-splitopt-224-on-dev (7a64aaa3e) is the same work merged with current origin/dev (7bf03044f), the only conflict being two TASTE rows added at the same spot; it is what the gate below ran on.

Commits (atomic): Option/Alt split buttons (#280); shared pieces for the card; the card from the pill, concerns in values, a card per tag; mock double + worst-case fixture + spec; TASTE rows (separate docs commit); contrast + Escape fix; key/value column fix; value-run redraw fix; count spacing; one card at a time with the version panel; spec waits.

Gate. scripts/verify fast on compute01 (x86_64) at 7a64aaa3e ran to its last row (TypeScript protocol tests: 39 pass, 0 fail); the log has no FAILED/panicked/error line. I did not capture its exit code. compute01 was unreachable for ~1 h in the middle (overloaded, not rebooted) and the first run was lost; the second queued ~75 min. Verbatim from that log:

JavaScript transfer, critical: identity=992782 gzip=308380/327680 brotli=256828/273408 bytes
JavaScript transfer, deferred: identity=172533 gzip=59954/61440 brotli=53231/55296 bytes
JavaScript transfer, onDemand: identity=259835 gzip=103090/20480 brotli=91900/18432 bytes
JavaScript transfer, onDemand largest chunk chunk-wvdhacx3.js: gzip=15467/20480 brotli=13811/18432 bytes

Against J56's head (critical gzip 305,444, identity 986,973) this branch adds +2,936 B gzip / +5,809 B identity to the critical path; that is the Option/Alt morph in window-card (icon-morph, key handling). On J56's old 300 KiB cap it is over (308,385 > 307,200); under dev's new 320 KiB cap (327,680) it passes. The person's card adds nothing critical (own chunk and stylesheet, loaded on idle). No Rust changed, so no static-size delta.

Browser specs (production build, Chromium, dev-02): account-card.spec.ts (17) + window-option-new.spec.ts (14) + captures (17): 48 run, 47 passed first time and the one failure (a spec race on the product mount) re-ran green; the account spec also covers 4.5:1 text contrast measured on the real composited background in ink and paper, the worst-case fixture bounds, a 393x852 phone, the product path, and the version panel.

Decisions for kayg:

  1. The product has no server data for passkeys, recovery-key state, devices, email, picture or "logs in as"; the product card shows name, username and sign out only and draws no row or control it cannot fill. The full card is on the mock double (?mock=1&proto=clusters&account=1, &data=worst|empty|one). Needs a backend grill.
  2. A recovery key counts as stale after 90 days (RECOVERY_STALE_DAYS).
  3. Only variant A (chips) of "Logs in as" shipped; the username-change flow and Settings, Account stay in the mockup (D283 Q3).
  4. A machine with several tags is listed in each of its tags' cards; the palette has 12 colours, so two tags can share one.
  5. Alt-click can be intercepted by some Linux window managers (KDE's default Alt-drag); the four actions keep their own keys and palette entries.
**J57 finished.** Branch `ui-approved-splitopt-224`, head `fbc39ec60` (stacked on J56's `ui-approved-189-212-216`, which is on dev `f4b61cd6c`; J56's version pill is in the base, so I made the two cards one-at-a-time). Branch `ui-approved-splitopt-224-on-dev` (`7a64aaa3e`) is the same work merged with current `origin/dev` (`7bf03044f`), the only conflict being two TASTE rows added at the same spot; it is what the gate below ran on. Commits (atomic): Option/Alt split buttons (#280); shared pieces for the card; the card from the pill, concerns in values, a card per tag; mock double + worst-case fixture + spec; TASTE rows (separate docs commit); contrast + Escape fix; key/value column fix; value-run redraw fix; count spacing; one card at a time with the version panel; spec waits. **Gate.** `scripts/verify fast` on compute01 (x86_64) at `7a64aaa3e` ran to its last row (TypeScript protocol tests: 39 pass, 0 fail); the log has no FAILED/panicked/error line. I did not capture its exit code. compute01 was unreachable for ~1 h in the middle (overloaded, not rebooted) and the first run was lost; the second queued ~75 min. Verbatim from that log: ``` JavaScript transfer, critical: identity=992782 gzip=308380/327680 brotli=256828/273408 bytes JavaScript transfer, deferred: identity=172533 gzip=59954/61440 brotli=53231/55296 bytes JavaScript transfer, onDemand: identity=259835 gzip=103090/20480 brotli=91900/18432 bytes JavaScript transfer, onDemand largest chunk chunk-wvdhacx3.js: gzip=15467/20480 brotli=13811/18432 bytes ``` Against J56's head (critical gzip 305,444, identity 986,973) this branch adds **+2,936 B gzip / +5,809 B identity to the critical path**; that is the Option/Alt morph in `window-card` (icon-morph, key handling). On J56's old 300 KiB cap it is over (308,385 > 307,200); under dev's new 320 KiB cap (327,680) it passes. The person's card adds nothing critical (own chunk and stylesheet, loaded on idle). No Rust changed, so no static-size delta. **Browser specs** (production build, Chromium, dev-02): `account-card.spec.ts` (17) + `window-option-new.spec.ts` (14) + captures (17): 48 run, 47 passed first time and the one failure (a spec race on the product mount) re-ran green; the account spec also covers 4.5:1 text contrast measured on the real composited background in ink and paper, the worst-case fixture bounds, a 393x852 phone, the product path, and the version panel. **Decisions for kayg:** 1. The product has no server data for passkeys, recovery-key state, devices, email, picture or "logs in as"; the product card shows name, username and sign out only and draws no row or control it cannot fill. The full card is on the mock double (`?mock=1&proto=clusters&account=1`, `&data=worst|empty|one`). Needs a backend grill. 2. A recovery key counts as stale after 90 days (`RECOVERY_STALE_DAYS`). 3. Only variant A (chips) of "Logs in as" shipped; the username-change flow and Settings, Account stay in the mockup (D283 Q3). 4. A machine with several tags is listed in each of its tags' cards; the palette has 12 colours, so two tags can share one. 5. Alt-click can be intercepted by some Linux window managers (KDE's default Alt-drag); the four actions keep their own keys and palette entries.
Author
Owner

P4 backend plan, planning only. branch plan/p4-account-backend, base/head dev ffebd0561. research steps: inspect mock rows, trace stored facts and routes, rank slices; budget 15 minutes. no code changed, gate not applicable. no builds or verify fast run.

the server already stores several facts the product model omits. the gap is partly exposure, not an entirely new account backend. source links below are pinned to the inspected dev head. sizes are rough engineer-days including focused tests/review, not measured commitments: S ≤1, M 2–3, L 4–7; access-policy work may exceed L.

mock row/control data/API needed already on dev gap / size
name + username current identity; profile write for name auth/sessions.rs returns both through /auth/session; identity columns exist display shipped; name PATCH missing (S). username change is separate certificate/rule work under D283 Q3 (L), not a profile PATCH
email optional stored email + profile write no email field in auth schema or route migration, validation, save (S); never a sign-in identifier, D283 Q4
picture bounded upload/read/delete initials only; no stored picture/API storage, validation, limits, save/delete (M)
company + role EE organisation/membership owner bit and mesh action roles exist, neither is an organisation membership EE model/API (L+); defer per D33, D283 Q5
passkeys / sign-in keys authenticated count; Settings add/list auth/webauthn.rs stores credentials, already counts them and supports adding a second; HTTP has add-start and shared registration finish. API keys separately have api-list/api-revoke in auth/devices.rs count + model wiring (S); passkey list/removal needs more work (M). API keys are not passkeys
recovery key presence; last successful verification of the current key recovery hash + wrap, recovery flow and legacy rotation in auth/recovery*.rs; audit stores start/cancel/complete only presence read (S); no last-verification timestamp or check-only action (M). creation, rotation and recovery-start times do not prove the user still holds it
devices → sessions defined count; list/current marker/revoke auth/sessions.rs: browser device label, expiry, freshness, unlock state, current logout. auth/devices.rs: approved CLI/API key list/revoke browser list/revoke missing (M). labels are not stable device IDs; counting sessions or keys does not count physical devices. no last-seen metadata; fresh is passkey freshness
logs in as → accounts, hosts, tag cards authoritative person → host account access summary, host names/OS/tags; bounded detail API remote_inventory.rs has host facts; 0015_remote_identity.sql has owner + execution UID. mesh API has paged nodes/tags/grants and scoped roles; rules.rs is network traffic policy no effective person-to-host-account summary. join identity/permissions, not just tags or open terminals (L+); mesh reachability does not grant a host login
concerns in values + amber pill passkey count, recovery presence/verification time mock account/words.ts already derives wording and the chosen 90-day threshold wire facts, preserve unknowns (S with reads); do not map an absent timestamp to “never verified”
sign out of this device revoke current session + local cleanup /auth/logout, auth/sessions.rs; product already wired no new backend slice

evidence: mock model/rows, auth schema, auth HTTP, passkeys, devices/API keys, sessions, recovery, recovery audit, host inventory, mesh tags/roles/API, mesh rules.

proposed order, with a stopping point after each:

  1. existing facts first (S): one small authenticated account-summary read: passkey count and recovery setup presence, wired to the card. distinguish verification history unavailable from never verified; show setup presence without claiming recent verification. connect the second-passkey flow in Settings. no schema change for these reads, no credentials/wraps in the summary, no extra first-paint request.
  2. take access away (M): browser sessions list + revoke another session with fresh confirmation, plus existing CLI/API key list/revoke in Settings. count only the agreed category; exclude expired/pending browser sessions. prove revoked sockets lose authority.
  3. recovery reassurance (M): dedicated authenticated check-only proof, bound to current recovery metadata; persist successful verification time, clear it on replacement, then enable the 90-day concern. do not reuse recovery-start: checking possession must not start account recovery. old history stays unknown.
  4. profile (S then M): name editing, then optional email, then picture. independent of security/access work. username changes and EE company/role stay parked.
  5. logs in as (L+, grill first): effective host-account access with permission-filtered, paged tag/host detail; reuse mesh inventory and revisions. no new policy engine chosen here. ship only what the actual enforcement can substantiate.

owner grill (recommended option first):

  1. fund slice 1 only, then choose the next slice; or commit to slices 1–3 now? the recommendation gives useful rows without buying the whole card.
  2. list signed-in browser sessions and CLI/API keys separately; or require one deduplicated Devices count now? the latter needs stable device identity and migration, not addition of the two counts.
  3. track successful current-key verification server-side, through a check-only flow; or ship setup presence only and defer age warnings? 90 days stays chosen; no need to re-grill the threshold. existing accounts have unknown verification history.
  4. limit the first login-access summary to the host-account paths the product actually enforces, naming that scope; or require every SSH/mesh/credential path before showing the row? this determines whether the slice is bounded or waits for broader access work.
  5. defer profile editing until the security rows are useful; or move name/email ahead of session/recovery work? picture, username change and EE remain separate slices either way.
P4 backend plan, planning only. branch `plan/p4-account-backend`, base/head dev `ffebd0561`. research steps: inspect mock rows, trace stored facts and routes, rank slices; budget 15 minutes. no code changed, gate not applicable. no builds or `verify fast` run. the server already stores several facts the product model omits. the gap is partly exposure, not an entirely new account backend. source links below are pinned to the inspected dev head. sizes are rough engineer-days including focused tests/review, not measured commitments: S ≤1, M 2–3, L 4–7; access-policy work may exceed L. | mock row/control | data/API needed | already on dev | gap / size | |---|---|---|---| | name + username | current identity; profile write for name | `auth/sessions.rs` returns both through `/auth/session`; identity columns exist | display shipped; name PATCH missing (S). username change is separate certificate/rule work under D283 Q3 (L), not a profile PATCH | | email | optional stored email + profile write | no email field in auth schema or route | migration, validation, save (S); never a sign-in identifier, D283 Q4 | | picture | bounded upload/read/delete | initials only; no stored picture/API | storage, validation, limits, save/delete (M) | | company + role | EE organisation/membership | owner bit and mesh action roles exist, neither is an organisation membership | EE model/API (L+); defer per D33, D283 Q5 | | passkeys / sign-in keys | authenticated count; Settings add/list | `auth/webauthn.rs` stores credentials, already counts them and supports adding a second; HTTP has `add-start` and shared registration finish. API keys separately have `api-list`/`api-revoke` in `auth/devices.rs` | count + model wiring (S); passkey list/removal needs more work (M). API keys are not passkeys | | recovery key | presence; last successful verification of the **current** key | recovery hash + wrap, recovery flow and legacy rotation in `auth/recovery*.rs`; audit stores start/cancel/complete only | presence read (S); no last-verification timestamp or check-only action (M). creation, rotation and recovery-start times do not prove the user still holds it | | devices → sessions | defined count; list/current marker/revoke | `auth/sessions.rs`: browser device label, expiry, freshness, unlock state, current logout. `auth/devices.rs`: approved CLI/API key list/revoke | browser list/revoke missing (M). labels are not stable device IDs; counting sessions or keys does not count physical devices. no last-seen metadata; `fresh` is passkey freshness | | logs in as → accounts, hosts, tag cards | authoritative person → host account access summary, host names/OS/tags; bounded detail API | `remote_inventory.rs` has host facts; `0015_remote_identity.sql` has owner + execution UID. mesh API has paged nodes/tags/grants and scoped roles; `rules.rs` is network traffic policy | no effective person-to-host-account summary. join identity/permissions, not just tags or open terminals (L+); mesh reachability does not grant a host login | | concerns in values + amber pill | passkey count, recovery presence/verification time | mock `account/words.ts` already derives wording and the **chosen 90-day threshold** | wire facts, preserve unknowns (S with reads); do not map an absent timestamp to “never verified” | | sign out of this device | revoke current session + local cleanup | `/auth/logout`, `auth/sessions.rs`; product already wired | no new backend slice | evidence: [mock model/rows](https://git.kayg.org/kayg/infraternal/src/commit/ffebd0561/apps/tracer/src/canvas/account/words.ts), [auth schema](https://git.kayg.org/kayg/infraternal/src/commit/ffebd0561/crates/infraternal-server/migrations/0001_auth.sql), [auth HTTP](https://git.kayg.org/kayg/infraternal/src/commit/ffebd0561/crates/infraternal-server/src/auth/http.rs), [passkeys](https://git.kayg.org/kayg/infraternal/src/commit/ffebd0561/crates/infraternal-server/src/auth/webauthn.rs), [devices/API keys](https://git.kayg.org/kayg/infraternal/src/commit/ffebd0561/crates/infraternal-server/src/auth/devices.rs), [sessions](https://git.kayg.org/kayg/infraternal/src/commit/ffebd0561/crates/infraternal-server/src/auth/sessions.rs), [recovery](https://git.kayg.org/kayg/infraternal/src/commit/ffebd0561/crates/infraternal-server/src/auth/recovery.rs), [recovery audit](https://git.kayg.org/kayg/infraternal/src/commit/ffebd0561/crates/infraternal-server/migrations/0010_recovery_audit.sql), [host inventory](https://git.kayg.org/kayg/infraternal/src/commit/ffebd0561/crates/infraternal-server/src/remote_inventory.rs), [mesh tags/roles/API](https://git.kayg.org/kayg/infraternal/src/commit/ffebd0561/crates/plugins/mesh/src/api.rs), [mesh rules](https://git.kayg.org/kayg/infraternal/src/commit/ffebd0561/crates/infraternal-mesh/src/rules.rs). proposed order, with a stopping point after each: 1. **existing facts first (S):** one small authenticated account-summary read: passkey count and recovery setup presence, wired to the card. distinguish verification history unavailable from never verified; show setup presence without claiming recent verification. connect the second-passkey flow in Settings. no schema change for these reads, no credentials/wraps in the summary, no extra first-paint request. 2. **take access away (M):** browser sessions list + revoke another session with fresh confirmation, plus existing CLI/API key list/revoke in Settings. count only the agreed category; exclude expired/pending browser sessions. prove revoked sockets lose authority. 3. **recovery reassurance (M):** dedicated authenticated check-only proof, bound to current recovery metadata; persist successful verification time, clear it on replacement, then enable the 90-day concern. do not reuse recovery-start: checking possession must not start account recovery. old history stays unknown. 4. **profile (S then M):** name editing, then optional email, then picture. independent of security/access work. username changes and EE company/role stay parked. 5. **logs in as (L+, grill first):** effective host-account access with permission-filtered, paged tag/host detail; reuse mesh inventory and revisions. no new policy engine chosen here. ship only what the actual enforcement can substantiate. owner grill (recommended option first): 1. **fund slice 1 only, then choose the next slice**; or commit to slices 1–3 now? the recommendation gives useful rows without buying the whole card. 2. **list signed-in browser sessions and CLI/API keys separately**; or require one deduplicated Devices count now? the latter needs stable device identity and migration, not addition of the two counts. 3. **track successful current-key verification server-side, through a check-only flow**; or ship setup presence only and defer age warnings? 90 days stays chosen; no need to re-grill the threshold. existing accounts have unknown verification history. 4. **limit the first login-access summary to the host-account paths the product actually enforces**, naming that scope; or require every SSH/mesh/credential path before showing the row? this determines whether the slice is bounded or waits for broader access work. 5. **defer profile editing until the security rows are useful**; or move name/email ahead of session/recovery work? picture, username change and EE remain separate slices either way.
Author
Owner

J126 start: branch j126-account-summary, base 7add126405a9205bb817a154af11fad2c438cd45 (origin/dev). Slice 1 only, per owner answers of 2026-11-03.

Plan, estimate 2 hours including shared VM gates (scope leash: stop and report if this exceeds 4 hours):

  1. Add one authenticated summary read selecting only passkey count and recovery setup presence, with no schema change or credential/wrap response; server tests for authorization, per-account counts and exact response fields.
  2. Load it on account-card open (no additional first-paint request), feed existing rows, distinguish unavailable verification history from never verified, and connect the existing second-passkey flow in Settings. Reuse approved card/Settings UI, no restyling.
  3. Verify production card rows, navigation and alignment at 1440×900 and 393×852, ink/paper, with issue-attached screenshots. Run full scripts/verify fast through build-slot on the emptier VM, report static sizes/deltas.
  4. Commit atomically, push only this branch, post head SHA and verbatim gate output. No merge/deploy.

Initial finding: identityModel currently exposes only name/username, and Recovery.verifiedAt: null explicitly means never verified. The production read must preserve unavailable history separately rather than map it to null. Existing UI/wording and server auth paths are being inspected before implementation.

J126 start: branch `j126-account-summary`, base `7add126405a9205bb817a154af11fad2c438cd45` (origin/dev). Slice 1 only, per owner answers of 2026-11-03. Plan, estimate 2 hours including shared VM gates (scope leash: stop and report if this exceeds 4 hours): 1. Add one authenticated summary read selecting only passkey count and recovery setup presence, with no schema change or credential/wrap response; server tests for authorization, per-account counts and exact response fields. 2. Load it on account-card open (no additional first-paint request), feed existing rows, distinguish unavailable verification history from never verified, and connect the existing second-passkey flow in Settings. Reuse approved card/Settings UI, no restyling. 3. Verify production card rows, navigation and alignment at 1440×900 and 393×852, ink/paper, with issue-attached screenshots. Run full `scripts/verify fast` through build-slot on the emptier VM, report static sizes/deltas. 4. Commit atomically, push only this branch, post head SHA and verbatim gate output. No merge/deploy. Initial finding: `identityModel` currently exposes only name/username, and `Recovery.verifiedAt: null` explicitly means never verified. The production read must preserve unavailable history separately rather than map it to null. Existing UI/wording and server auth paths are being inspected before implementation.
Author
Owner

J126 finding: there is a server second-passkey ceremony (auth/webauthn.rs:add_start, registration finish), but no existing browser add-start caller. The Settings connection therefore reuses the browser login/PRF unwrap and registration primitives in auth.ts; it retains the DEK only for this operation, checks the expected account before enrollment, and clears it in finally. No new crypto or server ceremony is introduced.

Summary contract proposed/implemented: POST /auth/account-summary, empty body + same-origin unlocked live browser cookie; response only {user_id,session_incarnation,passkeys,recovery_set_up}. A single indexed query authenticates and scopes the count/presence, and does not select credentials, wraps or verifier values. Recovery presence requires existing verifier plus recovery wrap. The identity/lifetime fields fence shared-cookie account changes. Existing verification history remains unavailable, rather than null/never or an invented date.

Progress: summary, card wiring and Settings entry point implemented; focused VM checks queued on dev-02 (compute01 all four slots occupied). Evidence/target: /home/claude/.cache/codex-scratch/j126/evidence, /home/claude/worktrees/j126/target. Next: focused compiler/tests, browser production captures and full fast gate.

J126 finding: there is a server second-passkey ceremony (`auth/webauthn.rs:add_start`, registration finish), but no existing browser `add-start` caller. The Settings connection therefore reuses the browser login/PRF unwrap and registration primitives in `auth.ts`; it retains the DEK only for this operation, checks the expected account before enrollment, and clears it in finally. No new crypto or server ceremony is introduced. Summary contract proposed/implemented: POST `/auth/account-summary`, empty body + same-origin unlocked live browser cookie; response only `{user_id,session_incarnation,passkeys,recovery_set_up}`. A single indexed query authenticates and scopes the count/presence, and does not select credentials, wraps or verifier values. Recovery presence requires existing verifier plus recovery wrap. The identity/lifetime fields fence shared-cookie account changes. Existing verification history remains unavailable, rather than null/never or an invented date. Progress: summary, card wiring and Settings entry point implemented; focused VM checks queued on dev-02 (compute01 all four slots occupied). Evidence/target: `/home/claude/.cache/codex-scratch/j126/evidence`, `/home/claude/worktrees/j126/target`. Next: focused compiler/tests, browser production captures and full fast gate.
Author
Owner

J126 checkpoint pushed: 6afecf6e5 on j126-account-summary. Implementation and focused tests are present; not yet qualified. The first VM run remains queued (dev-02: both configured slots busy; compute01: all four busy). No gate claim yet. Next: compiler/server tests, production browser tests/captures, final full fast gate and squash the WIP into the slice commit. Scope unchanged.

J126 checkpoint pushed: `6afecf6e5` on `j126-account-summary`. Implementation and focused tests are present; not yet qualified. The first VM run remains queued (dev-02: both configured slots busy; compute01: all four busy). No gate claim yet. Next: compiler/server tests, production browser tests/captures, final full fast gate and squash the WIP into the slice commit. Scope unchanged.
Author
Owner

Owner grill answers carried by J126 (2026-11-03): build slice 1 only, then decide. Later slices list signed-in browsers and CLI/API keys separately; recovery gets a check-only “I still have them” step; “Logs in as” covers only paths actually enforced; profile editing comes last. Existing accounts have unavailable recovery verification history, not evidence of never/recently verified. J126 implements none of those later slices.

Owner grill answers carried by J126 (2026-11-03): build slice 1 only, then decide. Later slices list signed-in browsers and CLI/API keys separately; recovery gets a check-only “I still have them” step; “Logs in as” covers only paths actually enforced; profile editing comes last. Existing accounts have unavailable recovery verification history, not evidence of never/recently verified. J126 implements none of those later slices.
Author
Owner

J126 checkpoint pushed: eb36a146cee9f02db026b800c623644b60945409 on j126-account-summary (WIP commits will be squashed after qualification). Focused client/crypto/model/wording tests: 25 pass, 0 fail, 116 expectations; cargo fmt --all clean. No performance claims from functional test timings.

The broader VM run is still queued, second on dev-02; both Linux VMs were full when checked. No final gate claim. Planned run: compiler + account-summary server tests + production account-card and summary browser specs, then full scripts/verify fast on the final head. Evidence: /home/claude/.cache/codex-scratch/j126/evidence; sole target: /home/claude/worktrees/j126/target. Scope unchanged, no blockers requiring an owner decision.

J126 checkpoint pushed: `eb36a146cee9f02db026b800c623644b60945409` on `j126-account-summary` (WIP commits will be squashed after qualification). Focused client/crypto/model/wording tests: 25 pass, 0 fail, 116 expectations; `cargo fmt --all` clean. No performance claims from functional test timings. The broader VM run is still queued, second on dev-02; both Linux VMs were full when checked. No final gate claim. Planned run: compiler + account-summary server tests + production account-card and summary browser specs, then full `scripts/verify fast` on the final head. Evidence: `/home/claude/.cache/codex-scratch/j126/evidence`; sole target: `/home/claude/worktrees/j126/target`. Scope unchanged, no blockers requiring an owner decision.
Author
Owner

J126 browser finding (regression reproduced before the repair): server tests 2/2, TypeScript and focused units 25/25 passed; existing account-card suite 17/17 passed. The new product summary checks exposed an actual phone navigation defect: pressing Passkeys opens Settings but leaves the sidebar/card over it, so Add a second cannot be reached. Playwright at 393×852 reports “sidebar … subtree intercepts pointer events”; raw evidence is evidence/browser-phone-error.md and the initial account-settings-393-ink.png (outside the checkout).

Repair stays within wiring: close the summary card and sidebar through their existing controls when a row opens Settings. The regression now asserts that the sidebar closes and then physically clicks Add a second. Also corrected the test's ambiguous toast locator (two mounted toast containers) and wait for the existing card transition before taking screenshots. No style changes.

J126 browser finding (regression reproduced before the repair): server tests 2/2, TypeScript and focused units 25/25 passed; existing account-card suite 17/17 passed. The new product summary checks exposed an actual phone navigation defect: pressing Passkeys opens Settings but leaves the sidebar/card over it, so Add a second cannot be reached. Playwright at 393×852 reports “sidebar … subtree intercepts pointer events”; raw evidence is `evidence/browser-phone-error.md` and the initial `account-settings-393-ink.png` (outside the checkout). Repair stays within wiring: close the summary card and sidebar through their existing controls when a row opens Settings. The regression now asserts that the sidebar closes and then physically clicks Add a second. Also corrected the test's ambiguous toast locator (two mounted toast containers) and wait for the existing card transition before taking screenshots. No style changes.
Author
Owner

J126 source head is now 824f7f027d4838ab03496e7e664d3a1fb04145e5, pushed as one slice commit on j126-account-summary (WIP checkpoints squashed). The first production browser run passed all 17 existing card cases and the new unavailable/foreign-account case. The four new summary cases passed their no-first-paint-read, row text, history tooltip, count, card alignment and Settings alignment checks before exposing the navigation/test-locator findings recorded above.

The repaired run is queued on dev-02, PID 734708, log /home/claude/.cache/codex-scratch/j126/evidence/final-driver.log: TypeScript + five summary browser cases, then full scripts/verify fast if green. Sole target remains /home/claude/worktrees/j126/target. Server summary tests: 2 pass, 0 fail. Client focused units: 25 pass, 0 fail. No final gate claim yet; original 2-hour estimate remains in force.

J126 source head is now `824f7f027d4838ab03496e7e664d3a1fb04145e5`, pushed as one slice commit on `j126-account-summary` (WIP checkpoints squashed). The first production browser run passed all 17 existing card cases and the new unavailable/foreign-account case. The four new summary cases passed their no-first-paint-read, row text, history tooltip, count, card alignment and Settings alignment checks before exposing the navigation/test-locator findings recorded above. The repaired run is queued on dev-02, PID 734708, log `/home/claude/.cache/codex-scratch/j126/evidence/final-driver.log`: TypeScript + five summary browser cases, then full `scripts/verify fast` if green. Sole target remains `/home/claude/worktrees/j126/target`. Server summary tests: 2 pass, 0 fail. Client focused units: 25 pass, 0 fail. No final gate claim yet; original 2-hour estimate remains in force.
Author
Owner

J126 qualification update: final pushed head 1cd1b6917203102f8bdd8071349087747f3a4751 on j126-account-summary (supersedes the prior checkpoint SHA). Source is clean locally and synchronized to dev-02. The repaired production browser run and full fast gate remain queued together through build-slot, PID 734708; both Linux VMs remain fully occupied. No final gate claim.

Already passed: TypeScript, 25 focused client/crypto/model/wording tests, two real-router/SQLite summary tests, 17 existing account-card browser cases and the new unavailable/foreign-account browser case. Remaining: repaired five-case browser run with fresh screenshots, full fast gate, attach evidence and clean the sole target. Scope unchanged. Log /home/claude/.cache/codex-scratch/j126/evidence/final-driver.log; target /home/claude/worktrees/j126/target. Original 2-hour estimate / 4-hour stop remains in force.

J126 qualification update: final pushed head `1cd1b6917203102f8bdd8071349087747f3a4751` on `j126-account-summary` (supersedes the prior checkpoint SHA). Source is clean locally and synchronized to dev-02. The repaired production browser run and full fast gate remain queued together through build-slot, PID 734708; both Linux VMs remain fully occupied. No final gate claim. Already passed: TypeScript, 25 focused client/crypto/model/wording tests, two real-router/SQLite summary tests, 17 existing account-card browser cases and the new unavailable/foreign-account browser case. Remaining: repaired five-case browser run with fresh screenshots, full fast gate, attach evidence and clean the sole target. Scope unchanged. Log `/home/claude/.cache/codex-scratch/j126/evidence/final-driver.log`; target `/home/claude/worktrees/j126/target`. Original 2-hour estimate / 4-hour stop remains in force.
Author
Owner

J126 qualification host update: compute01 had a free slot, so per the brief I cancelled the dev-02 queue entry (PID 734708) and moved the exact final head 1cd1b6917203102f8bdd8071349087747f3a4751 to /home/claude/worktrees/j126 on compute01. The slot filled during checkout/cleanup; final browser + fast gate are now queued there as PID 2330006. Log /home/claude/.cache/codex-scratch/j126/evidence/final-driver.log.

Infrastructure finding: removal of the old sole Rust target /home/claude/worktrees/j126/target on dev-02 is stuck in filesystem I/O: 866456 ... Ds rm -rf /home/claude/worktrees/j126/target for over six minutes. SSH responds. The unnecessary size scan was stopped; removal remains active. No infrastructure repair attempted. I will collect the final gate and complete cleanup before handing back; no code or scope changes.

J126 qualification host update: compute01 had a free slot, so per the brief I cancelled the dev-02 queue entry (PID 734708) and moved the exact final head `1cd1b6917203102f8bdd8071349087747f3a4751` to `/home/claude/worktrees/j126` on compute01. The slot filled during checkout/cleanup; final browser + fast gate are now queued there as PID 2330006. Log `/home/claude/.cache/codex-scratch/j126/evidence/final-driver.log`. Infrastructure finding: removal of the old sole Rust target `/home/claude/worktrees/j126/target` on dev-02 is stuck in filesystem I/O: `866456 ... Ds rm -rf /home/claude/worktrees/j126/target` for over six minutes. SSH responds. The unnecessary size scan was stopped; removal remains active. No infrastructure repair attempted. I will collect the final gate and complete cleanup before handing back; no code or scope changes.
Author
Owner

J126c takeover (Sonnet). Previous job J126 (Codex) stopped at a provider 429. Nothing is restarted or redone.

State found:

  • Branch j126-account-summary, head 1cd1b6917203102f8bdd8071349087747f3a4751 (one slice commit on base 7add126405a9; origin/dev is now 29 commits ahead, none touch the same files). My worktree: ~/Developer/Worktrees/infraternal-j126.
  • No uncommitted work: the VM worktrees on compute01 and dev-02 are clean at that SHA. No j126 processes are running on either VM.
  • The previous job had finished its qualification on compute01 but never reported it. final-driver.log: BROWSER_REPAIR_EXIT=0 (5 summary browser cases pass) and VERIFY_EXIT=0 for the full scripts/verify fast. The log is for HEAD 1cd1b6917, and the 8 screenshots exist (2 sizes x ink/paper x card/Settings).
  • Leftover: a 43 GB Rust target at /home/claude/worktrees/j126/target on compute01. The dev-02 one is already gone.

Remaining plan (about 30 minutes, no gate re-run, because no code will change):

  1. Review the diff myself for stored-facts-only and secrets (done before this comment: one query, count and boolean only, no credential/wrap/verifier selected, unavailable history is not mapped to "never").
  2. Look at the screenshots, attach them here, quote the gate output verbatim.
  3. Delete the leftover target and hand back.
**J126c takeover (Sonnet).** Previous job J126 (Codex) stopped at a provider 429. Nothing is restarted or redone. State found: - Branch `j126-account-summary`, head `1cd1b6917203102f8bdd8071349087747f3a4751` (one slice commit on base `7add126405a9`; origin/dev is now 29 commits ahead, none touch the same files). My worktree: `~/Developer/Worktrees/infraternal-j126`. - No uncommitted work: the VM worktrees on compute01 and dev-02 are clean at that SHA. No j126 processes are running on either VM. - The previous job had finished its qualification on compute01 but never reported it. `final-driver.log`: `BROWSER_REPAIR_EXIT=0` (5 summary browser cases pass) and `VERIFY_EXIT=0` for the full `scripts/verify fast`. The log is for HEAD `1cd1b6917`, and the 8 screenshots exist (2 sizes x ink/paper x card/Settings). - Leftover: a 43 GB Rust target at `/home/claude/worktrees/j126/target` on compute01. The dev-02 one is already gone. Remaining plan (about 30 minutes, no gate re-run, because no code will change): 1. Review the diff myself for stored-facts-only and secrets (done before this comment: one query, count and boolean only, no credential/wrap/verifier selected, unavailable history is not mapped to "never"). 2. Look at the screenshots, attach them here, quote the gate output verbatim. 3. Delete the leftover target and hand back.
Author
Owner

J126c finish. Branch j126-account-summary, head 1cd1b6917203102f8bdd8071349087747f3a4751 (J126's single slice commit; I added no commits, no code changed after its qualification). Not merged, not deployed. Base 7add126405a9; origin/dev has since moved 29 commits and none touch these 17 files.

What the card shows (slice 1 only, all from stored state): passkey count (rows in passkeys for the session's account) and recovery set-up presence (recovery_hash non-empty AND a recovery wrap exists). One authenticated read, POST /auth/account-summary, answering only {user_id,session_incarnation,passkeys,recovery_set_up}. It selects no credential, wrap or verifier value. It is fetched when the card or Account settings opens (no first-paint request, test asserts 0 requests before open). A failed read draws no rows and reopening retries; a reply for another account/session lifetime is dropped. Recovery verification history is not recorded anywhere, so the card says "Set up" with the tip "verification history unavailable" and Settings says "Set up; verification history unavailable": it is never shown as "never verified" and no date is invented. "Add a second" in Settings runs the existing add-start/registration-finish ceremony after a same-account passkey unlock; the key is zeroed in every outcome.

Review by me (diff read in full): auth scoping is in the same SQL statement as the counts (token hash + unlocked=1 + not expired); same-origin and empty-body are enforced (a user_id in the body is a 400); session_incarnation is a domain-separated hash, neither the bearer nor its lookup hash.

Gate: scripts/verify fast on compute01 at 1cd1b6917, exit 0 (log ~/.cache/codex-scratch/j126/evidence/verify-fast.log, 11,979 lines, tail verbatim):

+---------------------------+--------------+
| Rust gate                 | Result       |
+---------------------------+--------------+
| fmt                       | PASS         |
| VT native fmt             | PASS         |
| CLI plugin compile-out    | PASS         |
| clippy                    | PASS         |
| SSH runtime test helper   | PASS         |
| real-binary fixture paths | PASS         |
| test                      | PASS         |
| SSH inode reuse fixture   | PASS         |
| shim process-per-user     | PASS         |
| cargo-deny                | PASS         |
| gate architecture         | PASS         |
| static builds             | PASS         |
+---------------------------+--------------+
JavaScript transfer, critical: identity=1012125 gzip=315490/327680 brotli=263195/273408 bytes
JavaScript transfer, deferred: identity=172742 gzip=60325/61440 brotli=53286/55296 bytes
JavaScript transfer, onDemand: identity=380174 gzip=149809/20480 brotli=133423/18432 bytes
JavaScript transfer, onDemand largest chunk chunk-k9qtejx3.js: gzip=16209/20480 brotli=14425/18432 bytes (class total gzip=149809 brotli=133423, not limited)
 48 pass
 0 fail
 77248 expect() calls
Ran 48 tests across 12 files. [8.83s]
VERIFY_EXIT=0

Static sizes (verbatim): x86_64 size: 22478288 bytes; baseline 22418256; delta +60032 bytes and aarch64 size: 20494552 bytes; baseline 20453280; delta +41272 bytes (report only, D243 amended 2026-11-01). The gate's baseline may lag dev, so I do not claim the whole delta is this slice. The server change is one handler and one query, but I did not measure the pure delta against the exact base.

Browser (production build of the test double, Chromium, compute01, DPR 2): account-summary.spec.ts 5 passed (1440 and 393 in ink and paper, plus the unavailable/foreign-account case). It checks: 0 reads before open, row text, history tooltip, key left edges within 0.5 px, rows inside the card, label/button centre line in Settings within 0.5 px, the phone sidebar closing so "Add a second" is reachable, and the failure toast. Earlier: the 17 existing account-card cases pass.

Screenshots attached below (1440x900 and 393x852, ink and paper; card, then Settings, Account). Read by me for wrong states, not judged as final. Two observations: (1) in account-summary-393-paper.png the pill's hover tip "You have only one passkey" covers the sign-out row, because the test mouse is still over the pill after the click; it is a capture artifact, not a defect I could reproduce by touch, but it should be re-taken without hover if the owner wants clean evidence; (2) at 292 px "Only one, add a second" wraps to two lines, right-aligned.

Not verified / limits: the successful second-passkey ceremony needs a real authenticator; it is covered by unit tests (add-passkey.test.ts) and the failure path in the browser, not end to end. It also signs a new browser session in (the existing login path), and the previous session lasts until it expires. Real iPhone/Safari untested. No performance claim.

Left for later slices (as the owner decided): browser sessions/CLI keys list, recovery check-only step, "Logs in as", profile editing. Cleanup: the previous job's 43 GB target on compute01 is deleted; nothing of mine is running.

**J126c finish.** Branch `j126-account-summary`, head `1cd1b6917203102f8bdd8071349087747f3a4751` (J126's single slice commit; I added no commits, no code changed after its qualification). Not merged, not deployed. Base `7add126405a9`; origin/dev has since moved 29 commits and none touch these 17 files. **What the card shows (slice 1 only, all from stored state):** passkey count (rows in `passkeys` for the session's account) and recovery set-up presence (`recovery_hash` non-empty AND a `recovery` wrap exists). One authenticated read, `POST /auth/account-summary`, answering only `{user_id,session_incarnation,passkeys,recovery_set_up}`. It selects no credential, wrap or verifier value. It is fetched when the card or Account settings opens (no first-paint request, test asserts 0 requests before open). A failed read draws no rows and reopening retries; a reply for another account/session lifetime is dropped. Recovery verification history is not recorded anywhere, so the card says "Set up" with the tip "verification history unavailable" and Settings says "Set up; verification history unavailable": it is never shown as "never verified" and no date is invented. "Add a second" in Settings runs the existing add-start/registration-finish ceremony after a same-account passkey unlock; the key is zeroed in every outcome. **Review by me (diff read in full):** auth scoping is in the same SQL statement as the counts (token hash + `unlocked=1` + not expired); same-origin and empty-body are enforced (a `user_id` in the body is a 400); `session_incarnation` is a domain-separated hash, neither the bearer nor its lookup hash. **Gate: `scripts/verify fast` on compute01 at `1cd1b6917`, exit 0** (log `~/.cache/codex-scratch/j126/evidence/verify-fast.log`, 11,979 lines, tail verbatim): ``` +---------------------------+--------------+ | Rust gate | Result | +---------------------------+--------------+ | fmt | PASS | | VT native fmt | PASS | | CLI plugin compile-out | PASS | | clippy | PASS | | SSH runtime test helper | PASS | | real-binary fixture paths | PASS | | test | PASS | | SSH inode reuse fixture | PASS | | shim process-per-user | PASS | | cargo-deny | PASS | | gate architecture | PASS | | static builds | PASS | +---------------------------+--------------+ JavaScript transfer, critical: identity=1012125 gzip=315490/327680 brotli=263195/273408 bytes JavaScript transfer, deferred: identity=172742 gzip=60325/61440 brotli=53286/55296 bytes JavaScript transfer, onDemand: identity=380174 gzip=149809/20480 brotli=133423/18432 bytes JavaScript transfer, onDemand largest chunk chunk-k9qtejx3.js: gzip=16209/20480 brotli=14425/18432 bytes (class total gzip=149809 brotli=133423, not limited) 48 pass 0 fail 77248 expect() calls Ran 48 tests across 12 files. [8.83s] VERIFY_EXIT=0 ``` Static sizes (verbatim): `x86_64 size: 22478288 bytes; baseline 22418256; delta +60032 bytes` and `aarch64 size: 20494552 bytes; baseline 20453280; delta +41272 bytes` (report only, D243 amended 2026-11-01). The gate's baseline may lag dev, so I do not claim the whole delta is this slice. The server change is one handler and one query, but I did not measure the pure delta against the exact base. **Browser** (production build of the test double, Chromium, compute01, DPR 2): `account-summary.spec.ts` 5 passed (1440 and 393 in ink and paper, plus the unavailable/foreign-account case). It checks: 0 reads before open, row text, history tooltip, key left edges within 0.5 px, rows inside the card, label/button centre line in Settings within 0.5 px, the phone sidebar closing so "Add a second" is reachable, and the failure toast. Earlier: the 17 existing account-card cases pass. **Screenshots attached below** (1440x900 and 393x852, ink and paper; card, then Settings, Account). Read by me for wrong states, not judged as final. Two observations: (1) in `account-summary-393-paper.png` the pill's hover tip "You have only one passkey" covers the sign-out row, because the test mouse is still over the pill after the click; it is a capture artifact, not a defect I could reproduce by touch, but it should be re-taken without hover if the owner wants clean evidence; (2) at 292 px "Only one, add a second" wraps to two lines, right-aligned. **Not verified / limits:** the successful second-passkey ceremony needs a real authenticator; it is covered by unit tests (`add-passkey.test.ts`) and the failure path in the browser, not end to end. It also signs a new browser session in (the existing login path), and the previous session lasts until it expires. Real iPhone/Safari untested. No performance claim. **Left for later slices (as the owner decided):** browser sessions/CLI keys list, recovery check-only step, "Logs in as", profile editing. Cleanup: the previous job's 43 GB target on compute01 is deleted; nothing of mine is running.
Author
Owner

Closed by the orchestrator during the 2026-11-09 night housekeeping (owner order: "close stale prs/issues"). The work has landed on dev, and the latest note here is a finish or merge note (J387 inventory on #436). Reopen if anything is still missing.

Closed by the orchestrator during the 2026-11-09 night housekeeping (owner order: "close stale prs/issues"). The work has landed on dev, and the latest note here is a finish or merge note (J387 inventory on #436). Reopen if anything is still missing.
kayg closed this issue 2026-11-08 19:04:29 +00:00
Sign in to join this conversation.
No labels
launch-qa
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/infraternal#224
No description provided.