Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ui-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ jobs:
- name: Build UI
env:
VITE_GITTENSORY_API_ORIGIN: https://gittensory-api.aethereal.dev
# Preview-only: enables the synthetic demo session (useSession().signInPreview) so reviewbot can
# screenshot the authenticated /app/* dashboard via ?preview=1 instead of the sign-in wall. The
# production build (ui-deploy.yml) does NOT set this, so the escape hatch is dead-code-eliminated
# from prod. (#authed-route-preview)
VITE_PREVIEW: "1"
run: npm run ui:build

# The trusted deploy workflow downloads this by name + run-id. It contains only the built bundle
Expand Down
14 changes: 12 additions & 2 deletions apps/gittensory-ui/src/components/site/app-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import type { ComponentType } from "react";
import { useEffect, useState } from "react";

import { useSession } from "@/lib/api/session";
import { PREVIEW_SESSION_ALLOWED, useSession } from "@/lib/api/session";
import { describeApiStatus, pingHealth, useApiStatus } from "@/lib/api/status";
import {
Sidebar,
Expand Down Expand Up @@ -85,7 +85,7 @@ const GROUPS: NavGroup[] = [
];

export function AppShell() {
const { session, hydrated, signOut } = useSession();
const { session, hydrated, signOut, signInPreview } = useSession();
const loc = useLocation();
const navigate = useNavigate();
const routerState = useRouterState();
Expand All @@ -96,6 +96,16 @@ export function AppShell() {
setSidebarOpen(m ? m[1] === "true" : true);
}, []);

// Preview deploys: when this is a preview build (VITE_PREVIEW) and the URL carries `?preview=1`, start
// the synthetic demo session automatically once hydration confirms there's no real session. This lets the
// reviewbot screenshot pipeline capture the authenticated /app/* UI instead of the sign-in wall. The
// effect depends on `session`, so it self-heals if a later refresh clears the synthetic session. Inert in
// production (PREVIEW_SESSION_ALLOWED is compiled to false) and without the param. (#authed-route-preview)
useEffect(() => {
if (!PREVIEW_SESSION_ALLOWED || session || !hydrated) return;
if (new URLSearchParams(window.location.search).get("preview") === "1") signInPreview();
}, [session, hydrated, signInPreview]);

// Keyboard shortcuts: g+o overview, g+w workbench, g+r runs, g+p repos, g+a analytics.
useEffect(() => {
if (!session) return;
Expand Down
13 changes: 10 additions & 3 deletions apps/gittensory-ui/src/lib/api/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ function emitSessionChanged() {
if (typeof window !== "undefined") window.dispatchEvent(new Event(SESSION_CHANGED_EVENT));
}

// The synthetic local-preview session is available in `vite dev` (DEV) AND on dedicated preview deploys
// built with VITE_PREVIEW=1 (the per-PR `ui-preview.yml` build sets it; production builds never do, so this
// escape hatch is dead-code-eliminated from prod). It writes no real token and grants only a client-side
// demo session — it lets the reviewbot screenshot pipeline render the authenticated /app/* UI instead of a
// screenshot of the sign-in wall. (#authed-route-preview)
export const PREVIEW_SESSION_ALLOWED = import.meta.env.DEV || import.meta.env.VITE_PREVIEW === "1";

export function useSession() {
const [session, setSession] = useState<AppSession | null>(null);
const [hydrated, setHydrated] = useState(false);
Expand Down Expand Up @@ -96,16 +103,16 @@ export function useSession() {
};

const signInPreview = () => {
if (!import.meta.env.DEV) return;
if (!PREVIEW_SESSION_ALLOWED) return;
setSession({
login: "local-preview",
roles: ["miner", "maintainer", "owner", "operator"],
confirmed_miner: false,
});
setHydrated(true);
setAuth({ status: "idle" });
toast.success("Local preview session started", {
description: "This exists only in dev mode and never writes a production token.",
toast.success("Preview session started", {
description: "A demo session for dev + preview deploys — it never writes a production token.",
});
};

Expand Down
Loading