Skip to content
104 changes: 100 additions & 4 deletions artifacts/sandbox-ai/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,34 @@ const queryClient = new QueryClient();
const clerkPubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY;
const clerkProxyUrl = import.meta.env.VITE_CLERK_PROXY_URL;
const basePath = import.meta.env.BASE_URL.replace(/\/$/, "");
const requestedAuthMode = (import.meta.env.VITE_AUTH_MODE ?? "clerk").toLowerCase();
const authMode = requestedAuthMode === "public" ? "public" : "clerk";
const isClerkConfigured = Boolean(clerkPubKey);
const isAuthEnabled = authMode === "clerk" && isClerkConfigured;

const envChecklist = [
{
key: "VITE_AUTH_MODE",
value: import.meta.env.VITE_AUTH_MODE,
required: false,
status: "info" as const,
hint: "Optional. Use 'clerk' (default) or 'public'.",
},
{
key: "VITE_CLERK_PUBLISHABLE_KEY",
value: clerkPubKey,
required: authMode === "clerk",
status: isClerkConfigured ? ("ok" as const) : ("missing" as const),
hint: "Required when VITE_AUTH_MODE=clerk.",
},
{
key: "VITE_CLERK_PROXY_URL",
value: clerkProxyUrl,
required: false,
status: "info" as const,
hint: "Optional proxy URL for Clerk.",
},
];

function stripBase(path: string): string {
return basePath && path.startsWith(basePath)
Expand Down Expand Up @@ -198,6 +226,33 @@ function ClerkAuthTokenSetter() {
return null;
}


function HealthConfigPage() {
return (
<div className="min-h-screen" style={{ backgroundColor: "var(--sb-bg)", color: "#fff", padding: 24 }}>
<h1 style={{ fontSize: 28, fontWeight: 800, marginBottom: 12 }}>Health Config</h1>
<p style={{ opacity: 0.8, marginBottom: 20 }}>
Runtime auth mode: <strong>{authMode}</strong>. Auth enabled: <strong>{String(isAuthEnabled)}</strong>.
</p>
<div style={{ display: "grid", gap: 10 }}>
{envChecklist.map((item) => (
<div key={item.key} style={{ border: "1px solid rgba(255,255,255,0.15)", borderRadius: 10, padding: 12 }}>
<div style={{ display: "flex", justifyContent: "space-between", gap: 12 }}>
<code>{item.key}</code>
<strong>
{item.status === "ok" ? "✅ configured" : item.status === "missing" ? "❌ missing" : "ℹ️ optional"}
</strong>
</div>
<div style={{ marginTop: 6, fontSize: 13, opacity: 0.85 }}>required: {String(item.required)}</div>
<div style={{ marginTop: 6, fontSize: 13, opacity: 0.85 }}>value: {item.value ? "set" : "empty"}</div>
<div style={{ marginTop: 6, fontSize: 13, opacity: 0.85 }}>{item.hint}</div>
</div>
))}
</div>
</div>
);
}

function Router() {
return (
<Switch>
Expand All @@ -207,6 +262,7 @@ function Router() {
<Route path="/sign-in/*?" component={SignInPage} />
<Route path="/sign-up/*?" component={SignUpPage} />
<Route path="/auth-check" component={AuthCheck} />
<Route path="/health-config" component={HealthConfigPage} />
<Route path="/prompts">
<ProtectedRoute><Prompts /></ProtectedRoute>
</Route>
Expand Down Expand Up @@ -258,14 +314,54 @@ function ClerkProviderWithRoutes() {
);
}


function PublicOnlyRouter() {
return (
<Switch>
<Route path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/pricing" component={Pricing} />
<Route path="/get-app" component={GetApp} />
<Route path="/download" component={Download} />
<Route path="/health-config" component={HealthConfigPage} />
<Route path="/sign-in/*?">
<Redirect to="/" />
</Route>
<Route path="/sign-up/*?">
<Redirect to="/" />
</Route>
<Route path="/chat">
<Redirect to="/" />
</Route>
<Route path="/chat/:id">
<Redirect to="/" />
</Route>
<Route component={NotFound} />
</Switch>
);
}

function PublicAppWithoutAuth() {
return (
<QueryClientProvider client={queryClient}>
<TooltipProvider>
<div style={{ color: "#fff", padding: 16, fontSize: 14, opacity: 0.85 }}>
Auth is running in public mode. Open <code>/health-config</code> to inspect environment readiness.
</div>
<PublicOnlyRouter />
<Toaster />
</TooltipProvider>
</QueryClientProvider>
);
}
function App() {
const [showSplash, setShowSplash] = useState(true);

if (!clerkPubKey) {
if (!isAuthEnabled) {
return (
<div style={{ color: "#fff", padding: 24 }}>
Missing VITE_CLERK_PUBLISHABLE_KEY — please check environment variables.
</div>
<WouterRouter base={basePath}>
<PublicAppWithoutAuth />
</WouterRouter>
);
}

Expand Down
27 changes: 27 additions & 0 deletions docs_self_healing_process.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Self-healing reminder workflow (OpenClaw/Eios style)

## Goal
Automatically remind maintainers when deployment-critical configuration or code artifacts are missing.

## 1) Detect
- On every deploy, open `/health-config` and validate required env keys.
- On CI, run `pnpm --filter @workspace/sandbox-ai run build` and fail on errors.

## 2) Classify
- **Config missing** (e.g. `VITE_CLERK_PUBLISHABLE_KEY` when `VITE_AUTH_MODE=clerk`).
- **Artifact missing** (route/page/module referenced but not found).
- **Type/build breakage** (TypeScript/build errors).

## 3) Notify owner
- Config missing -> notify DevOps/release owner.
- Artifact missing -> notify feature owner/repo maintainer.
- Build/type breakage -> notify author of latest PR and reviewer.

## 4) Auto-remediation checklist
- If auth env missing in non-production: set `VITE_AUTH_MODE=public` as temporary fallback.
- If auth env missing in production: block release and require secret injection.
- If artifact missing: create placeholder file + TODO with owner and deadline.

## 5) Prevent regressions
- Add a PR checklist item: “Did you verify `/health-config` and auth mode behavior?”
- Keep a short runbook for required variables per environment.
Loading