From 4244fbd53efbbf2f78e1f1ebb09f1934473e4983 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 11 Nov 2026 14:51:56 +0800 Subject: [PATCH 01/51] =?UTF-8?q?Show=20a=20calm=20Saving=E2=80=A6=20pill?= =?UTF-8?q?=20first;=20warn=20only=20after=208=20s,=20offline=20or=20on=20?= =?UTF-8?q?failure=20(live=20audit=20item=208)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ward-flow/desk-guides/access-admin.md | 3 +- docs/ward-flow/desk-guides/community-team.md | 3 +- .../ward-flow/desk-guides/flow-coordinator.md | 3 +- .../ward-flow/desk-guides/site-bed-manager.md | 3 +- .../desk-guides/transport-officer.md | 3 +- docs/ward-flow/desk-guides/ward.md | 3 +- .../ward-shared-save-status.module.css | 23 +++++ .../ward-shared-save-status.tsx | 96 ++++++++++++++----- .../ward-shared-save-status-bar.dom.test.tsx | 52 +++++++++- 9 files changed, 157 insertions(+), 32 deletions(-) diff --git a/docs/ward-flow/desk-guides/access-admin.md b/docs/ward-flow/desk-guides/access-admin.md index 7f679bcebc..c4138b0dce 100644 --- a/docs/ward-flow/desk-guides/access-admin.md +++ b/docs/ward-flow/desk-guides/access-admin.md @@ -29,7 +29,8 @@ Access is managed in **Settings**. On a phone, the bar at the bottom has **Gover ## Saved, or not - After a change, a short message says it was recorded. -- On a shared board the status line reads "Saving shared changes…", then your desk name again. +- On a shared board a small "Saving…" note shows while a save goes through. If it takes longer + than 8 seconds, or you are offline, the status line warns you not to reload. - If it reads "Connection unavailable — changes are disabled." and shows **Retry**: stay on the page and press **Retry**. Do not reload. Reloading loses the unconfirmed change. - A message saying a change was not recorded means nothing changed. Read the reason and try again. diff --git a/docs/ward-flow/desk-guides/community-team.md b/docs/ward-flow/desk-guides/community-team.md index 6b5e948555..5004b671f9 100644 --- a/docs/ward-flow/desk-guides/community-team.md +++ b/docs/ward-flow/desk-guides/community-team.md @@ -43,7 +43,8 @@ holds **New referral**, and **Activity** is under **Menu**. - After an action, a short message says it was recorded. In the demo it adds "Saved on this device only." - A contact is shown as saved only once the save is confirmed. -- On a shared board the status line reads "Saving shared changes…", then your desk name again. +- On a shared board a small "Saving…" note shows while a save goes through. If it takes longer + than 8 seconds, or you are offline, the status line warns you not to reload. - If it reads "Connection unavailable — changes are disabled." and shows **Retry**: stay on the page and press **Retry**. Do not reload. Reloading loses the unconfirmed action. - A message saying an action was not recorded means nothing changed. Read the reason and try again. diff --git a/docs/ward-flow/desk-guides/flow-coordinator.md b/docs/ward-flow/desk-guides/flow-coordinator.md index b83c57a84e..e4be9e7b6e 100644 --- a/docs/ward-flow/desk-guides/flow-coordinator.md +++ b/docs/ward-flow/desk-guides/flow-coordinator.md @@ -62,7 +62,8 @@ Source: [Home queue order](../../../src/components/ward-management/ward-priority - After an action, a short message says it was recorded. In the demo it adds "Saved on this device only." -- On a shared board the status line reads "Saving shared changes…", then your desk name again. +- On a shared board a small "Saving…" note shows while a save goes through. If it takes longer + than 8 seconds, or you are offline, the status line warns you not to reload. - If it reads "Connection unavailable — changes are disabled." and shows **Retry**: stay on the page and press **Retry**. Do not reload. Reloading loses the unconfirmed action. - A message saying an action was not recorded means nothing changed. Read the reason and try again. diff --git a/docs/ward-flow/desk-guides/site-bed-manager.md b/docs/ward-flow/desk-guides/site-bed-manager.md index fe7f774dc2..e9f22a302c 100644 --- a/docs/ward-flow/desk-guides/site-bed-manager.md +++ b/docs/ward-flow/desk-guides/site-bed-manager.md @@ -38,7 +38,8 @@ are under **Menu**. - After an action, a short message says it was recorded. In the demo it adds "Saved on this device only." -- On a shared board the status line reads "Saving shared changes…", then your desk name again. +- On a shared board a small "Saving…" note shows while a save goes through. If it takes longer + than 8 seconds, or you are offline, the status line warns you not to reload. - If it reads "Connection unavailable — changes are disabled." and shows **Retry**: stay on the page and press **Retry**. Do not reload. Reloading loses the unconfirmed action. - A message saying an action was not recorded means nothing changed. Read the reason and try again. diff --git a/docs/ward-flow/desk-guides/transport-officer.md b/docs/ward-flow/desk-guides/transport-officer.md index ef9d9442a8..2a6c3af24c 100644 --- a/docs/ward-flow/desk-guides/transport-officer.md +++ b/docs/ward-flow/desk-guides/transport-officer.md @@ -34,7 +34,8 @@ You get phone alerts when a job is new or changes. - After each step, a short message says it was recorded, or why it was refused. In the demo it adds "Saved on this device only." -- On a shared board the status line reads "Saving shared changes…", then your desk name again. +- On a shared board a small "Saving…" note shows while a save goes through. If it takes longer + than 8 seconds, or you are offline, the status line warns you not to reload. - If it reads "Connection unavailable — changes are disabled." and shows **Retry**: stay on the page and press **Retry**. Do not reload. Reloading loses the unconfirmed action. - A message saying an action was not recorded means nothing changed. Read the reason and try again. diff --git a/docs/ward-flow/desk-guides/ward.md b/docs/ward-flow/desk-guides/ward.md index 47219e6372..30137f4fad 100644 --- a/docs/ward-flow/desk-guides/ward.md +++ b/docs/ward-flow/desk-guides/ward.md @@ -45,7 +45,8 @@ bed count is not read as a problem with the patient. - After an action, a short message says it was recorded. In the demo it adds "Saved on this device only." -- On a shared board the status line reads "Saving shared changes…", then your desk name again. +- On a shared board a small "Saving…" note shows while a save goes through. If it takes longer + than 8 seconds, or you are offline, the status line warns you not to reload. - If it reads "Connection unavailable — changes are disabled." and shows **Retry**: stay on the page and press **Retry**. Do not reload. Reloading loses the unconfirmed action. - A message saying an action was not recorded means nothing changed. Read the reason and try again. diff --git a/src/components/ward-management/ward-shared-save-status.module.css b/src/components/ward-management/ward-shared-save-status.module.css index 7fe1fe5e9a..f850f84cab 100644 --- a/src/components/ward-management/ward-shared-save-status.module.css +++ b/src/components/ward-management/ward-shared-save-status.module.css @@ -60,6 +60,25 @@ white-space: nowrap; } +/* + * Live audit item 8 (11 Nov 2026): a save in flight shows this small calm pill, not the warning + * strip. It floats, so the page never jumps on every save, and takes no taps. + */ +.pill { + position: fixed; + inset-block-end: var(--wf-s-4); + inset-inline-start: var(--wf-s-4); + z-index: 40; + padding: var(--wf-s-1) var(--wf-s-3); + border: 1px solid var(--wf-line); + border-radius: 999px; + color: var(--wf-ink-2); + background: var(--wf-surface); + font-size: var(--wf-fs-12); + line-height: 1.4; + pointer-events: none; +} + /* * The strip above the app on the shared board, shown only when a lost connection or an unconfirmed * save needs action. One slim row in the shell's own tokens: what is wrong on the left, Retry and @@ -152,6 +171,10 @@ flex: none; } + .pill { + inset-block-end: calc(var(--wf-dock-clear, calc(72px + max(8px, env(safe-area-inset-bottom, 0px)))) + 0.5rem); + } + /* The reload warning must stay readable, so a strip carrying it wraps. */ .bar:has(> p) { flex-wrap: wrap; diff --git a/src/components/ward-management/ward-shared-save-status.tsx b/src/components/ward-management/ward-shared-save-status.tsx index 81979cd452..24f2547317 100644 --- a/src/components/ward-management/ward-shared-save-status.tsx +++ b/src/components/ward-management/ward-shared-save-status.tsx @@ -1,6 +1,14 @@ "use client"; -import { useLayoutEffect, useRef, useState, useSyncExternalStore, type ReactNode, type RefObject } from "react"; +import { + useEffect, + useLayoutEffect, + useRef, + useState, + useSyncExternalStore, + type ReactNode, + type RefObject, +} from "react"; import { createPortal } from "react-dom"; import { clk, useSharedRefreshLate } from "@/components/wf"; import { subscribeToSheetStack, topSheetRoot } from "@/components/wf/sheet-focus"; @@ -17,6 +25,38 @@ function waitingMessage(waiting: number): string { const RELOAD_WARNING = "Not confirmed yet. Do not reload: reloading now would lose this action. Press Retry first."; +/** + * Live audit item 8 (11 Nov 2026): a normal save takes 3 to 4 s, so the full warning flashed on every + * save. A save in flight shows a calm "Saving…" first; the warning comes after this long, when the + * browser is offline, or once the save has failed. + */ +const SAVE_WARNING_AFTER_MS = 8_000; + +const onlineNow = () => typeof navigator === "undefined" || navigator.onLine !== false; +function subscribeOnline(notify: () => void) { + window.addEventListener("online", notify); + window.addEventListener("offline", notify); + return () => { + window.removeEventListener("online", notify); + window.removeEventListener("offline", notify); + }; +} + +/** True once a save has been in flight for `SAVE_WARNING_AFTER_MS`, or at once while offline. */ +function useSaveSlow(saving: boolean): boolean { + const online = useSyncExternalStore(subscribeOnline, onlineNow, () => true); + const [slow, setSlow] = useState(false); + useEffect(() => { + if (!saving) return; + const timer = setTimeout(() => setSlow(true), SAVE_WARNING_AFTER_MS); + return () => { + clearTimeout(timer); + setSlow(false); + }; + }, [saving]); + return saving && (slow || !online); +} + /** Keep recovery inside the top overlay's focus trap, without reopening or closing any record. */ function useRecoveryHost(show: boolean): HTMLElement | null { "use no memo"; @@ -89,26 +129,32 @@ export function WardSharedSaveStatus({ "use no memo"; const save = useWardSharedSave(); const [retrying, setRetrying] = useState(false); + const inFlight = connection.status === "saving"; + const slow = useSaveSlow(inFlight); + // Item 8: a save in flight with no error stays calm until it is slow or the browser is offline. + // A save still waiting after a failure (status back to ready or unavailable) warns at once. + const calmSave = inFlight && !retrying && !slow && !connection.error; // WF-07: one warning node reused in both strip layouts (was duplicated JSX). - const reloadWarning = save?.unconfirmedSave ? ( -
- {RELOAD_WARNING} -
- ) : null; + const reloadWarning = + save?.unconfirmedSave && !calmSave ? ( ++ {RELOAD_WARNING} +
+ ) : null; const stale = useSharedRefreshLate(connection.enabled, 5 * 60_000, false) && connection.receivedAt > 0; // Tier 1 item 4 (Josh, 4 Nov 2026): a refresh that is late but not yet failed used to show only on // pages with a Live chip. The strip now says when the board last updated, on every screen. const late = useSharedRefreshLate(connection.enabled && connection.status !== "unavailable") && connection.receivedAt > 0; // Inside an open sheet the strip is inert, so a late refresh moves its words and Retry there too. - const showRecovery = connection.status === "saving" || connection.status === "unavailable" || late || stale; + const showRecovery = (inFlight && !calmSave) || connection.status === "unavailable" || late || stale; const host = useRecoveryHost(showRecovery); const saving = connection.status === "saving" || retrying; const recoveryRef = useRef