refactor(web): resolve Mantine/CSS rule violations in mid-session auth UI

- App.tsx: replace the inline `style={{}}` on the re-auth banner wrapper with
  Mantine style props (pos/top/bg) plus a `.reauth-banner-bar` class for the
  z-index/box-shadow that have no Mantine shorthand.
- App.tsx: extract the ~100-line inline `onAuthorize` and the inline `onCancel`
  step-up handlers into named `handleStepUpAuthorize`/`handleStepUpCancel`.
- ReAuthBanner: move the flat `styles={{ root }}` (background/border) into a
  `reauth` Alert theme variant; replace the raw `<span>` with `<Text>`.
- StepUpAuthModal: extract the inline `onClick` wrapper into `handleAuthorize`.

No behavior change; validate + integration green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DZyeWManYTvcXxjcrhxSQc
This commit is contained in:
cliffhall 2026-07-03 13:03:01 -04:00
commit 74c6491b52
5 changed files with 173 additions and 155 deletions

View file

@ -412,3 +412,13 @@
.server-drag-handle:active {
cursor: grabbing;
}
/*
* Sticky re-auth banner bar. Positioning (pos/top) and background are set via
* Mantine props on the Box; z-index and box-shadow have no Mantine style-prop
* shorthand, so they live here.
*/
.reauth-banner-bar {
z-index: 200;
box-shadow: var(--mantine-shadow-sm);
}

View file

