Linear: https://linear.app/engineereddev/issue/ENG-258
What's in this PR (all under apps/web/, plus the lockfile):
devices/:/deviceslist (platform icon, name, "This device" badge, online state fromDeviceDTO.online+device_updated, last-seen relative time), inline rename (PATCH), in-place-confirmed remove (DELETE; removing this device signs out), and a device reducer fordevice_*frames.- "Add a phone" modal:
POST /api/pair→buildPairingUrifrom@pneumo/crypto→ QR (qrcode, error correction M), a 10-minute countdown and New code, closes on a matchingdevice_addedand highlights the new device. The raw URI never enters the DOM, a link or the clipboard. - A browser removed elsewhere lands on
/loginwith "This browser was removed from Pneumo." - Settings: this device's name is editable.
- Reuses the existing device list and the single WebSocket (
home/useHome.ts,App.tsx).
Verify (lead, on cba2c93, based on e5f90f2):
pnpm install --frozen-lockfile→ exit 0mise run check:web→ exit 0 (Tests 80 passed (80); build OK)mise run e2e:web→ exit 0 (20 passed: rename persists across reload, removal →/loginwith the message, QR decoded withjsQRand parsed withparsePairingUri(u= origin,k= session MK), real claim closes the modal and shows the device, axe)- Screenshots (not committed): devices-list, devices-rename, devices-remove-confirm, devices-pairing, devices-paired, devices-revoked-login, devices-self-removed-login, settings-device-name (light/dark/mobile).
Deviations: none. Settings stays in views/SettingsView.tsx.
🤖 Generated with Claude Code