Public: anyone can read this; the crew can change it.

Settings › Passkeys speaks the home page's language #9736

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

Settings › Passkeys now uses the home page's layout: headings over rows, with no card, table or empty-state box. The page change was already committed before the interruption. In this session I added the "Try:" line the gate wanted to the commit message and pushed the branch to the fork. Gate, `bun scripts/design-lint.ts`, `bun x tsc --noEmit` and all 34 tests in `test/passkey*.test.ts` and `test/…

shipped production is serving it deploy a5a2512

opened this

Settings › Passkeys now uses the home page's layout: headings over rows, with no card, table or empty-state box. The page change was already committed before the interruption. In this session I added the "Try:" line the gate wanted to the commit message and pushed the branch to the fork. Gate, bun scripts/design-lint.ts, bun x tsc --noEmit and all 34 tests in test/passkey*.test.ts and test/passkeys*.test.ts pass.

  • Your passkeys: a settings-block with one muted sentence and a .setting-list of row()s. Each row shows the passkey's name, when it was added and when it was last used (or "never used"), and its own Remove.
  • Remove: it goes through confirmAction and /-/confirm, so it works with JavaScript off. It still goes through readForm(ctx, "admin"), which checks for same origin. Removing the last way in is still refused with LAST_WAY_IN.
  • New passkey: a second settings-block with the name field, the passkey note and the add button. The add ceremony is unchanged: passkeyJs against the JSON endpoints, each checked for same origin. A <noscript> line says adding needs JavaScript.
  • No passkeys: one plain sentence says so.

No component class is added. docs/design/areas/passkey.md describes the new shape, and test/passkeys-page.test.ts holds it. Two tests were wrong, not the code: test/passkeys.test.ts and test/passkey-recovery.test.ts checked for the old <strong>Name</strong> markup, and now look for each row's aria-label="Remove <name>".

Not done: no screenshot at 390 px. I expect the page to fit a phone because it uses only rows, a field and a button that already fit elsewhere, but I haven't checked it in a browser. The preview's looks will show it.

The keep_plan tool the issue asks for wasn't available in this session, so the issue has no plan on it.

Try: sign in on the preview and open /settings/passkeys. Add a passkey, remove one, then turn JavaScript off and remove another. With only one way in left, Remove is refused.

🤖 Generated with Claude Code

Closes #9726

commented

Settings › Passkeys now uses the home page's layout: headings over rows, with no card, table or empty-state box. The page change was already committed before the interruption. In this session I added the "Try:" line the gate wanted to the commit message and pushed the branch to the fork. Gate, bun scripts/design-lint.ts, bun x tsc --noEmit and all 34 tests in test/passkey*.test.ts and test/passkeys*.test.ts pass.

  • Your passkeys: a settings-block with one muted sentence and a .setting-list of row()s. Each row shows the passkey's name, when it was added and when it was last used (or "never used"), and its own Remove.
  • Remove: it goes through confirmAction and /-/confirm, so it works with JavaScript off. It still goes through readForm(ctx, "admin"), which checks for same origin. Removing the last way in is still refused with LAST_WAY_IN.
  • New passkey: a second settings-block with the name field, the passkey note and the add button. The add ceremony is unchanged: passkeyJs against the JSON endpoints, each checked for same origin. A <noscript> line says adding needs JavaScript.
  • No passkeys: one plain sentence says so.

No component class is added. docs/design/areas/passkey.md describes the new shape, and test/passkeys-page.test.ts holds it. Two tests were wrong, not the code: test/passkeys.test.ts and test/passkey-recovery.test.ts checked for the old <strong>Name</strong> markup, and now look for each row's aria-label="Remove <name>".

Not done: no screenshot at 390 px. I expect the page to fit a phone because it uses only rows, a field and a button that already fit elsewhere, but I haven't checked it in a browser. The preview's looks will show it.

The keep_plan tool the issue asks for wasn't available in this session, so the issue has no plan on it.

Try: sign in on the preview and open /settings/passkeys. Add a passkey, remove one, then turn JavaScript off and remove another. With only one way in left, Remove is refused.

🤖 Generated with Claude Code

commented

Settings › Passkeys now uses the home page's layout: two sections with headings, and plain rows instead of a card and a table.

  • Your passkeys: one short sentence about what a passkey is for, then a row per passkey. Each row shows the name, when it was added and when it was last used (or "never used"), and its own Remove.
  • Remove: it goes through confirmAction and /-/confirm, so it works with JavaScript off. It is still checked for same origin, and removing the last way in is still refused with LAST_WAY_IN.
  • No passkeys: one sentence instead of the empty-state box. It keeps the invitation test/signup-passkey.test.ts checks for: "Your password still works; add a passkey now, as every new account starts with one."
  • New passkey: a second section with the name field, the status line and the add button. The add script is unchanged and every request it makes is still checked for same origin. A <noscript> line says adding needs JavaScript.

No component class is added; docs/design/areas/passkey.md describes the new layout and test/passkeys-page.test.ts checks it. Two older tests looked for the old <strong>Name</strong> markup, so I fixed the tests rather than the code: test/passkeys.test.ts and test/passkey-recovery.test.ts now look for each row's aria-label="Remove <name>".

The last check failed on that test/signup-passkey.test.ts invitation, because the first version shortened the empty-state sentence; it is put back. The check's other failures, in test/etag.test.ts and test/web-origins.test.ts, don't touch this page: both files pass here, and the log says test/web-origins.test.ts "fails only beside others". scripts/gate.ts, scripts/design-lint.ts, tsc --noEmit and all 34 tests in test/passkey*.test.ts and test/passkeys*.test.ts pass.

I haven't checked the page at 390 px in a browser; it uses only rows, a field and a button.

Try: on the preview, sign in with a password-only account and open /settings/passkeys to see the empty state. Add a passkey and remove one, then turn JavaScript off and remove another. When only one way in is left, Remove is refused.

🤖 Generated with Claude Code

Approved

Settings › Passkeys is now two settings-blocks: headings over row()s, then the add form. No card, table or empty-state box, no new component class, and Remove still goes through /-/confirm so it works without JavaScript. Mutations keep their same-origin checks, since the handlers are untouched. The tests would fail without the change, the area doc describes the shape, and the TUI passkeys screen already exists. I did not read the phone and desktop pictures or load the rendered preview page, so the 390 px box is ticked from the markup alone. It reuses .setting-list rows, which wrap and drop the trail to its own line under 640px. Next: (1) look at the 390 px picture once the forge takes it, to confirm the Remove button has a comfortable thumb target. (2) Add the accessible name to the Remove link only if the confirm page repeats the passkey name, so a screen-reader user hears which key they are removing there too.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs