diff --git a/apps/web/src/components/auth/AuthSurfaceShell.tsx b/apps/web/src/components/auth/AuthSurfaceShell.tsx
index 1c45c98a9e49120ea47a14cb353594d2330d68d2..ed27bd482a5d9fbbc1293c53fc2717cce945f854 100644
--- a/apps/web/src/components/auth/AuthSurfaceShell.tsx
+++ b/apps/web/src/components/auth/AuthSurfaceShell.tsx
@@ -2,22 +2,18 @@ import type { ReactNode } from "react";
import { APP_DISPLAY_NAME, APP_STAGE_LABEL } from "../../branding";
import { resolveSidebarStageBackdropVariant, StageBackdropArt } from "../SidebarStageBackdrop";
+import { StandalonePage } from "../ui/standalone-page";
/**
- * Full-screen card for standalone auth pages, mirroring the pairing surface's
- * treatment. Used by the CLI-connect authorize and callback surfaces.
+ * Branded masthead for the CLI-connect authorize and callback pages.
*/
export function AuthSurfaceShell({ children }: { readonly children: ReactNode }) {
const stageVariant = resolveSidebarStageBackdropVariant(APP_STAGE_LABEL);
return (
-
-
-
-
+
{stageVariant ? (
@@ -36,9 +32,9 @@ export function AuthSurfaceShell({ children }: { readonly children: ReactNode })
-
- {children}
-
-
+ }
+ >
+ {children}
+
);
}
diff --git a/apps/web/src/components/auth/PairingRouteSurface.tsx b/apps/web/src/components/auth/PairingRouteSurface.tsx
index 59288506569c412db5e9a0a960fe43010c732c2c..cad11af57fc6418c29d18484cf472251d7cfa0c3 100644
--- a/apps/web/src/components/auth/PairingRouteSurface.tsx
+++ b/apps/web/src/components/auth/PairingRouteSurface.tsx
@@ -12,29 +12,18 @@ import {
import { readHostedPairingRequest } from "../../hostedPairing";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
+import { StandalonePage, StandalonePageHeader } from "../ui/standalone-page";
import { useAtomCommand } from "../../state/use-atom-command";
export function PairingPendingSurface() {
return (
-
-
-
-
-
- {APP_DISPLAY_NAME}
-
-
- Pairing with this environment
-
-
- Validating the pairing link and preparing your session.
-
-
-
+
+
+
);
}
@@ -97,69 +86,57 @@ export function PairingRouteSurface({
}, [submitCredential]);
return (
-
-
-
-
-
+
+
+
+
+
+
+ {describeSupportedMethods(auth.bootstrapMethods)}
-
-
-
- {APP_DISPLAY_NAME}
-
-
- Pair with this environment
-
-
- {describeAuthGate(auth.bootstrapMethods)}
-
-
-
-
-
- {describeSupportedMethods(auth.bootstrapMethods)}
-
-
-
+
);
}
@@ -233,57 +210,49 @@ export function HostedPairingRouteSurface() {
const request = hostedPairingRequestRef.current;
return (
-
-
-
-
-
- {APP_DISPLAY_NAME}
-
-
- {status === "paired"
+
+
- {message}
+ : "Pairing backend"
+ }
+ description={message}
+ />
- {request ? (
-
- Host: {request.host}
-
- ) : null}
-
- {status === "error" ? (
-
- Verify the backend is reachable from this browser, supports CORS for hosted clients, and
- is served over HTTPS when opening this page from HTTPS.
-
- ) : null}
+ {request ? (
+
+ Host: {request.host}
+
+ ) : null}
-
- {status === "pairing" ? (
-
- ) : canRetry ? (
-
- ) : null}
- {status === "paired" ? (
-
- ) : null}
+ {status === "error" ? (
+
+ Verify the backend is reachable from this browser, supports CORS for hosted clients, and
+ is served over HTTPS when opening this page from HTTPS.
-
-
+ ) : null}
+
+
+ {status === "pairing" ? (
+
+ ) : canRetry ? (
+
+ ) : null}
+ {status === "paired" ? (
+
+ ) : null}
+
+
);
}
diff --git a/apps/web/src/components/ui/standalone-page.tsx b/apps/web/src/components/ui/standalone-page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f44ec18f6825a3bc893f1fe0dfdff17418e42aa3
--- /dev/null
+++ b/apps/web/src/components/ui/standalone-page.tsx
@@ -0,0 +1,67 @@
+import type { ReactNode } from "react";
+
+const backdropClassNames = {
+ pairing:
+ "absolute inset-x-0 top-0 h-44 bg-[radial-gradient(44rem_16rem_at_top,color-mix(in_srgb,var(--color-emerald-500)_14%,transparent),transparent)]",
+ error:
+ "absolute inset-x-0 top-0 h-44 bg-[radial-gradient(44rem_16rem_at_top,color-mix(in_srgb,var(--color-red-500)_16%,transparent),transparent)]",
+ brand:
+ "absolute inset-x-0 top-0 h-72 bg-[radial-gradient(48rem_20rem_at_top,color-mix(in_srgb,var(--color-blue-500)_12%,transparent),transparent)]",
+};
+
+/** Shared page and card geometry for entry points outside the app shell. */
+export function StandalonePage({
+ tone,
+ masthead,
+ children,
+}: {
+ readonly tone: keyof typeof backdropClassNames;
+ readonly masthead?: ReactNode;
+ readonly children: ReactNode;
+}) {
+ return (
+
+
+
+ {tone === "pairing" ? (
+
+ ) : null}
+
+
+
+
+ {masthead}
+ {children}
+
+
+ );
+}
+
+/** Entry-page headings keep their typography and spacing together. */
+export function StandalonePageHeader({
+ eyebrow,
+ title,
+ description,
+}: {
+ readonly eyebrow: ReactNode;
+ readonly title: ReactNode;
+ readonly description: ReactNode;
+}) {
+ return (
+ <>
+
+ {eyebrow}
+
+ {title}
+ {description}
+ >
+ );
+}
diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx
index 42e40842a721cdd1074d0bb2861f19ea737f01ca..eee703ac3f4718e7e37f86c6c530b4c0ca536a01 100644
--- a/apps/web/src/routes/__root.tsx
+++ b/apps/web/src/routes/__root.tsx
@@ -35,6 +35,7 @@ import { useCopyToClipboard } from "../hooks/useCopyToClipboard";
import { useDefaultThemeAdoption } from "../hooks/useDefaultTheme";
import { useEnvironmentThemeSync } from "../hooks/useEnvironmentTheme";
import { Button } from "../components/ui/button";
+import { StandalonePage, StandalonePageHeader } from "../components/ui/standalone-page";
import {
AnchoredToastProvider,
stackedThreadToast,
@@ -368,39 +369,30 @@ function RootRouteErrorView({ error }: ErrorComponentProps) {
const report = useMemo(() => errorReport(error, pathname), [error, pathname]);
return (
-
-
-
-
-
- {APP_DISPLAY_NAME}
-
-
- Something went wrong.
-
- {message}
+
+
-
-
-
-
-
+
+
+
+
+
-
-
Error report
-
- {report}
-
-
-
-
+
+
Error report
+
+ {report}
+
+
+
);
}