From d6250d6b05627993dc9f59927b428b46b74eabe0 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Mon, 24 Aug 2026 23:16:44 -0400 Subject: [PATCH] fix(web): make the re-auth banner a centered popup, not a full-width top bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unrelated to #2108's diagnostics work, folded in at the maintainer's request rather than tracked separately. The sticky full-bleed bar cost the whole view a band of vertical space for what is a notification about one server, and it pushed down the monitoring sidebar that an OAuth failure now opens. Every corner is spoken for — the toast stack owns bottom-right, the right edge is the sidebar whose toolbar an anchored card covers, top-left is the Servers header — so it is centered instead, which is the one placement that reads as addressed to the window rather than attached to the wrong panel. Deliberately WITHOUT an overlay. This is a notification, not a decision that must be made now, and blocking the page would force a choice between reading the requests that explain the failure and keeping the affordance — dismissing is not free, since "Authorize again" also clears the stale OAuth state, which a plain reconnect does not do. Kept as the existing declarative component rather than routed through Mantine's `notifications`: that module is mocked in App.test.tsx, so switching would have broken the ten tests asserting on this banner's text and its action button, including the lost-authorization-state coverage. The message now stacks above a right-aligned action, which a ~420px card needs. Signed-off-by: cliffhall --- clients/web/src/App.tsx | 43 ++++++++++++++----- .../groups/ReAuthBanner/ReAuthBanner.tsx | 25 +++++++---- 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/clients/web/src/App.tsx b/clients/web/src/App.tsx index 64a3968a..9e0fde32 100644 --- a/clients/web/src/App.tsx +++ b/clients/web/src/App.tsx @@ -422,20 +422,41 @@ const ToastLinkButton = Anchor.withProps({ size: "sm", }); -// Sticky re-auth banner bar. A `Paper` so every static style is a prop: `shadow` -// emits `var(--mantine-shadow-sm)` (identical to the old CSS), and the stacking +// The re-auth popup. A `Paper` so every static style is a prop; the stacking // order goes through `styles.root` since Mantine has no `z` prop. +// +// Floats top-right rather than spanning the top as a sticky full-bleed bar. +// The bar cost the whole view a band of vertical space for what is a +// notification about one server, and it sat directly above the monitoring +// sidebar that an OAuth failure now opens (#2108) — the two things a user needs +// at once here are this affordance and those requests, so it must not push them +// around. `fixed`, not `sticky`, so scrolling the server list leaves it put. +// +// Centered, and deliberately WITHOUT an overlay. Every corner is spoken for — +// the toast stack owns bottom-right (see `main.tsx`), the right edge is the +// monitoring sidebar whose toolbar this would cover, and top-left is the +// Servers header — so anchoring it anywhere hides something. Centering is the +// one placement that reads as addressed to the whole window rather than +// attached to the wrong panel. +// +// The missing overlay is the point, not an omission: this is a notification, +// not a decision that must be made now. An OAuth failure opens the monitoring +// sidebar (#2108), and blocking the page would force a choice between reading +// those requests and keeping the affordance — dismissing is not free, since +// "Authorize again" also clears the stale OAuth state, which a plain reconnect +// does not do. So it floats above the page and leaves it usable. +// +// `transform` goes through `styles.root` for the same reason `zIndex` does: +// Mantine exposes neither as a style prop. const ReAuthBannerBar = Paper.withProps({ - px: "md", - pt: "xs", - pos: "sticky", - top: 60, + pos: "fixed", + top: "50%", + left: "50%", + w: 420, bg: "var(--mantine-color-body)", - shadow: "sm", - styles: { root: { zIndex: 200 } }, - // Paper's default `radius: "md"` would round this full-bleed sticky bar's - // corners; the bar it replaced (a Box) had none. - radius: 0, + shadow: "xl", + radius: "md", + styles: { root: { transform: "translate(-50%, -50%)", zIndex: 200 } }, }); // Body of the "response body dropped" warning toast: a one-line summary of what diff --git a/clients/web/src/components/groups/ReAuthBanner/ReAuthBanner.tsx b/clients/web/src/components/groups/ReAuthBanner/ReAuthBanner.tsx index 817e19c3..737f2062 100644 --- a/clients/web/src/components/groups/ReAuthBanner/ReAuthBanner.tsx +++ b/clients/web/src/components/groups/ReAuthBanner/ReAuthBanner.tsx @@ -1,4 +1,4 @@ -import { Alert, Button, Group, Text } from "@mantine/core"; +import { Alert, Button, Group, Stack, Text } from "@mantine/core"; export const DEFAULT_REAUTH_BANNER_TITLE = "Re-authentication required"; export const DEFAULT_REAUTH_BANNER_ACTION_LABEL = "Re-authenticate"; @@ -24,11 +24,16 @@ const ReAuthAlert = Alert.withProps({ closeButtonLabel: "Dismiss", }); -const BannerRow = Group.withProps({ - justify: "space-between", - align: "center", - wrap: "nowrap", - gap: "md", +// Message above the action rather than beside it: this renders in a ~420px +// popup, where a side-by-side row squeezes the message into a few words per +// line while the button keeps its full width. +const BannerBody = Stack.withProps({ + gap: "xs", +}); + +const BannerActions = Group.withProps({ + justify: "flex-end", + gap: "xs", }); const MessageText = Text.withProps({ @@ -50,10 +55,12 @@ export function ReAuthBanner({ }: ReAuthBannerProps) { return ( - + {message} - {actionLabel} - + + {actionLabel} + + ); }