From 5cb3edc9df88674346c0e8b8ef38a40622ae8c5d Mon Sep 17 00:00:00 2001 From: maebeale Date: Fri, 27 Mar 2026 12:18:11 -0400 Subject: [PATCH 01/18] Add admin account flow diagram page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Visual reference for admins triaging user account issues — covers invite, resend, password reset, email change, lockout, and unlock flows with an interactive cheat sheet. Co-Authored-By: Claude Opus 4.6 --- app/controllers/users_controller.rb | 5 + app/frontend/javascript/controllers/index.js | 3 + .../controllers/toggle_details_controller.js | 13 + app/policies/user_policy.rb | 1 + app/views/users/flow_diagram.html.erb | 459 ++++++++++++++++++ config/routes.rb | 1 + 6 files changed, 482 insertions(+) create mode 100644 app/frontend/javascript/controllers/toggle_details_controller.js create mode 100644 app/views/users/flow_diagram.html.erb diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index f3a17c82f..4f1e6f754 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -305,6 +305,11 @@ def send_welcome_instructions notice: "Invitation sent to #{@user.email}." end + # Visual reference for admins triaging user account challenges + def flow_diagram + authorize! + end + # ========================================================= # PRIVATE # ========================================================= diff --git a/app/frontend/javascript/controllers/index.js b/app/frontend/javascript/controllers/index.js index 48d8f2f28..b68c9a989 100644 --- a/app/frontend/javascript/controllers/index.js +++ b/app/frontend/javascript/controllers/index.js @@ -96,6 +96,9 @@ application.register("tags-combination-highlight", TagsCombinationHighlightContr import TimeframeController from "./timeframe_controller" application.register("timeframe", TimeframeController) +import ToggleDetailsController from "./toggle_details_controller" +application.register("toggle-details", ToggleDetailsController) + import ToggleLockController from "./toggle_lock_controller" application.register("toggle-lock", ToggleLockController) diff --git a/app/frontend/javascript/controllers/toggle_details_controller.js b/app/frontend/javascript/controllers/toggle_details_controller.js new file mode 100644 index 000000000..d0736f843 --- /dev/null +++ b/app/frontend/javascript/controllers/toggle_details_controller.js @@ -0,0 +1,13 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["toggleBtn"] + + toggleAll() { + const details = this.element.querySelectorAll("details") + const allOpen = Array.from(details).every(d => d.open) + + details.forEach(d => d.open = !allOpen) + this.toggleBtnTarget.textContent = allOpen ? "Expand all" : "Collapse all" + } +} diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index 938efd324..93b1c0d76 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -15,6 +15,7 @@ def process_email_change? = admin? def confirm_email_manual? = admin? def process_email_manual? = admin? def send_welcome_instructions? = admin? + def flow_diagram? = admin? def search? = admin? def change_password? = authenticated? def update_password? = authenticated? diff --git a/app/views/users/flow_diagram.html.erb b/app/views/users/flow_diagram.html.erb new file mode 100644 index 000000000..99aa4d16a --- /dev/null +++ b/app/views/users/flow_diagram.html.erb @@ -0,0 +1,459 @@ +<% content_for(:page_bg_class, "admin-only bg-blue-100") %> + +
+
+

Account management flows

+

How invitation, password reset, email change, lockout, and unlock work from an admin's perspective

+
+ + <%# Triage cheat sheet %> +
+
+
+

Admin triage cheat sheet

+ +
+
+
+ +
+ "I never got the invite email" +

Invite may not have been sent, or ended up in spam

+
+ +
+
+

Check Confirmed column on users index:

+
+
+ Clock icon + (hover info) +
    +
  • Invite was sent — ask user to check spam
  • +
  • If still not found, click Invite on users index or Resend invite email on user's Edit
  • +
+ +
+
+ Invite button +
    +
  • No invite has been sent — click Invite to send
  • +
+ +
+
+ Check icon + (hover info) +
    +
  • Already confirmed — user doesn't need an invite, they need a password reset
  • +
+ +
+
+

Note: Confirmation links valid for 3 days, welcome tokens for 30 days

+
+
+
+ +
+ "My invite link doesn't work" +

Confirmation link likely expired (valid for 3 days)

+
+ +
+
+

Check Confirmed column on users index:

+
+
+ Clock icon +
    +
  • Click Invite (or Resend invite email on Edit) to refresh tokens. Old links are now invalid.
  • +
+ +
+
+ Check icon +
    +
  • Already confirmed — user doesn't need an invite, they need a password reset
  • +
+ +
+
+
+
+
+ +
+ "I forgot my password" +

User needs a password reset

+
+ +
+
+
    +
  • User clicks Forgot your password? from login
  • +
  • or admin clicks Send reset password email from user's Edit
  • +
+ +

Note: Reset link valid for 6 hours.
Note: User will be automatically signed in after reset.

+
+
+
+ +
+ "I'm locked out" +

Account likely locked after too many failed attempts

+
+ +
+
+

On users index, look under Access column:

+
+
+ Lock icon +
    +
  • Account is locked — go to user's Edit and click Unlock account (this sets failed attempts back to 0)
  • +
  • Then help user sign in — they may also need a password reset
  • +
+ +
+
+ No lock icon +
    +
  • Forgot password — trigger a password reset
  • +
  • Ban icon — account is inactive, set to active on user's Edit under Account flags
  • +
  • Clock icon in Confirmed — email unconfirmed, send an invite
  • +
+ +
+
+

Note: Accounts don't auto-unlock — needs an admin.

+
+
+
+ +
+ "My reset link expired" +

Reset links expire after 6 hours

+
+ +
+
+
    +
  • User clicks Forgot your password? from login again
  • +
  • or admin clicks Send reset password email from user's Edit
  • +
+ +

Note: Each new reset invalidates previous email links.

+
+
+
+ +
+ "I can sign in but nothing works" +

Account may be unconfirmed or inactive

+
+ +
+
+

On users index, look under Access column:

+
+
+ Lock icon +
    +
  • Account is unconfirmed
  • +
  • Click Invite (or Resend invite email on Edit) to refresh tokens. Old links are now invalid.
  • +
