Files
L-Ami-Fiduciaire/_bmad-output/implementation-artifacts/deferred-work.md
Saad Zoubir 88e5803061 feat: add team invitation acceptance flow with email link routing
Implement end-to-end invitation acceptance: neutral entry route validates
token and routes to register (new users), login (existing users), or
auto-accepts (authenticated users). Handles 2FA token survival via
session, email case-insensitive matching, and dedicated error pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 15:16:45 +01:00

1.6 KiB

Deferred Work

From: tech-spec-fix-permission-nudge-bulk-bugs (2026-03-27)

  • Null contact_email guard in BulkNotificationController — If a client exists but has null contact_email, Mail::to(null) will throw. Add a filter for non-null email before sending.
  • Cross-page selection UX on declarations page — Navigating between pages clears selections silently. Consider persisting selections across pages or warning the user.
  • Bulk notify count mismatch UX — When some selected declarations are filtered out (no client), the success message count differs from the selection count with no explanation. Consider showing skipped count.
  • Nudge email template null guardsnudge-notification.blade.php renders $clientName, $declarationType, $dueDate without null fallbacks, producing blank labels.

From: tech-spec-team-invitation-acceptance (2026-03-27)

  • Race condition on concurrent invitation acceptance — Two users clicking the same invitation link simultaneously could both pass isValid() before either sets accepted_at. Fix with SELECT FOR UPDATE or atomic UPDATE WHERE accepted_at IS NULL.
  • Multiple pending invitations per email/workspace — No unique constraint on [workspace_id, email] in team_invitations. Multiple tokens can exist for the same email+workspace. Second token in CreateNewUser path would hit unique constraint on workspace_user and throw.
  • Vue cross-link URLs should come from PHP props — Register.vue and Login.vue construct invitation-aware login/register URLs via JS string interpolation instead of receiving them as props from the controller.