mirror of
https://github.com/we-promise/sure.git
synced 2026-06-08 20:29:05 +00:00
* fix(sync-toast): morph refresh, defer behind modals, DS conformance Follow-up to #1964 (addresses #2071). - Refresh via Turbo morph visit instead of window.location.reload, so scroll position and data-turbo-permanent elements (the AI chat panel) survive and there is no white flash. - Defer the toast while a <dialog> is open and reveal it on close. A refresh mid-modal closes the dialog and discards its in-progress input, which is the exact data loss this toast exists to prevent. Handles stacked modals. - Refresh CTA and close button now use DS::Button (secondary / icon). The close is always visible, inside the card, focusable, and has an aria-label; the old hover-only corner chip was unreachable on touch and not keyboard-focusable. - Add role="status" / aria-live="polite" to the toast. - Fix icon color: "inverse" is not a key in the icon helper color map, so it silently rendered no color class (dark icon on bg-info). Use "white", which maps to the functional text-inverse token. - Tighten copy: "New data available" / "Refresh". - Sync the broadcast comment with the actual replace/morph behavior. * fix(sync-toast): detach deferred dialog listener on disconnect A toast replaced by a newer broadcast_replace_to while a <dialog> was open kept its 'close' listener attached, so the detached controller fired #reveal()/#arm() when the dialog closed — a spurious auto-refresh from a stale toast (and repeated syncs could queue several). Store the dialog + handler refs and remove the listener in disconnect(). Flagged by codex + coderabbit on #2105. * fix(sync-toast): re-check interaction and dialogs at refresh-fire time The interaction check ran once at arm time but the refresh fired two seconds later. The post-dialog reveal made that window matter: the user closes a dialog sitting on a form, resumes typing, and the timer morphs the page — wiping non-turbo-permanent input, the exact data-loss class this toast exists to prevent. A dialog opened during the window had the mirror problem (the refresh would close it). Bail inside the callback instead, leaving the toast visible for a manual refresh, matching the mid-form behavior. Also documents the dialog-removed-without-close edge on the deferred listener.
35 lines
1.0 KiB
Plaintext
35 lines
1.0 KiB
Plaintext
<div id="sync-toast"
|
|
role="status"
|
|
aria-live="polite"
|
|
data-controller="sync-toast element-removal"
|
|
class="relative flex gap-3 rounded-lg bg-container p-4 w-full md:max-w-80 shadow-border-xs">
|
|
<div class="h-5 w-5 shrink-0 p-px text-primary">
|
|
<div class="flex h-full items-center justify-center rounded-full bg-info">
|
|
<%= icon "refresh-cw", size: "xs", color: "white" %>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-2 flex-1 min-w-0">
|
|
<p class="text-primary text-sm font-medium"><%= t("shared.sync_toast.message") %></p>
|
|
<%= render DS::Button.new(
|
|
text: t("shared.sync_toast.refresh"),
|
|
variant: "secondary",
|
|
size: "sm",
|
|
icon: "refresh-cw",
|
|
type: "button",
|
|
data: { action: "sync-toast#refresh" }
|
|
) %>
|
|
</div>
|
|
|
|
<div class="absolute top-1 right-1">
|
|
<%= render DS::Button.new(
|
|
variant: "icon",
|
|
size: "sm",
|
|
icon: "x",
|
|
type: "button",
|
|
"aria-label": t("defaults.common.close"),
|
|
data: { action: "click->element-removal#remove" }
|
|
) %>
|
|
</div>
|
|
</div>
|