Skip to content

Render flash messages with UI::Alerts::FlashMessage - #4020

Merged
sethherr merged 11 commits into
mainfrom
sethherr/flash-message-ui-alerts
Aug 2, 2026
Merged

Render flash messages with UI::Alerts::FlashMessage#4020
sethherr merged 11 commits into
mainfrom
sethherr/flash-message-ui-alerts

Conversation

@sethherr

@sethherr sethherr commented Aug 2, 2026

Copy link
Copy Markdown
Member

The four layouts that rendered flash through /layouts/revised_messages now render the component instead, the way admin already did. The partial stays behind for @page_errors and the mustache template BikeIndexAlerts renders client-side alerts into, so the JS alerts on the bike-edit and payments pages keep working.

  • flash[:info] becomes flash[:notice]. info isn't one of KINDS — 24 call sites across 14 controllers set it directly, four more reach it by passing flash_type: :info to authenticate_user, and four views passed it as a kind, where it silently fell back. Renamed rather than aliased in the component, so the flash type and the kind it renders stay the same word. KINDS now derives from TEXT_CLASSES.
  • The unknown-kind guard raises in dev and test, falls back to notice elsewhere. Rails sweeps the flash after the layout renders, so a raise from the layout leaves the offending key in the session and raises again on the next request, and the next.
  • Every flash value is a string now. Six sites set an Array (errors.full_messages) or a flash[:errors] key; the component renders neither, and the old bootstrap partial rendered the Array's inspect. Five get to_sentence, and the theft alert plan forms get UI::Alerts::ObjectError above the form like the other admin forms.
  • The impound multi-update's turbo_stream replaces #flash-messages alongside the results frame, rather than rendering a second copy of the flash inside the frame. The component renders its container even when empty so there's something to target, and the replace only fires when there's a flash — otherwise every filter and sort would wipe an alert the rider hadn't dismissed.
  • UI::Alert moves to UI::Alerts::Base, so all three alert components share the namespace. The three MARKUP_DIGEST bumps are that rename reaching templates inside fragment caches.

Flash now sits bottom-center instead of top, and success messages no longer auto-fade after 10s — the component's behavior, already what admin gets.

Switches the four layouts that rendered flash through /layouts/revised_messages
to the component. The partial stays for @page_errors and the mustache template
BikeIndexAlerts renders client-side alerts into.

Maps flash[:info] to the notice kind, and skips rendering when the flash is
empty. The unknown-kind guard now falls back to notice outside dev/test -- Rails
sweeps the flash after the layout renders, so raising would raise again on every
subsequent request.

Also moves UI::Alert to UI::Alerts::Base, so every alert component shares the
one namespace.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sethherr

sethherr commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

Screenshots

/session/new (failed login)

Desktop Mobile
main 👆 this branch 👇

sethherr and others added 10 commits August 2, 2026 12:33
The multi-update form redirects back to a turbo_stream response, so its flash
needed a copy inside the results frame to survive. Replace #flash-messages
alongside the frame instead, and drop the render_flash local and the bootstrap
alert markup it rendered.

The flash component renders its container even when empty, so the turbo_stream
has something to target.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
flash[:errors] on the theft alert plan forms is validation errors on an object
that's already in scope, set on a render rather than a redirect. Render
UI::Alerts::ObjectError above the form instead, the way the other admin forms
do -- the flash never displayed, it just wasn't a flash type.

Three sites assigned errors.full_messages -- an Array -- to flash[:error];
to_sentence them, matching their siblings. UI::Alerts::FlashMessage skips
non-strings, so those messages rendered nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Was tw:px-4, which cost the alert enough width that the dismiss button wrapped
below the message at 390px. Desktop keeps its 16px gutter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
users_controller and admin/theft_alerts_controller still assigned an Array to
flash[:error]. UI::Alerts::FlashMessage skips non-strings, so a failed
terms-of-service update and a failed promoted alert update both redirected with
no error shown. to_sentence them, matching their siblings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
info isn't one of UI::Alerts::Base's KINDS -- 24 call sites across 14
controllers set it, and four views passed it as a kind, where it silently fell
back to notice. Rename them rather than aliasing info to notice in the
component, so the flash type and the kind it renders stay the same word.

KINDS now derives from TEXT_CLASSES, which is the list a kind actually needs an
entry in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
find raises Capybara::Ambiguous when a page carries two, which is whenever a
controller sets two flash keys. minimum: 1 keeps find's wait-for-it-to-appear,
which a bare all would drop -- the assertion below would then pass on a flash
that hadn't rendered yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four controllers reach flash[:info] indirectly, by passing flash_type: :info to
authenticate_user, so the direct-assignment rename missed them. Signed-out
/my_account, /choose_registration and the Strava connect flow all raised
"Unknown flash type: info" out of the layout once the alias was gone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deriving KINDS from TEXT_CLASSES moved lines in a file all three cached
components reach through TEXT_CLASSES.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every impound index filter and sort hits format.turbo_stream, so replacing
#flash-messages unconditionally wiped whatever alert the rider hadn't dismissed
yet -- and shipped an empty container to do it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sethherr
sethherr marked this pull request as ready for review August 2, 2026 20:40
@sethherr
sethherr merged commit d3c2022 into main Aug 2, 2026
9 checks passed
@sethherr
sethherr deleted the sethherr/flash-message-ui-alerts branch August 2, 2026 20:40
sethherr added a commit that referenced this pull request Aug 3, 2026
Follow-up to #4020. An unknown alert kind used to fall back to notice
silently, so a caller that passed a typo'd or unhandled kind got a blue
box and no signal. And a dismissable alert's text could only wrap inside
its own column, so a long message pushed the dismiss button onto a line
of its own.

- **`kind` defaults to `:notice`, and anything outside `KINDS` raises.**
An omitted kind and a wrong one used to be the same input; now only the
wrong one is an error. Deployed environments notify Honeybadger and fall
back instead of raising — the flash is swept after the layout renders,
so a raise from there would raise again on every subsequent request.
That's `production? || sandbox?`, per the gotcha in
`config/environments/sandbox.rb`.
- **`UI::Alerts::FlashMessage` drops its own copy of that guard** and
hands the flash type straight to the alert.
- **The icon and dismiss button float rather than sitting in a flex
row**, so an alert's first line renders between them and every line
after runs the full width. A float displaces only the lines it overlaps,
which a flex item can't do — its text is stuck in its own column. Both
are kept to about a line tall and precede the text, which is what floats
require; the dismiss button moving above the message in the DOM also
puts it first in tab order.
- **Preview reorganization**: the warning kind variant is now the header
variant, the separate header group is gone, and the dismissable group
picks up a header variant and a long-text purple one that exercises the
wrapping above.

The `MARKUP_DIGEST` bumps are these edits reaching templates inside
fragment caches.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant