User goal: from the Shop Floor Terminal lock screen, a user with no
PIN (or who forgot their PIN) should be able to set / reset their
own PIN without a manager's help. Today, FpPinSetup runs only from
Preferences which requires being logged in — there's no path from
the lock screen.
Design (approved, with user-picked defaults):
- Tap tile of no-PIN user -> 'Send temporary PIN' button -> email
4-digit code, valid 72 hours -> enter code -> choose new PIN ->
auto-login.
- For existing-PIN users: 3 failed PIN entries -> 'Forgot? Reset
PIN via email' button appears below keypad -> same email flow.
- Both flows merge at: enter temp code -> set new PIN.
- Email goes to res.users.login (or partner_id.email fallback).
No-email-on-file -> 'Contact your manager: <owner>' message.
- Rate limit: 3 requests per user per rolling 60 min.
- Per-code cap: 5 wrong attempts invalidates the code.
- New model fp.tablet.pin.reset stores hashed code + expires_at
with SQL constraint enforcing one-active-row-per-user.
- 2 new endpoints (request_reset_code, verify_reset_code) + extend
existing /fp/tablet/set_pin to accept reset_token alternative
to old_pin.
- Audit: 3 new event_type values on fp.tablet.session.event.
- Reuses existing PBKDF2 helpers, FpPinPad component (mode prop),
fp.notification.template dispatch, mail.template pattern.
Per CLAUDE.md Rule 25 the mail template references ONLY core
res.users fields (object.name, object.email, object.login,
object.company_id) — ctx.code is dispatched as extra_context, not
a model field. Safe at parse-time.
Self-review fixed 2 issues:
- event_kind -> event_type (real field name on fp.tablet.session.event)
- Listed existing event_type values explicitly for context
Spec: docs/superpowers/specs/2026-05-25-tablet-pin-self-service-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>