+ +
+
+ Ban icon +
    +
  • Account is inactive (Active: No on user's Show)
  • +
  • On user's Edit, set to active under Account flags
  • +
+
+
+
+
+
+
+
+ + <%# Flow index + Legend card %> +
+

Flows

+
    +
  1. 1. Invite new user
  2. +
  3. 2. Resend invite email
  4. +
  5. 3. Reset password
  6. +
  7. 4. Email change
  8. +
  9. 5. Failed attempts & lockout
  10. +
  11. 6. Manual lock / unlock
  12. +
+ +

Key

+
+
+ Admin action +
+
+ System / email +
+
+ User action +
+
+ Error / blocked state +
+
+ Decision point +
+
+
+ + <%# Flow 1: Invite new user %> +
+

1. Invite new user

+

Admin creates a user and sends welcome instructions • Token valid 30 days • Confirmation token valid 3 days

+
+%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#dbeafe', 'primaryTextColor': '#1e3a5f', 'primaryBorderColor': '#93c5fd', 'secondaryColor': '#dcfce7', 'tertiaryColor': '#fef9c3', 'lineColor': '#94a3b8', 'fontSize': '14px' }}}%%
+flowchart TD
+    A["Admin: creates user
(Users#create)"]:::admin + A2["Admin: clicks 'Invite'
on users index or
'Resend invite email'
on user edit page"]:::admin + B["System generates
welcome_instructions_token
for confirmation + password
(valid 30 days)"]:::system + C["System sends
welcome instructions /
confirmation email
(token valid 3 days)"]:::system + D{"User clicks
'Set your password'
link in email?"}:::decision + E["Confirmations#show
confirms email"]:::system + F["Redirect to
/welcome/:token"]:::system + G["User: sets password
(min 5 chars)"]:::user + H["Account active!
Auto-signed in"]:::success + I["Link expired
(3 days)"]:::error + J["Admin resends invite
from user edit page
(see flow 2)"]:::admin + + A --> A2 --> B --> C --> D + D -- "Yes, within 3 days" --> E --> F --> G --> H + D -- "No / too late" --> I --> J + + linkStyle 8 stroke:#f87171 + + classDef admin fill:#dbeafe,stroke:#93c5fd,color:#1e3a5f + classDef system fill:#dcfce7,stroke:#86efac,color:#14532d + classDef user fill:#fef9c3,stroke:#fde047,color:#713f12 + classDef decision fill:#f3e8ff,stroke:#c4b5fd,color:#4c1d95 + classDef error fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d + classDef success fill:#d1fae5,stroke:#6ee7b7,color:#065f46 +
+
+ + <%# Flow 2: Resend invite %> +
+

2. Resend invite email

+

Same endpoint as invite • Button shows on users index and user edit page when user has not yet confirmed

+
+%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#dbeafe', 'primaryTextColor': '#1e3a5f', 'primaryBorderColor': '#93c5fd', 'secondaryColor': '#dcfce7', 'tertiaryColor': '#fef9c3', 'lineColor': '#94a3b8', 'fontSize': '14px' }}}%%
+flowchart TD
+    A{"User's email
is not confirmed?"}:::decision + B["Button visible:
'Invite' on users index
'Resend invite email'
on user edit page"]:::admin + C["Buttons hidden
(user already confirmed)"]:::error + D["POST /users/:id/
send_welcome_instructions"]:::system + E["Regenerates
welcome_instructions_token
(new 30-day window)"]:::system + F["Sends new
confirmation email
(new 3-day token)"]:::system + G["User receives
fresh link"]:::user + + A -- "Yes" --> B --> D --> E --> F --> G + A -- "No" --> C + + linkStyle 5 stroke:#f87171 + + classDef admin fill:#dbeafe,stroke:#93c5fd,color:#1e3a5f + classDef system fill:#dcfce7,stroke:#86efac,color:#14532d + classDef user fill:#fef9c3,stroke:#fde047,color:#713f12 + classDef decision fill:#f3e8ff,stroke:#c4b5fd,color:#4c1d95 + classDef error fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d +
+
+ + <%# Flow 3: Reset password %> +
+

3. Reset password

+

Admin or user can initiate • Token valid 6 hours • User auto-signed in after reset

+
+%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#dbeafe', 'primaryTextColor': '#1e3a5f', 'primaryBorderColor': '#93c5fd', 'secondaryColor': '#dcfce7', 'tertiaryColor': '#fef9c3', 'lineColor': '#94a3b8', 'fontSize': '14px' }}}%%
+flowchart TD
+    A["Admin: clicks
'Send reset password email'
on user edit page"]:::admin + A2["User: clicks
'Forgot your password?'
on sign-in page"]:::user + B["System generates
reset_password_token
(valid 6 hours)"]:::system + C["Email sent to user
+ FYI notification to admin"]:::system + D{"User clicks
'Reset your password'
within 6 hours?"}:::decision + E["Passwords#edit
shows reset form"]:::system + F["User: enters new
password (min 5 chars)"]:::user + G{"Was user
unconfirmed?"}:::decision + H["Also confirms email"]:::system + I["Password updated
Auto-signed in"]:::success + J["Link expired
(6 hours)"]:::error + K["Admin sends another
from user edit page,
or user requests from
sign-in page"]:::admin + + A --> B + A2 --> B + B --> C --> D + D -- "Yes" --> E --> F --> G + G -- "Yes" --> H --> I + G -- "No" --> I + D -- "No / too late" --> J --> K + + linkStyle 9 stroke:#f87171 + linkStyle 10 stroke:#f87171 + + classDef admin fill:#dbeafe,stroke:#93c5fd,color:#1e3a5f + classDef system fill:#dcfce7,stroke:#86efac,color:#14532d + classDef user fill:#fef9c3,stroke:#fde047,color:#713f12 + classDef decision fill:#f3e8ff,stroke:#c4b5fd,color:#4c1d95 + classDef error fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d + classDef success fill:#d1fae5,stroke:#6ee7b7,color:#065f46 +
+
+ + <%# Flow 4: Email change %> +
+

4. Email change (admin-initiated)

+

Devise reconfirmable • Old email stays active until new email is confirmed • Confirmation token valid 3 days

+
+%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#dbeafe', 'primaryTextColor': '#1e3a5f', 'primaryBorderColor': '#93c5fd', 'secondaryColor': '#dcfce7', 'tertiaryColor': '#fef9c3', 'lineColor': '#94a3b8', 'fontSize': '14px' }}}%%
+flowchart TD
+    A["Admin: changes email
on user edit page"]:::admin + B["Old email stays active
New email saved to
unconfirmed_email"]:::system + C["Interstitial page:
'Email change saved'"]:::system + D{"Admin chooses to
send confirmation
email?"}:::decision + E["Confirmation email
sent to new address
(token valid 3 days)"]:::system + F["Skip — no email sent
Warning icon shown on
users index and user show page"]:::admin + G{"User clicks
confirm link
in email?"}:::decision + H["New email becomes
active, old email
replaced"]:::success + I["Link expired
(3 days)"]:::error + J["Admin can resend
confirmation email from
the user show page"]:::admin + K["User keeps signing in
with old email
until confirmed"]:::user + + A --> B --> C --> D + D -- "Yes" --> E --> G + D -- "No / Skip" --> F --> K + G -- "Yes, within 3 days" --> H + G -- "No / too late" --> I --> J + E --> K + + linkStyle 5 stroke:#f87171 + linkStyle 8 stroke:#f87171 + + classDef admin fill:#dbeafe,stroke:#93c5fd,color:#1e3a5f + classDef system fill:#dcfce7,stroke:#86efac,color:#14532d + classDef user fill:#fef9c3,stroke:#fde047,color:#713f12 + classDef decision fill:#f3e8ff,stroke:#c4b5fd,color:#4c1d95 + classDef error fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d + classDef success fill:#d1fae5,stroke:#6ee7b7,color:#065f46 +
+
+ + <%# Flow 5: Failed passwords & lockout %> +
+

5. Failed password attempts & account lockout

+

10 attempts before lockout • Unlock strategy: none (admin must unlock manually) • Last attempt warning shown

+
+%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#dbeafe', 'primaryTextColor': '#1e3a5f', 'primaryBorderColor': '#93c5fd', 'secondaryColor': '#dcfce7', 'tertiaryColor': '#fef9c3', 'lineColor': '#94a3b8', 'fontSize': '14px' }}}%%
+flowchart TD
+    A["User: enters
wrong password
on sign-in page"]:::user + B["failed_attempts
counter +1"]:::system + C{"failed_attempts
== 9?"}:::decision + D["Warning shown on
sign-in page:
'Last attempt before
account is locked'"]:::error + E{"failed_attempts
≥ 10?"}:::decision + F["Account LOCKED
(locked_at = now)"]:::error + G["User sees on sign-in
page: 'Account is locked'"]:::error + H["User can
try again"]:::user + I["Admin: sees lock icon
in Access col on
users index"]:::admin + J["Admin: clicks
'Unlock account'
on user edit page"]:::admin + K["locked_at cleared
failed_attempts reset to 0"]:::system + L["User can
sign in again"]:::success + + A --> B --> C + C -- "Yes" --> D --> E + C -- "No" --> E + E -- "Yes" --> F --> G --> I --> J --> K --> L + E -- "No" --> H --> A + + linkStyle 4 stroke:#f87171 + linkStyle 11 stroke:#f87171 + + classDef admin fill:#dbeafe,stroke:#93c5fd,color:#1e3a5f + classDef system fill:#dcfce7,stroke:#86efac,color:#14532d + classDef user fill:#fef9c3,stroke:#fde047,color:#713f12 + classDef decision fill:#f3e8ff,stroke:#c4b5fd,color:#4c1d95 + classDef error fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d + classDef success fill:#d1fae5,stroke:#6ee7b7,color:#065f46 +
+
+ + <%# Flow 6: Admin manual lock/unlock %> +
+

6. Admin manual lock / unlock

+

Admin can lock or unlock any account at any time, independent of failed attempts

+
+%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#dbeafe', 'primaryTextColor': '#1e3a5f', 'primaryBorderColor': '#93c5fd', 'secondaryColor': '#dcfce7', 'tertiaryColor': '#fef9c3', 'lineColor': '#94a3b8', 'fontSize': '14px' }}}%%
+flowchart LR
+    A["Admin: user edit page"]:::admin
+    B{"Account
currently
locked?"}:::decision + C["Button: 'Unlock account'"]:::admin + D["Button: 'Lock account'"]:::admin + E["Account unlocked
failed_attempts reset to 0"]:::system + F["Account locked
immediately"]:::system + + A --> B + B -- "Yes" --> C --> E + B -- "No" --> D --> F + + linkStyle 3 stroke:#f87171 + + classDef admin fill:#dbeafe,stroke:#93c5fd,color:#1e3a5f + classDef system fill:#dcfce7,stroke:#86efac,color:#14532d + classDef decision fill:#f3e8ff,stroke:#c4b5fd,color:#4c1d95 +
+
+ +

+ Generated from codebase analysis • Source: UsersController, WelcomeController, ConfirmationsController, PasswordsController, User model, Devise config +

+
+ + diff --git a/config/routes.rb b/config/routes.rb index 470d39f72..eab4cd7bd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -35,6 +35,7 @@ resources :users, only: [ :new, :index, :show, :edit, :update, :create, :destroy ] do collection do get :check_duplicates + get :flow_diagram end member do post :send_reset_password_instructions From 7668f6d4d1ee1fe921d94f3a32fda99e70eb0e66 Mon Sep 17 00:00:00 2001 From: maebeale Date: Fri, 27 Mar 2026 12:33:13 -0400 Subject: [PATCH 02/18] Add flow_diagram view to page_bg_class spec Co-Authored-By: Claude Opus 4.6 --- spec/views/page_bg_class_alignment_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/views/page_bg_class_alignment_spec.rb b/spec/views/page_bg_class_alignment_spec.rb index 5cfa33794..6cb38d478 100644 --- a/spec/views/page_bg_class_alignment_spec.rb +++ b/spec/views/page_bg_class_alignment_spec.rb @@ -92,6 +92,7 @@ "app/views/admin/ahoy_activities/index.html.erb" => "admin-only bg-blue-100", "app/views/admin/analytics/index.html.erb" => "admin-only bg-blue-100", "app/views/bookmarks/tally.html.erb" => "admin-only bg-blue-100", + "app/views/users/flow_diagram.html.erb" => "admin-only bg-blue-100", "app/views/dedupes/index.html.erb" => "admin-only bg-blue-100", "app/views/dedupes/preview.html.erb" => "admin-only bg-blue-100", "app/views/taggings/matrix.html.erb" => "admin-only bg-blue-100", From 0e05846184fb3519f2fb5edebffb03189903e8b0 Mon Sep 17 00:00:00 2001 From: maebeale Date: Fri, 27 Mar 2026 12:54:09 -0400 Subject: [PATCH 03/18] Add link to account flow diagram from users index Co-Authored-By: Claude Opus 4.6 --- app/views/users/index.html.erb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 95f98c8f0..ba2ec4905 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -8,6 +8,9 @@

<%= User.model_name.human.pluralize %>

+ <%= link_to flow_diagram_users_path, class: "text-xs text-blue-600 hover:text-blue-800 hover:underline" do %> + Account flow diagram + <% end %>
From 7834f86679b6f7774c00417a2bc9c88eac4c8561 Mon Sep 17 00:00:00 2001 From: maebeale Date: Fri, 27 Mar 2026 12:56:45 -0400 Subject: [PATCH 04/18] Move flow diagram link next to New User button Avoids Turbo frame refresh hiding the link. Co-Authored-By: Claude Opus 4.6 --- app/views/users/index.html.erb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index ba2ec4905..50bd2c861 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -8,12 +8,12 @@

<%= User.model_name.human.pluralize %>

- <%= link_to flow_diagram_users_path, class: "text-xs text-blue-600 hover:text-blue-800 hover:underline" do %> - Account flow diagram - <% end %>
-
+
+ <%= link_to flow_diagram_users_path, class: "text-xs text-blue-600 hover:text-blue-800 hover:underline" do %> + Account flow diagram + <% end %> <% if allowed_to?(:new?, User) %> <%= link_to "New #{User.model_name.human.downcase}", new_user_path, From 4c9751a0d92bb36fe8922ba672869c07f1e93dbd Mon Sep 17 00:00:00 2001 From: maebeale Date: Fri, 27 Mar 2026 12:57:51 -0400 Subject: [PATCH 05/18] Match flow diagram link style to other text links in app Co-Authored-By: Claude Opus 4.6 --- app/views/users/index.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 50bd2c861..69fdfc13b 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -11,7 +11,7 @@
- <%= link_to flow_diagram_users_path, class: "text-xs text-blue-600 hover:text-blue-800 hover:underline" do %> + <%= link_to flow_diagram_users_path, class: "text-sm text-gray-500 hover:text-gray-700" do %> Account flow diagram <% end %> <% if allowed_to?(:new?, User) %> From daf08727bc73896e9a1da9e7205d388eca1cfb4a Mon Sep 17 00:00:00 2001 From: maebeale Date: Fri, 27 Mar 2026 12:59:22 -0400 Subject: [PATCH 06/18] Wrap flow diagram page in card, add Home/Users nav links Matches the card layout pattern used on other admin pages like visits. Co-Authored-By: Claude Opus 4.6 --- app/views/users/flow_diagram.html.erb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/views/users/flow_diagram.html.erb b/app/views/users/flow_diagram.html.erb index 99aa4d16a..5fef59ad2 100644 --- a/app/views/users/flow_diagram.html.erb +++ b/app/views/users/flow_diagram.html.erb @@ -1,10 +1,14 @@ <% content_for(:page_bg_class, "admin-only bg-blue-100") %> -
-
-

Account management flows

-

How invitation, password reset, email change, lockout, and unlock work from an admin's perspective

+
+
+

Account management flows

+
+ <%= link_to "Home", root_path, class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %> + <%= link_to "Users", users_path, class: "text-sm text-gray-500 hover:text-gray-700 px-2 py-1" %> +
+

How invitation, password reset, email change, lockout, and unlock work from an admin's perspective

<%# Triage cheat sheet %>
@@ -420,7 +424,7 @@ flowchart LR

- Generated from codebase analysis • Source: UsersController, WelcomeController, ConfirmationsController, PasswordsController, User model, Devise config + Source: UsersController, WelcomeController, ConfirmationsController, PasswordsController, User model, Devise config

From 18873461b4bc856d65dd1e9de6f313fd53a0a8e8 Mon Sep 17 00:00:00 2001 From: maebeale Date: Fri, 27 Mar 2026 13:04:01 -0400 Subject: [PATCH 07/18] Fix diagram accuracy, add icons to flowcharts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Flow 1: Add missing welcome token expiry decision diamond after confirmation — shows redirect to password reset when token expired - Cheat sheet card 6: Fix lock icon incorrectly described as "unconfirmed" — lock means locked, clock means unconfirmed - Fix fa fa-check-circle → fa-solid fa-check-circle (4 occurrences) - Add Font Awesome icons to all flowchart nodes for visual scanning Co-Authored-By: Claude Opus 4.6 --- app/views/users/flow_diagram.html.erb | 139 ++++++++++++++------------ 1 file changed, 77 insertions(+), 62 deletions(-) diff --git a/app/views/users/flow_diagram.html.erb b/app/views/users/flow_diagram.html.erb index 5fef59ad2..8dc9b87f4 100644 --- a/app/views/users/flow_diagram.html.erb +++ b/app/views/users/flow_diagram.html.erb @@ -49,7 +49,7 @@
- Check icon + Check icon (hover info)
  • Already confirmed — user doesn't need an invite, they need a password reset
  • @@ -79,7 +79,7 @@
- Check icon + Check icon
  • Already confirmed — user doesn't need an invite, they need a password reset
@@ -163,21 +163,30 @@
-

On users index, look under Access column:

+

Check users index columns:

Lock icon + (Access column)
    -
  • Account is unconfirmed
  • -
  • Click Invite (or Resend invite email on Edit) to refresh tokens. Old links are now invalid.
  • +
  • Account is locked — Unlock account on user's Edit first
  • +
  • Then send a password reset if needed
  • +
+ +
+
+ Clock icon + (Confirmed column) +
    +
  • Email is unconfirmed — click Invite (or Resend invite email on Edit)
Ban icon + (Access column)
    -
  • Account is inactive (Active: No on user's Show)
  • -
  • On user's Edit, set to active under Account flags
  • +
  • Account is inactive — set to active on user's Edit under Account flags
@@ -226,23 +235,29 @@
 %%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#dbeafe', 'primaryTextColor': '#1e3a5f', 'primaryBorderColor': '#93c5fd', 'secondaryColor': '#dcfce7', 'tertiaryColor': '#fef9c3', 'lineColor': '#94a3b8', 'fontSize': '14px' }}}%%
 flowchart TD
-    A["Admin: creates user
(Users#create)"]:::admin - A2["Admin: clicks 'Invite'
on users index or
'Resend invite email'
on user edit page"]:::admin - B["System generates
welcome_instructions_token
for confirmation + password
(valid 30 days)"]:::system - C["System sends
welcome instructions /
confirmation email
(token valid 3 days)"]:::system - D{"User clicks
'Set your password'
link in email?"}:::decision - E["Confirmations#show
confirms email"]:::system - F["Redirect to
/welcome/:token"]:::system - G["User: sets password
(min 5 chars)"]:::user - H["Account active!
Auto-signed in"]:::success - I["Link expired
(3 days)"]:::error - J["Admin resends invite
from user edit page
(see flow 2)"]:::admin + A["fa:fa-user-plus Admin: creates user
(Users#create)"]:::admin + A2["fa:fa-paper-plane Admin: clicks 'Invite'
on users index or
'Resend invite email'
on user edit page"]:::admin + B["fa:fa-key System generates
welcome_instructions_token
for confirmation + password
(valid 30 days)"]:::system + C["fa:fa-envelope System sends
welcome instructions /
confirmation email
(token valid 3 days)"]:::system + D{"fa:fa-clock User clicks
'Set your password'
link in email?"}:::decision + E["fa:fa-check-circle Confirmations#show
confirms email"]:::system + F{"fa:fa-key Welcome token
still valid?
(30 days)"}:::decision + F2["Redirect to
/welcome/:token"]:::system + G["fa:fa-lock User: sets password
(min 5 chars)"]:::user + H["fa:fa-circle-check Account active!
Auto-signed in"]:::success + I["fa:fa-clock Link expired
(3 days)"]:::error + J["fa:fa-rotate Admin resends invite
from user edit page
(see flow 2)"]:::admin + STUCK["fa:fa-triangle-exclamation Redirected to
password reset form
(user has no known password)"]:::error + FIX["fa:fa-envelope User requests
password reset
(see flow 3)"]:::admin A --> A2 --> B --> C --> D - D -- "Yes, within 3 days" --> E --> F --> G --> H + D -- "Yes, within 3 days" --> E --> F + F -- "Yes" --> F2 --> G --> H + F -- "No (expired)" --> STUCK --> FIX D -- "No / too late" --> I --> J - linkStyle 8 stroke:#f87171 + linkStyle 10 stroke:#f87171 + linkStyle 11 stroke:#f87171 classDef admin fill:#dbeafe,stroke:#93c5fd,color:#1e3a5f classDef system fill:#dcfce7,stroke:#86efac,color:#14532d @@ -260,13 +275,13 @@ flowchart TD
 %%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#dbeafe', 'primaryTextColor': '#1e3a5f', 'primaryBorderColor': '#93c5fd', 'secondaryColor': '#dcfce7', 'tertiaryColor': '#fef9c3', 'lineColor': '#94a3b8', 'fontSize': '14px' }}}%%
 flowchart TD
-    A{"User's email
is not confirmed?"}:::decision - B["Button visible:
'Invite' on users index
'Resend invite email'
on user edit page"]:::admin - C["Buttons hidden
(user already confirmed)"]:::error + A{"fa:fa-question User's email
is not confirmed?"}:::decision + B["fa:fa-paper-plane Button visible:
'Invite' on users index
'Resend invite email'
on user edit page"]:::admin + C["fa:fa-eye-slash Buttons hidden
(user already confirmed)"]:::error D["POST /users/:id/
send_welcome_instructions"]:::system - E["Regenerates
welcome_instructions_token
(new 30-day window)"]:::system - F["Sends new
confirmation email
(new 3-day token)"]:::system - G["User receives
fresh link"]:::user + E["fa:fa-key Regenerates
welcome_instructions_token
(new 30-day window)"]:::system + F["fa:fa-envelope Sends new
confirmation email
(new 3-day token)"]:::system + G["fa:fa-inbox User receives
fresh link"]:::user A -- "Yes" --> B --> D --> E --> F --> G A -- "No" --> C @@ -288,18 +303,18 @@ flowchart TD
 %%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#dbeafe', 'primaryTextColor': '#1e3a5f', 'primaryBorderColor': '#93c5fd', 'secondaryColor': '#dcfce7', 'tertiaryColor': '#fef9c3', 'lineColor': '#94a3b8', 'fontSize': '14px' }}}%%
 flowchart TD
-    A["Admin: clicks
'Send reset password email'
on user edit page"]:::admin - A2["User: clicks
'Forgot your password?'
on sign-in page"]:::user - B["System generates
reset_password_token
(valid 6 hours)"]:::system - C["Email sent to user
+ FYI notification to admin"]:::system - D{"User clicks
'Reset your password'
within 6 hours?"}:::decision + A["fa:fa-paper-plane Admin: clicks
'Send reset password email'
on user edit page"]:::admin + A2["fa:fa-question User: clicks
'Forgot your password?'
on sign-in page"]:::user + B["fa:fa-key System generates
reset_password_token
(valid 6 hours)"]:::system + C["fa:fa-envelope Email sent to user
+ FYI notification to admin"]:::system + D{"fa:fa-clock User clicks
'Reset your password'
within 6 hours?"}:::decision E["Passwords#edit
shows reset form"]:::system - F["User: enters new
password (min 5 chars)"]:::user - G{"Was user
unconfirmed?"}:::decision - H["Also confirms email"]:::system - I["Password updated
Auto-signed in"]:::success - J["Link expired
(6 hours)"]:::error - K["Admin sends another
from user edit page,
or user requests from
sign-in page"]:::admin + F["fa:fa-lock User: enters new
password (min 5 chars)"]:::user + G{"fa:fa-check-circle Was user
unconfirmed?"}:::decision + H["fa:fa-envelope-circle-check Also confirms email"]:::system + I["fa:fa-circle-check Password updated
Auto-signed in"]:::success + J["fa:fa-clock Link expired
(6 hours)"]:::error + K["fa:fa-rotate Admin sends another
from user edit page,
or user requests from
sign-in page"]:::admin A --> B A2 --> B @@ -328,17 +343,17 @@ flowchart TD
 %%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#dbeafe', 'primaryTextColor': '#1e3a5f', 'primaryBorderColor': '#93c5fd', 'secondaryColor': '#dcfce7', 'tertiaryColor': '#fef9c3', 'lineColor': '#94a3b8', 'fontSize': '14px' }}}%%
 flowchart TD
-    A["Admin: changes email
on user edit page"]:::admin + A["fa:fa-pen-to-square Admin: changes email
on user edit page"]:::admin B["Old email stays active
New email saved to
unconfirmed_email"]:::system C["Interstitial page:
'Email change saved'"]:::system - D{"Admin chooses to
send confirmation
email?"}:::decision - E["Confirmation email
sent to new address
(token valid 3 days)"]:::system - F["Skip — no email sent
Warning icon shown on
users index and user show page"]:::admin - G{"User clicks
confirm link
in email?"}:::decision - H["New email becomes
active, old email
replaced"]:::success - I["Link expired
(3 days)"]:::error - J["Admin can resend
confirmation email from
the user show page"]:::admin - K["User keeps signing in
with old email
until confirmed"]:::user + D{"fa:fa-question Admin chooses to
send confirmation
email?"}:::decision + E["fa:fa-envelope Confirmation email
sent to new address
(token valid 3 days)"]:::system + F["fa:fa-triangle-exclamation Skip — no email sent
Warning icon shown on
users index and user show page"]:::admin + G{"fa:fa-clock User clicks
confirm link
in email?"}:::decision + H["fa:fa-circle-check New email becomes
active, old email
replaced"]:::success + I["fa:fa-clock Link expired
(3 days)"]:::error + J["fa:fa-rotate Admin can resend
confirmation email from
the user show page"]:::admin + K["fa:fa-right-to-bracket User keeps signing in
with old email
until confirmed"]:::user A --> B --> C --> D D -- "Yes" --> E --> G @@ -366,18 +381,18 @@ flowchart TD
 %%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#dbeafe', 'primaryTextColor': '#1e3a5f', 'primaryBorderColor': '#93c5fd', 'secondaryColor': '#dcfce7', 'tertiaryColor': '#fef9c3', 'lineColor': '#94a3b8', 'fontSize': '14px' }}}%%
 flowchart TD
-    A["User: enters
wrong password
on sign-in page"]:::user - B["failed_attempts
counter +1"]:::system + A["fa:fa-xmark User: enters
wrong password
on sign-in page"]:::user + B["fa:fa-plus failed_attempts
counter +1"]:::system C{"failed_attempts
== 9?"}:::decision - D["Warning shown on
sign-in page:
'Last attempt before
account is locked'"]:::error + D["fa:fa-triangle-exclamation Warning shown on
sign-in page:
'Last attempt before
account is locked'"]:::error E{"failed_attempts
≥ 10?"}:::decision - F["Account LOCKED
(locked_at = now)"]:::error - G["User sees on sign-in
page: 'Account is locked'"]:::error - H["User can
try again"]:::user - I["Admin: sees lock icon
in Access col on
users index"]:::admin - J["Admin: clicks
'Unlock account'
on user edit page"]:::admin - K["locked_at cleared
failed_attempts reset to 0"]:::system - L["User can
sign in again"]:::success + F["fa:fa-lock Account LOCKED
(locked_at = now)"]:::error + G["fa:fa-ban User sees on sign-in
page: 'Account is locked'"]:::error + H["fa:fa-rotate User can
try again"]:::user + I["fa:fa-lock Admin: sees lock icon
in Access col on
users index"]:::admin + J["fa:fa-unlock Admin: clicks
'Unlock account'
on user edit page"]:::admin + K["fa:fa-eraser locked_at cleared
failed_attempts reset to 0"]:::system + L["fa:fa-circle-check User can
sign in again"]:::success A --> B --> C C -- "Yes" --> D --> E @@ -404,12 +419,12 @@ flowchart TD
 %%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#dbeafe', 'primaryTextColor': '#1e3a5f', 'primaryBorderColor': '#93c5fd', 'secondaryColor': '#dcfce7', 'tertiaryColor': '#fef9c3', 'lineColor': '#94a3b8', 'fontSize': '14px' }}}%%
 flowchart LR
-    A["Admin: user edit page"]:::admin
-    B{"Account
currently
locked?"}:::decision - C["Button: 'Unlock account'"]:::admin - D["Button: 'Lock account'"]:::admin - E["Account unlocked
failed_attempts reset to 0"]:::system - F["Account locked
immediately"]:::system + A["fa:fa-pen-to-square Admin: user edit page"]:::admin + B{"fa:fa-question Account
currently
locked?"}:::decision + C["fa:fa-unlock Button: 'Unlock account'"]:::admin + D["fa:fa-lock Button: 'Lock account'"]:::admin + E["fa:fa-eraser Account unlocked
failed_attempts reset to 0"]:::system + F["fa:fa-lock Account locked
immediately"]:::system A --> B B -- "Yes" --> C --> E From ea2cd4352a691a6601b4ff74eddc36d3b7461c1e Mon Sep 17 00:00:00 2001 From: maebeale Date: Fri, 27 Mar 2026 13:25:08 -0400 Subject: [PATCH 08/18] Swap cheat sheet card order: locked out before forgot password Co-Authored-By: Claude Opus 4.6 --- app/views/users/flow_diagram.html.erb | 34 +++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/app/views/users/flow_diagram.html.erb b/app/views/users/flow_diagram.html.erb index 8dc9b87f4..53da5b062 100644 --- a/app/views/users/flow_diagram.html.erb +++ b/app/views/users/flow_diagram.html.erb @@ -88,23 +88,6 @@
-
- -
- "I forgot my password" -

User needs a password reset

-
- -
-
-
    -
  • User clicks Forgot your password? from login
  • -
  • or admin clicks Send reset password email from user's Edit
  • -
- -

Note: Reset link valid for 6 hours.
Note: User will be automatically signed in after reset.

-
-
@@ -137,6 +120,23 @@

Note: Accounts don't auto-unlock — needs an admin.

+
+ +
+ "I forgot my password" +

User needs a password reset

+
+ +
+
+
    +
  • User clicks Forgot your password? from login
  • +
  • or admin clicks Send reset password email from user's Edit
  • +
+ +

Note: Reset link valid for 6 hours.
Note: User will be automatically signed in after reset.

+
+
From 994eafb4a152d9999b6f457bf93e716db2cca1cf Mon Sep 17 00:00:00 2001 From: maebeale Date: Fri, 27 Mar 2026 13:38:37 -0400 Subject: [PATCH 09/18] Add hover info labels to invite link subcards for consistency Co-Authored-By: Claude Opus 4.6 --- app/views/users/flow_diagram.html.erb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/users/flow_diagram.html.erb b/app/views/users/flow_diagram.html.erb index 53da5b062..f75eb91f2 100644 --- a/app/views/users/flow_diagram.html.erb +++ b/app/views/users/flow_diagram.html.erb @@ -73,6 +73,7 @@
Clock icon + (hover info)
  • Click Invite (or Resend invite email on Edit) to refresh tokens. Old links are now invalid.
@@ -80,6 +81,7 @@
Check icon + (hover info)
  • Already confirmed — user doesn't need an invite, they need a password reset
From 854cc8095481f82ac52409fb291f9de08594ffe5 Mon Sep 17 00:00:00 2001 From: maebeale Date: Fri, 27 Mar 2026 13:46:00 -0400 Subject: [PATCH 10/18] Make invite link subcards text match invite email card style Co-Authored-By: Claude Opus 4.6 --- app/views/users/flow_diagram.html.erb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/users/flow_diagram.html.erb b/app/views/users/flow_diagram.html.erb index f75eb91f2..c5e1a06fa 100644 --- a/app/views/users/flow_diagram.html.erb +++ b/app/views/users/flow_diagram.html.erb @@ -75,7 +75,8 @@ Clock icon (hover info)
    -
  • Click Invite (or Resend invite email on Edit) to refresh tokens. Old links are now invalid.
  • +
  • Invite was sent but link expired — click Invite on users index or Resend invite email on user's Edit
  • +
  • Old links are now invalid
From 602df32e02cf9591a650582c9134a3153f8ef319 Mon Sep 17 00:00:00 2001 From: maebeale Date: Fri, 27 Mar 2026 13:50:32 -0400 Subject: [PATCH 11/18] Match invite link subcards to invite email card content Co-Authored-By: Claude Opus 4.6 --- app/views/users/flow_diagram.html.erb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/views/users/flow_diagram.html.erb b/app/views/users/flow_diagram.html.erb index c5e1a06fa..6e29fdd8f 100644 --- a/app/views/users/flow_diagram.html.erb +++ b/app/views/users/flow_diagram.html.erb @@ -75,11 +75,18 @@ Clock icon (hover info)
    -
  • Invite was sent but link expired — click Invite on users index or Resend invite email on user's Edit
  • -
  • Old links are now invalid
  • +
  • Invite was sent — ask user to check spam
  • +
  • If still not found, click Invite on users index or Resend invite email on user's Edit
+
+ Invite button +
    +
  • No invite has been sent — click Invite to send
  • +
+ +
Check icon (hover info) @@ -89,6 +96,7 @@
+

Note: Confirmation links valid for 3 days, welcome tokens for 30 days

From 301427dd31b3e7833e15a0bcc9d991c12bd79063 Mon Sep 17 00:00:00 2001 From: maebeale Date: Fri, 27 Mar 2026 13:51:48 -0400 Subject: [PATCH 12/18] Fix invite link card: remove Invite button subcard, clarify expired link User already received the email so Invite button scenario doesn't apply. Co-Authored-By: Claude Opus 4.6 --- app/views/users/flow_diagram.html.erb | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/app/views/users/flow_diagram.html.erb b/app/views/users/flow_diagram.html.erb index 6e29fdd8f..d81c125d5 100644 --- a/app/views/users/flow_diagram.html.erb +++ b/app/views/users/flow_diagram.html.erb @@ -75,18 +75,11 @@ Clock icon (hover info)
    -
  • Invite was sent — ask user to check spam
  • -
  • If still not found, click Invite on users index or Resend invite email on user's Edit
  • +
  • Confirmation link expired — click Invite on users index or Resend invite email on user's Edit
  • +
  • This generates fresh tokens and invalidates old links
-
- Invite button -
    -
  • No invite has been sent — click Invite to send
  • -
- -
Check icon (hover info) From b86de8d50912b5610bc9d0bcb566f12fa725f0ad Mon Sep 17 00:00:00 2001 From: maebeale Date: Fri, 27 Mar 2026 13:55:28 -0400 Subject: [PATCH 13/18] Fix card 6 and card 3 text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove lock icon subcard from "I can sign in but nothing works" — locked users can't sign in, so that scenario belongs in "I'm locked out" card only. Also standardize Access column label text. Co-Authored-By: Claude Opus 4.6 --- app/views/users/flow_diagram.html.erb | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/app/views/users/flow_diagram.html.erb b/app/views/users/flow_diagram.html.erb index d81c125d5..c2e76c7eb 100644 --- a/app/views/users/flow_diagram.html.erb +++ b/app/views/users/flow_diagram.html.erb @@ -101,7 +101,7 @@
-

On users index, look under Access column:

+

Check Access column on users index:

Lock icon @@ -169,15 +169,6 @@

Check users index columns:

-
- Lock icon - (Access column) -
    -
  • Account is locked — Unlock account on user's Edit first
  • -
  • Then send a password reset if needed
  • -
- -
Clock icon (Confirmed column) From 96ce6b682f87d998fbd55ef05d0978ec2ce2b08d Mon Sep 17 00:00:00 2001 From: maebeale Date: Fri, 27 Mar 2026 13:56:25 -0400 Subject: [PATCH 14/18] Rename reset link card to password reset link Co-Authored-By: Claude Opus 4.6 --- app/views/users/flow_diagram.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/users/flow_diagram.html.erb b/app/views/users/flow_diagram.html.erb index c2e76c7eb..c682c8faa 100644 --- a/app/views/users/flow_diagram.html.erb +++ b/app/views/users/flow_diagram.html.erb @@ -144,7 +144,7 @@
- "My reset link expired" + "My password reset link expired"

Reset links expire after 6 hours

From 57c51e54f4a0979fde7581002743fe940b9423f9 Mon Sep 17 00:00:00 2001 From: maebeale Date: Fri, 27 Mar 2026 14:00:28 -0400 Subject: [PATCH 15/18] Replace card 6 with confirmed-but-no-password scenario Covers the edge case where a user confirmed their email but the welcome token expired before they set a password. Admin fix: send a password reset email. Co-Authored-By: Claude Opus 4.6 --- app/views/users/flow_diagram.html.erb | 30 +++++++++------------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/app/views/users/flow_diagram.html.erb b/app/views/users/flow_diagram.html.erb index c682c8faa..fe0ef6dac 100644 --- a/app/views/users/flow_diagram.html.erb +++ b/app/views/users/flow_diagram.html.erb @@ -161,30 +161,20 @@
- "I can sign in but nothing works" -

Account may be unconfirmed or inactive

+ "I confirmed my email but can't sign in" +

Email confirmed but user never set a password

-

Check users index columns:

-
-
- Clock icon - (Confirmed column) -
    -
  • Email is unconfirmed — click Invite (or Resend invite email on Edit)
  • -
- -
-
- Ban icon - (Access column) -
    -
  • Account is inactive — set to active on user's Edit under Account flags
  • -
-
-
+

User clicked the confirmation link but the welcome token expired before they set a password. They see "Invalid email or password" because they never had one.

+
    +
  • Check Confirmed column on users index — should show (confirmed)
  • +
  • Admin clicks Send reset password email from user's Edit
  • +
  • User sets a password via the reset link — this also auto-signs them in
  • +
+ +

Note: Accounts are created with a random password the user doesn't know. The welcome flow is the only way they set a real one.

From d93fafd8f0d66be668056b33f4f1347cd63f0a9d Mon Sep 17 00:00:00 2001 From: maebeale Date: Sat, 28 Mar 2026 07:48:22 -0400 Subject: [PATCH 16/18] Refine cheat sheet card text for clarity and consistency - Fix hover text to "(hover on index for details)" - Remove Invite button reference from clock icon subcards (mutually exclusive) - Simplify locked account and password reset card text - Reorder "No lock icon" options: ban, clock, then forgot password - Make reset/forgot password actions single-line paragraphs - Add "each new reset invalidates previous links" note Co-Authored-By: Claude Opus 4.6 --- app/views/users/flow_diagram.html.erb | 43 ++++++++++++--------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/app/views/users/flow_diagram.html.erb b/app/views/users/flow_diagram.html.erb index fe0ef6dac..6ce2fda59 100644 --- a/app/views/users/flow_diagram.html.erb +++ b/app/views/users/flow_diagram.html.erb @@ -34,10 +34,10 @@
Clock icon - (hover info) + (hover on index for details)
  • Invite was sent — ask user to check spam
  • -
  • If still not found, click Invite on users index or Resend invite email on user's Edit
  • +
  • Otherwise click Resend invite email on user's Edit
@@ -50,7 +50,7 @@
Check icon - (hover info) + (hover on index for details)
  • Already confirmed — user doesn't need an invite, they need a password reset
@@ -73,16 +73,17 @@
Clock icon - (hover info) + (hover on index for details)
    -
  • Confirmation link expired — click Invite on users index or Resend invite email on user's Edit
  • -
  • This generates fresh tokens and invalidates old links
  • +
  • Invite was sent but not yet confirmed
  • +
  • Click Resend invite email on user's Edit
+

Note: This invalidates old links

Check icon - (hover info) + (hover on index for details)
  • Already confirmed — user doesn't need an invite, they need a password reset
@@ -106,17 +107,17 @@
Lock icon
    -
  • Account is locked — go to user's Edit and click Unlock account (this sets failed attempts back to 0)
  • -
  • Then help user sign in — they may also need a password reset
  • +
  • Account is locked — go to user's Edit and click Unlock account (sets failed attempts back to 0)
  • +
  • User may also need a password reset
No lock icon
    -
  • Forgot password — trigger a password reset
  • Ban icon — account is inactive, set to active on user's Edit under Account flags
  • -
  • Clock icon in Confirmed — email unconfirmed, send an invite
  • +
  • Clock icon in Confirmed — email unconfirmed, send an Invite
  • +
  • Maybe they mean they forgot their password?
@@ -133,12 +134,9 @@
-
    -
  • User clicks Forgot your password? from login
  • -
  • or admin clicks Send reset password email from user's Edit
  • -
- -

Note: Reset link valid for 6 hours.
Note: User will be automatically signed in after reset.

+

User clicks Forgot your password? from login or admin clicks Send reset password email from user's Edit

+ +

Note: Reset links expire after 6 hours.
Note: Each new reset invalidates previous email links.
Note: User will be automatically signed in after reset.

@@ -150,11 +148,8 @@
-
    -
  • User clicks Forgot your password? from login again
  • -
  • or admin clicks Send reset password email from user's Edit
  • -
- +

User clicks Forgot your password? again or admin clicks Send reset password email from user's Edit

+

Note: Each new reset invalidates previous email links.

@@ -170,11 +165,11 @@

User clicked the confirmation link but the welcome token expired before they set a password. They see "Invalid email or password" because they never had one.

  • Check Confirmed column on users index — should show (confirmed)
  • -
  • Admin clicks Send reset password email from user's Edit
  • +
  • Admin clicks Send reset password email from user's Edit or user clicks Forgot your password? from login
  • User sets a password via the reset link — this also auto-signs them in
-

Note: Accounts are created with a random password the user doesn't know. The welcome flow is the only way they set a real one.

+

Note: Accounts are created with a random password the user doesn't know. Ideally they set a real one during the welcome invite flow.

From b64d7378a0cf0ce7e1711d4c7651e78c392673d4 Mon Sep 17 00:00:00 2001 From: maebeale Date: Sat, 28 Mar 2026 08:53:49 -0400 Subject: [PATCH 17/18] Update timeouts, combine password cards, add email change card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Confirmation links: 3 days → no expiry - Welcome tokens: 30 days → no expiry - Reset links: 6 hours → 7 days - Combine "I forgot my password" and "My reset link expired" cards - Add "I changed my email but can't log in" card for flow 4 - Add legacy note to "confirmed but no password" card - Update all flow diagrams to match new timeout values Co-Authored-By: Claude Opus 4.6 --- app/views/users/flow_diagram.html.erb | 95 ++++++++++++++------------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/app/views/users/flow_diagram.html.erb b/app/views/users/flow_diagram.html.erb index 6ce2fda59..a42e62981 100644 --- a/app/views/users/flow_diagram.html.erb +++ b/app/views/users/flow_diagram.html.erb @@ -32,6 +32,13 @@

Check Confirmed column on users index:

+
+ Invite button +
    +
  • No invite has been sent — click Invite to send
  • +
+ +
Clock icon (hover on index for details) @@ -41,13 +48,6 @@
-
- Invite button -
    -
  • No invite has been sent — click Invite to send
  • -
- -
Check icon (hover on index for details) @@ -57,14 +57,14 @@
-

Note: Confirmation links valid for 3 days, welcome tokens for 30 days

+

Note: Confirmation links and welcome tokens do not expire

"My invite link doesn't work" -

Confirmation link likely expired (valid for 3 days)

+

Confirmation link may be invalid or already used

@@ -90,7 +90,7 @@
-

Note: Confirmation links valid for 3 days, welcome tokens for 30 days

+

Note: Confirmation links and welcome tokens do not expire

@@ -105,21 +105,21 @@

Check Access column on users index:

- Lock icon + No lock icon
    -
  • Account is locked — go to user's Edit and click Unlock account (sets failed attempts back to 0)
  • -
  • User may also need a password reset
  • +
  • Clock icon in Confirmed — email unconfirmed, send an Invite
  • +
  • Ban icon — account is inactive, set to active on user's Edit under Account flags
  • +
  • Maybe they mean they forgot their password?
- +
- No lock icon + Lock icon
    -
  • Ban icon — account is inactive, set to active on user's Edit under Account flags
  • -
  • Clock icon in Confirmed — email unconfirmed, send an Invite
  • -
  • Maybe they mean they forgot their password?
  • +
  • Account is locked — go to user's Edit and click Unlock account (sets failed attempts back to 0)
  • +
  • User may also need a password reset
- +

Note: Accounts don't auto-unlock — needs an admin.

@@ -128,29 +128,34 @@
- "I forgot my password" -

User needs a password reset

+ "I forgot my password" / "My reset link expired" +

User needs a password reset • Reset links expire after 7 days

User clicks Forgot your password? from login or admin clicks Send reset password email from user's Edit

-

Note: Reset links expire after 6 hours.
Note: Each new reset invalidates previous email links.
Note: User will be automatically signed in after reset.

+

Note: Reset links expire after 7 days.
Note: Each new reset invalidates previous email links.
Note: User will be automatically signed in after reset.

- "My password reset link expired" -

Reset links expire after 6 hours

+ "I changed my email but can't log in with it" +

New email needs confirmation before it becomes active

-

User clicks Forgot your password? again or admin clicks Send reset password email from user's Edit

- -

Note: Each new reset invalidates previous email links.

+

After an email change, the old email stays active until the new one is confirmed. User must sign in with the old email until then.

+
    +
  • Check if user has a warning icon on users index or user show — means confirmation email wasn't sent
  • +
  • Admin can resend confirmation email from the user show page
  • +
  • Confirmation link does not expire — admin can resend if user lost the email
  • +
+ +

Note: User keeps signing in with old email until new email is confirmed.

@@ -162,14 +167,14 @@
-

User clicked the confirmation link but the welcome token expired before they set a password. They see "Invalid email or password" because they never had one.

+

User confirmed their email but never set a password. They see "Invalid email or password" because they never had one.

  • Check Confirmed column on users index — should show (confirmed)
  • Admin clicks Send reset password email from user's Edit or user clicks Forgot your password? from login
  • User sets a password via the reset link — this also auto-signs them in
-

Note: Accounts are created with a random password the user doesn't know. Ideally they set a real one during the welcome invite flow.

+

Note: Accounts are created with a random password the user doesn't know. Ideally they set a real one during the welcome invite flow.
Note: Welcome tokens no longer expire, so this mainly applies to legacy accounts created before that change.

@@ -211,27 +216,27 @@ <%# Flow 1: Invite new user %>

1. Invite new user

-

Admin creates a user and sends welcome instructions • Token valid 30 days • Confirmation token valid 3 days

+

Admin creates a user and sends welcome instructions • Tokens do not expire

 %%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#dbeafe', 'primaryTextColor': '#1e3a5f', 'primaryBorderColor': '#93c5fd', 'secondaryColor': '#dcfce7', 'tertiaryColor': '#fef9c3', 'lineColor': '#94a3b8', 'fontSize': '14px' }}}%%
 flowchart TD
     A["fa:fa-user-plus Admin: creates user
(Users#create)"]:::admin A2["fa:fa-paper-plane Admin: clicks 'Invite'
on users index or
'Resend invite email'
on user edit page"]:::admin - B["fa:fa-key System generates
welcome_instructions_token
for confirmation + password
(valid 30 days)"]:::system - C["fa:fa-envelope System sends
welcome instructions /
confirmation email
(token valid 3 days)"]:::system + B["fa:fa-key System generates
welcome_instructions_token
for confirmation + password"]:::system + C["fa:fa-envelope System sends
welcome instructions /
confirmation email"]:::system D{"fa:fa-clock User clicks
'Set your password'
link in email?"}:::decision E["fa:fa-check-circle Confirmations#show
confirms email"]:::system - F{"fa:fa-key Welcome token
still valid?
(30 days)"}:::decision + F{"fa:fa-key Welcome token
still valid?"}:::decision F2["Redirect to
/welcome/:token"]:::system G["fa:fa-lock User: sets password
(min 5 chars)"]:::user H["fa:fa-circle-check Account active!
Auto-signed in"]:::success - I["fa:fa-clock Link expired
(3 days)"]:::error + I["fa:fa-clock Link not clicked"]:::error J["fa:fa-rotate Admin resends invite
from user edit page
(see flow 2)"]:::admin STUCK["fa:fa-triangle-exclamation Redirected to
password reset form
(user has no known password)"]:::error FIX["fa:fa-envelope User requests
password reset
(see flow 3)"]:::admin A --> A2 --> B --> C --> D - D -- "Yes, within 3 days" --> E --> F + D -- "Yes" --> E --> F F -- "Yes" --> F2 --> G --> H F -- "No (expired)" --> STUCK --> FIX D -- "No / too late" --> I --> J @@ -259,8 +264,8 @@ flowchart TD B["fa:fa-paper-plane Button visible:
'Invite' on users index
'Resend invite email'
on user edit page"]:::admin C["fa:fa-eye-slash Buttons hidden
(user already confirmed)"]:::error D["POST /users/:id/
send_welcome_instructions"]:::system - E["fa:fa-key Regenerates
welcome_instructions_token
(new 30-day window)"]:::system - F["fa:fa-envelope Sends new
confirmation email
(new 3-day token)"]:::system + E["fa:fa-key Regenerates
welcome_instructions_token"]:::system + F["fa:fa-envelope Sends new
confirmation email"]:::system G["fa:fa-inbox User receives
fresh link"]:::user A -- "Yes" --> B --> D --> E --> F --> G @@ -279,21 +284,21 @@ flowchart TD <%# Flow 3: Reset password %>

3. Reset password

-

Admin or user can initiate • Token valid 6 hours • User auto-signed in after reset

+

Admin or user can initiate • Token valid 7 days • User auto-signed in after reset

 %%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#dbeafe', 'primaryTextColor': '#1e3a5f', 'primaryBorderColor': '#93c5fd', 'secondaryColor': '#dcfce7', 'tertiaryColor': '#fef9c3', 'lineColor': '#94a3b8', 'fontSize': '14px' }}}%%
 flowchart TD
     A["fa:fa-paper-plane Admin: clicks
'Send reset password email'
on user edit page"]:::admin A2["fa:fa-question User: clicks
'Forgot your password?'
on sign-in page"]:::user - B["fa:fa-key System generates
reset_password_token
(valid 6 hours)"]:::system + B["fa:fa-key System generates
reset_password_token
(valid 7 days)"]:::system C["fa:fa-envelope Email sent to user
+ FYI notification to admin"]:::system - D{"fa:fa-clock User clicks
'Reset your password'
within 6 hours?"}:::decision + D{"fa:fa-clock User clicks
'Reset your password'
within 7 days?"}:::decision E["Passwords#edit
shows reset form"]:::system F["fa:fa-lock User: enters new
password (min 5 chars)"]:::user G{"fa:fa-check-circle Was user
unconfirmed?"}:::decision H["fa:fa-envelope-circle-check Also confirms email"]:::system I["fa:fa-circle-check Password updated
Auto-signed in"]:::success - J["fa:fa-clock Link expired
(6 hours)"]:::error + J["fa:fa-clock Link expired
(7 days)"]:::error K["fa:fa-rotate Admin sends another
from user edit page,
or user requests from
sign-in page"]:::admin A --> B @@ -319,7 +324,7 @@ flowchart TD <%# Flow 4: Email change %>

4. Email change (admin-initiated)

-

Devise reconfirmable • Old email stays active until new email is confirmed • Confirmation token valid 3 days

+

Devise reconfirmable • Old email stays active until new email is confirmed • Confirmation token does not expire

 %%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#dbeafe', 'primaryTextColor': '#1e3a5f', 'primaryBorderColor': '#93c5fd', 'secondaryColor': '#dcfce7', 'tertiaryColor': '#fef9c3', 'lineColor': '#94a3b8', 'fontSize': '14px' }}}%%
 flowchart TD
@@ -327,18 +332,18 @@ flowchart TD
     B["Old email stays active
New email saved to
unconfirmed_email"]:::system C["Interstitial page:
'Email change saved'"]:::system D{"fa:fa-question Admin chooses to
send confirmation
email?"}:::decision - E["fa:fa-envelope Confirmation email
sent to new address
(token valid 3 days)"]:::system + E["fa:fa-envelope Confirmation email
sent to new address"]:::system F["fa:fa-triangle-exclamation Skip — no email sent
Warning icon shown on
users index and user show page"]:::admin G{"fa:fa-clock User clicks
confirm link
in email?"}:::decision H["fa:fa-circle-check New email becomes
active, old email
replaced"]:::success - I["fa:fa-clock Link expired
(3 days)"]:::error + I["fa:fa-clock Link not clicked"]:::error J["fa:fa-rotate Admin can resend
confirmation email from
the user show page"]:::admin K["fa:fa-right-to-bracket User keeps signing in
with old email
until confirmed"]:::user A --> B --> C --> D D -- "Yes" --> E --> G D -- "No / Skip" --> F --> K - G -- "Yes, within 3 days" --> H + G -- "Yes" --> H G -- "No / too late" --> I --> J E --> K From 354c2f0e5cca7bac5ed94473ca25a1ea72e7b13d Mon Sep 17 00:00:00 2001 From: maebeale Date: Sat, 28 Mar 2026 09:13:53 -0400 Subject: [PATCH 18/18] Refine cheat sheet cards and update flow diagrams - Combine "I forgot my password" and "My reset link expired" cards - Add "I changed my email" card for flow 4 - Add legacy note to "confirmed but can't sign in" card - Swap card 5/6 order - Update all timeouts: confirmation/welcome no expiry, reset 7 days - Fix flow 2: clickable link to flow 1, better "No" label - Various text refinements across cards Co-Authored-By: Claude Opus 4.6 --- app/views/users/flow_diagram.html.erb | 48 ++++++++++++++------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/app/views/users/flow_diagram.html.erb b/app/views/users/flow_diagram.html.erb index a42e62981..f1fb7e27b 100644 --- a/app/views/users/flow_diagram.html.erb +++ b/app/views/users/flow_diagram.html.erb @@ -57,7 +57,7 @@
-

Note: Confirmation links and welcome tokens do not expire

+

Note: Confirmation and welcome tokens do not expire

@@ -90,7 +90,7 @@
-

Note: Confirmation links and welcome tokens do not expire

+

Note: Confirmation and welcome tokens do not expire

@@ -107,7 +107,7 @@
No lock icon
    -
  • Clock icon in Confirmed — email unconfirmed, send an Invite
  • +
  • Clock icon in Confirmed — email unconfirmed, Resend invite email on user's Edit
  • Ban icon — account is inactive, set to active on user's Edit under Account flags
  • Maybe they mean they forgot their password?
@@ -128,8 +128,8 @@
- "I forgot my password" / "My reset link expired" -

User needs a password reset • Reset links expire after 7 days

+ "I forgot my password" / "My reset expired" +

User needs a password reset

@@ -142,39 +142,39 @@
- "I changed my email but can't log in with it" -

New email needs confirmation before it becomes active

+ "I confirmed my email but can't sign in" +

Email confirmed but user never set a password

-

After an email change, the old email stays active until the new one is confirmed. User must sign in with the old email until then.

+

User confirmed their email but never set a password. They see "Invalid email or password" because they never had one.

    -
  • Check if user has a warning icon on users index or user show — means confirmation email wasn't sent
  • -
  • Admin can resend confirmation email from the user show page
  • -
  • Confirmation link does not expire — admin can resend if user lost the email
  • +
  • Check Confirmed column on users index — should show (confirmed)
  • +
  • Admin clicks Send reset password email from user's Edit or user clicks Forgot your password? from login
  • +
  • User sets a password via the reset link — this also auto-signs them in
- -

Note: User keeps signing in with old email until new email is confirmed.

+ +

Note: Accounts are created with a random password the user doesn't know. Ideally they set a real one during the welcome invite flow.
Note: Welcome tokens no longer expire, so this mainly applies to legacy accounts created before that change.

- "I confirmed my email but can't sign in" -

Email confirmed but user never set a password

+ "I changed my email but can't log in with it" +

New email needs confirmation to become active

-

User confirmed their email but never set a password. They see "Invalid email or password" because they never had one.

+

After an email change, the old email stays active until the new one is confirmed. User must sign in with the old email until then.

    -
  • Check Confirmed column on users index — should show (confirmed)
  • -
  • Admin clicks Send reset password email from user's Edit or user clicks Forgot your password? from login
  • -
  • User sets a password via the reset link — this also auto-signs them in
  • +
  • Check if user has a warning icon on users index or user show — means confirmation email wasn't sent
  • +
  • Admin can resend confirmation email from the user show page
  • +
  • Confirmation link does not expire — admin can resend if user lost the email
- -

Note: Accounts are created with a random password the user doesn't know. Ideally they set a real one during the welcome invite flow.
Note: Welcome tokens no longer expire, so this mainly applies to legacy accounts created before that change.

+ +

Note: User keeps signing in with old email until new email is confirmed.

@@ -267,9 +267,11 @@ flowchart TD E["fa:fa-key Regenerates
welcome_instructions_token"]:::system F["fa:fa-envelope Sends new
confirmation email"]:::system G["fa:fa-inbox User receives
fresh link"]:::user + H["fa:fa-arrow-right User clicks
'Set your password'
(see flow 1)"]:::system + click H href "#flow-1" "Go to flow 1" - A -- "Yes" --> B --> D --> E --> F --> G - A -- "No" --> C + A -- "Yes" --> B --> D --> E --> F --> G --> H + A -- "No, email is
already confirmed" --> C linkStyle 5 stroke:#f87171