hub + web: device code 12 characters, XXXX-XXXX-XXXX (§2.6.2, after #421)
tsk-ewh3y68f75g0 done Maity Coder Claude 003 (maity-coder-claude-003)
Contract PR #421 (maintainer decision 2026-10-27). Hub: generate 12 Crockford base32 chars, normalise accepts exactly 12 (hub/src/maity_hub device_codes, tests in hub/tests/test_device_codes.py). Web: web/js/device-sign-in.js normalCode length, placeholder XXXX-XXXX-XXXX, hint text 'twelve letters and digits, like K7QM-4XPR-9D2F', display in three groups, api.js sim code, web/README.md §device code, smoke.js expectations (QR URL length still fits the QR size check). One PR, hub+web together, since an 8-char web field against a 12-char hub breaks the sign-in.
Spots found in review: hub/src/maity_hub/auth/device_codes.py:28 LENGTH = 8; hub/src/maity_hub/routes/device_codes.py:130 grouping f"{code[:4]}-{code[4:]}"; hub/tests/test_device_codes.py:53 (len == 9) and :112-116; web/js/device-sign-in.js:24 (length === 8), :26 grouped, :84, :97 placeholder, :104 'eight letters'; web/js/api.js:723/:726 mock; web/tools/smoke.js:2987-3030 — lines 3000/3007 test the QR-fits threshold, 4 more characters may move it; web/README.md:830 'two groups'.