@ -3434,20 +3434,154 @@ function App() {
[pendingElicitations],
);
const handleStepUpAuthorize = async () => {
if (!pendingStepUp || stepUpAuthorizeInProgressRef.current) {
return;
}
const stepUp = pendingStepUp;
const client = inspectorClient;
if (!client) {
return;
}
if (stepUp.enterpriseManaged) {
stepUpAuthorizeInProgressRef.current = true;
setPendingStepUp(null);
notifications.show({
title: "Organization permissions",
message: emaStepUpInProgressMessage(),
color: "blue",
autoClose: 4000,
});
try {
const outcome = await client.handleAuthChallenge(stepUp.challenge, {
confirmedStepUp: true,
});
if (outcome.kind === "satisfied") {
await client.pushRemoteAuthState();
notifications.show({
title: "Permissions updated",
message: emaStepUpSuccessMessage({
recoverySource: stepUp.source,
}),
color: "green",
autoClose: 5000,
});
const retry = pendingStepUpRetryRef.current;
pendingStepUpRetryRef.current = null;
if (retry) {
await retry();
}
return;
}
if (outcome.kind === "interactive") {
prepareOAuthRedirect({
serverId: stepUp.serverId,
authKind: "step_up",
authorizationUrl: outcome.authorizationUrl,
authChallenge: outcome.challenge,
recoverySource: stepUp.source,
});
return;
}
if (outcome.kind === "failed") {
const failureMessage = emaStepUpFailureMessage(outcome.error.message);
notifications.show({
title: "Organization permissions",
message: failureMessage,
color: "red",
autoClose: 6000,
});
switch (stepUp.source) {
case "tool":
setToolCallState({
status: "error",
error: failureMessage,
});
break;
case "prompt":
setGetPromptState((prev) =>
prev
? { ...prev, status: "error", error: failureMessage }
: prev,
);
break;
case "resource":
setReadResourceState((prev) =>
prev
? { ...prev, status: "error", error: failureMessage }
: prev,
);
break;
default:
break;
}
}
} finally {
stepUpAuthorizeInProgressRef.current = false;
}
return;
}
stepUpAuthorizeInProgressRef.current = true;
prepareOAuthRedirect({
serverId: stepUp.serverId,
authKind: "step_up",
authorizationUrl: stepUp.authorizationUrl,
authChallenge: stepUp.challenge,
recoverySource: stepUp.source,
});
setPendingStepUp(null);
pendingStepUpRetryRef.current = null;
stepUpAuthorizeInProgressRef.current = false;
};
const handleStepUpCancel = () => {
const stepUp = pendingStepUpRef.current;
setPendingStepUp(null);
pendingStepUpRetryRef.current = null;
if (!stepUp) {
return;
}
const cancelled = "Authorization cancelled.";
switch (stepUp.source) {
case "tool":
setToolCallState({ status: "error", error: cancelled });
break;
case "prompt":
setGetPromptState((prev) =>
prev ? { ...prev, status: "error", error: cancelled } : prev,
);
break;
case "resource":
setReadResourceState((prev) =>
prev ? { ...prev, status: "error", error: cancelled } : prev,
);
break;
case "app":
notifications.show({
title: "Authorization cancelled",
message: cancelled,
color: "gray",
autoClose: 4000,
});
break;
case "ambient":
break;
}
};
return (
<>
<Box>
{reAuthBanner ? (
<Box
className="reauth-banner-bar"
px="md"
pt="xs"
style={{
position: "sticky",
top: 60,
zIndex: 200,
backgroundColor: "var(--mantine-color-body)",
boxShadow: "var(--mantine-shadow-sm)",
}}
pos="sticky"
top={60}
bg="var(--mantine-color-body)"
>
<ReAuthBanner
message={reAuthBanner.message}
@ -3693,144 +3827,8 @@ function App() {
challenge={pendingStepUp?.challenge ?? null}
authorizationScopes={pendingStepUp?.challenge?.authorizationScopes}
enterpriseManaged={pendingStepUp?.enterpriseManaged}
onAuthorize={async () => {
if (!pendingStepUp || stepUpAuthorizeInProgressRef.current) {
return;
}
const stepUp = pendingStepUp;
const client = inspectorClient;
if (!client) {
return;
}
if (stepUp.enterpriseManaged) {
stepUpAuthorizeInProgressRef.current = true;
setPendingStepUp(null);
notifications.show({
title: "Organization permissions",
message: emaStepUpInProgressMessage(),
color: "blue",
autoClose: 4000,
});
try {
const outcome = await client.handleAuthChallenge(
stepUp.challenge,
{ confirmedStepUp: true },
);
if (outcome.kind === "satisfied") {
await client.pushRemoteAuthState();
notifications.show({
title: "Permissions updated",
message: emaStepUpSuccessMessage({
recoverySource: stepUp.source,
}),
color: "green",
autoClose: 5000,
});
const retry = pendingStepUpRetryRef.current;
pendingStepUpRetryRef.current = null;
if (retry) {
await retry();
}
return;
}
if (outcome.kind === "interactive") {
prepareOAuthRedirect({
serverId: stepUp.serverId,
authKind: "step_up",
authorizationUrl: outcome.authorizationUrl,
authChallenge: outcome.challenge,
recoverySource: stepUp.source,
});
return;
}
if (outcome.kind === "failed") {
const failureMessage = emaStepUpFailureMessage(
outcome.error.message,
);
notifications.show({
title: "Organization permissions",
message: failureMessage,
color: "red",
autoClose: 6000,
});
switch (stepUp.source) {
case "tool":
setToolCallState({
status: "error",
error: failureMessage,
});
break;
case "prompt":
setGetPromptState((prev) =>
prev
? { ...prev, status: "error", error: failureMessage }
: prev,
);
break;
case "resource":
setReadResourceState((prev) =>
prev
? { ...prev, status: "error", error: failureMessage }
: prev,
);
break;
default:
break;
}
}
} finally {
stepUpAuthorizeInProgressRef.current = false;
}
return;
}
stepUpAuthorizeInProgressRef.current = true;
prepareOAuthRedirect({
serverId: stepUp.serverId,
authKind: "step_up",
authorizationUrl: stepUp.authorizationUrl,
authChallenge: stepUp.challenge,
recoverySource: stepUp.source,
});
setPendingStepUp(null);
pendingStepUpRetryRef.current = null;
stepUpAuthorizeInProgressRef.current = false;
}}
onCancel={() => {
const stepUp = pendingStepUpRef.current;
setPendingStepUp(null);
pendingStepUpRetryRef.current = null;
if (!stepUp) {
return;
}
const cancelled = "Authorization cancelled.";
switch (stepUp.source) {
case "tool":
setToolCallState({ status: "error", error: cancelled });
break;
case "prompt":
setGetPromptState((prev) =>
prev ? { ...prev, status: "error", error: cancelled } : prev,
);
break;
case "resource":
setReadResourceState((prev) =>
prev ? { ...prev, status: "error", error: cancelled } : prev,
);
break;
case "app":
notifications.show({
title: "Authorization cancelled",
message: cancelled,
color: "gray",
autoClose: 4000,
});
break;
case "ambient":
break;
}
}}
onAuthorize={handleStepUpAuthorize}
onCancel={handleStepUpCancel}
/>
</>
);

View file

@ -1,4 +1,4 @@
import { Alert, Button, Group } from "@mantine/core";
import { Alert, Button, Group, Text } from "@mantine/core";
export interface ReAuthBannerProps {
message: string;
@ -14,19 +14,15 @@ export function ReAuthBanner({
return (
<Alert
color="red"
variant="light"
variant="reauth"
title="Re-authentication required"
withCloseButton
onClose={onDismiss}
styles={{
root: {
backgroundColor: "var(--mantine-color-body)",
border: "1px solid var(--mantine-color-red-3)",
},
}}
>
<Group justify="space-between" align="center" wrap="nowrap" gap="md">
<span>{message}</span>
<Text component="span" size="sm">
{message}
</Text>
<Button size="xs" variant="filled" onClick={onReauthenticate}>
Re-authenticate
</Button>

View file

@ -30,6 +30,7 @@ export function StepUpAuthModal({
}: StepUpAuthModalProps) {
const additionalScopes = challenge ? stepUpAdditionalScopes(challenge) : [];
const ema = enterpriseManaged === true;
const handleAuthorize = () => void onAuthorize();
return (
<Modal
@ -79,7 +80,7 @@ export function StepUpAuthModal({
<Button variant="default" onClick={onCancel}>
Cancel
</Button>
<Button onClick={() => void onAuthorize()}>Authorize</Button>
<Button onClick={handleAuthorize}>Authorize</Button>
</Actions>
</Stack>
</Modal>

View file

@ -4,4 +4,17 @@ export const ThemeAlert = Alert.extend({
defaultProps: {
radius: "md",
},
styles: (_theme, props) => {
// Re-auth banner: body-colored surface with a red hairline border, used
// for the persistent mid-session re-authentication banner.
if (props.variant === "reauth") {
return {
root: {
backgroundColor: "var(--mantine-color-body)",
border: "1px solid var(--mantine-color-red-3)",
},
};
}
return { root: {} };
},
});