Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

Commit

A part of a page that fails stays a part: a card that breaks while drawing says so in its place, and a page that fails shows its error inside its repository's, workspace's or settings' frame, not over the whole app. Before, the root route was the only error boundary, so anything failing on any page replaced everything below the app shell, the repository's header and tabs included. Cards: Boundary (components/boundary.tsx) catches what breaks while drawing and shows Unavailable ("X could not be loaded just now") in its place, drawing again on the next refresh or navigation; every Panel wraps its contents in one. Data: lib/partial.server.ts soft() is the loader side, for a part beside what the page is about: null when it fails, which the part shows as Unavailable. Pages: routes.ts wraps the pages of a repository, a workspace and your settings in a pathless route (routes/page-boundary.tsx) whose boundary renders inside the layout's Outlet (a layout's own boundary would replace the layout too). Its error view is the root's, shared as RouteError (components/route-error.tsx): the stale-build reload, the self-healing "Can't reach g1t", the 404 page and developer details, now with Try again; contained, it says the rest of g1t is fine. The root keeps it as the last resort. Checked: apps/web tsc -b (with react-router typegen); npm test (986).

G1t-Session:
ses_01m4zzpc96ect8d41mffgr1a45
syntaqxcommitted Parent4103eb9Browse files
7 files+495−3690/7 viewed
+70−0
1+import { Component, type ErrorInfo, type ReactNode, useEffect, useRef, useState } from "react";
2+import { TriangleAlert } from "lucide-react";
3+import { useLocation, useRevalidator } from "react-router";
4+
5+import { Card } from "./ui/card";
6+
7+/** Says a section could not be loaded, without taking the page down. */
8+export function Unavailable({ what }: { what: string }) {
9+ return (
10+ <Card asChild tone="plain" className="flex items-center gap-2 border-dashed px-4 py-5 text-sm text-muted">
11+ <p>
12+ <TriangleAlert size={14} className="text-warn" />
13+ {what} could not be loaded just now. It will be back on the next refresh.
14+ </p>
15+ </Card>
16+ );
17+}
18+
19+/**
20+ * Keeps a part of a page that fails to draw to itself: in its place it says
21+ * that `what` could not be loaded, and the rest of the page stays. It tries
22+ * again on the next refresh or navigation (whatever reloads the page's
23+ * data). Data a part could not fetch is the loader's to say
24+ * (lib/partial.server.ts `soft`); this catches what breaks while drawing.
25+ */
26+export function Boundary({ what, children, fallback }: { what: string; children: ReactNode; fallback?: ReactNode }) {
27+ const { key } = useLocation();
28+ const loads = useLoads();
29+ return (
30+ <Catch what={what} fallback={fallback} reset={`${key}:${loads}`}>
31+ {children}
32+ </Catch>
33+ );
34+}
35+
36+/** How many times the page's data has loaded again in place (a refresh, a form). */
37+function useLoads(): number {
38+ const { state } = useRevalidator();
39+ const [loads, setLoads] = useState(0);
40+ const was = useRef(state);
41+ useEffect(() => {
42+ if (was.current !== "idle" && state === "idle") setLoads((n) => n + 1);
43+ was.current = state;
44+ }, [state]);
45+ return loads;
46+}
47+
48+type CatchProps = { what: string; fallback?: ReactNode; reset: string; children: ReactNode };
49+
50+class Catch extends Component<CatchProps, { failed: boolean }> {
51+ state = { failed: false };
52+
53+ static getDerivedStateFromError() {
54+ return { failed: true };
55+ }
56+
57+ componentDidCatch(error: unknown, info: ErrorInfo) {
58+ console.error(`g1t: ${this.props.what} failed to draw`, error, info.componentStack);
59+ }
60+
61+ componentDidUpdate(previous: CatchProps) {
62+ // Fresh data, or another page: draw it again.
63+ if (this.state.failed && previous.reset !== this.props.reset) this.setState({ failed: false });
64+ }
65+
66+ render() {
67+ if (this.state.failed) return this.props.fallback ?? <Unavailable what={this.props.what} />;
68+ return this.props.children;
69+ }
70+}
+8−12
1111
1212 import { cn } from "../lib/cn";
1313 import { type Need, type NeedKind, sparkPoints } from "../lib/mission";
14+import { Boundary } from "./boundary";
1415 import { TimeAgo } from "./ui";
1516 import { Button } from "./ui/button";
1617 import { Card } from "./ui/card";
2829 extra,
2930 children,
3031 className,
32+ what,
3133 }: {
3234 id?: string;
3335 title: ReactNode;
36+ /** What it shows, in a few words, said if it fails to draw; its title when that is text. */
37+ what?: string;
3438 icon?: ReactNode;
3539 count?: number | null;
3640 all?: { to: string; label?: string } | null;
5761 </Link>
5862 )}
5963 </div>
60− <div className="mt-3">{children}</div>
64+ <div className="mt-3">
65+ <Boundary what={what ?? (typeof title === "string" ? title : "This section")}>{children}</Boundary>
66+ </div>
6167 </section>
6268 );
6369 }
6470
65−/** Says a section could not be loaded, without taking the page down. */
66−export function Unavailable({ what }: { what: string }) {
67− return (
68− <Card asChild tone="plain" className="flex items-center gap-2 border-dashed px-4 py-5 text-sm text-muted">
69− <p>
70− <TriangleAlert size={14} className="text-warn" />
71− {what} could not be loaded just now. It will be back on the next refresh.
72− </p>
73− </Card>
74− );
75−}
71+export { Unavailable } from "./boundary";
7672
7773 /** A quiet, dashed empty state with an optional call to action. */
7874 export function Quiet({ children, action }: { children: ReactNode; action?: ReactNode }) {
+132−0
1+import { useEffect, useMemo, useState } from "react";
2+import { Link, isRouteErrorResponse, useLocation, useMatches, useRevalidator, useRouteLoaderData } from "react-router";
3+
4+import { DebugDetails } from "./debug-details";
5+import { PageMain } from "./landmark";
6+import { NotFound } from "./not-found";
7+import { Button } from "./ui/button";
8+import { ButtonLink } from "./ui";
9+import { useDeveloperMode } from "../lib/developer-mode";
10+import { useReconnect } from "../lib/reconnect";
11+import { isNetworkFailure } from "../lib/resilient-fetch";
12+import { RELOADED_KEY, RELOAD_GIVE_UP_MS, clientNavigated, reloadFixes, reloadedBefore } from "../lib/stale-build";
13+import { isNeedsSignIn } from "../lib/website-token";
14+
15+/**
16+ * What a page shows in place of itself when it failed: the root's error
17+ * page, and, `contained`, the one a repository's, workspace's or settings'
18+ * page shows inside its frame (routes/page-boundary.tsx), so its header,
19+ * tabs and sidebar stay and only the page itself is replaced.
20+ *
21+ * A tab left open across a deploy loads the address again, once; no answer
22+ * from g1t at all keeps trying and comes back by itself (lib/reconnect.ts);
23+ * a 404 is the not-found page; anything else says what went wrong, with
24+ * a way to try again, and in developer mode the error in full.
25+ */
26+export function RouteError({ error, contained = false }: { error: unknown; contained?: boolean }) {
27+ const location = useLocation();
28+ const matches = useMatches();
29+ const revalidator = useRevalidator();
30+ const href = location.pathname + location.search + location.hash;
31+ // A tab left open across a deploy: load the address again as a whole
32+ // page, once, rather than show an error the reload fixes. A document load
33+ // never reloads, so the server and the hydrating client render the same.
34+ const reloadable = reloadFixes({
35+ error,
36+ status: isRouteErrorResponse(error) ? error.status : undefined,
37+ clientNavigation: clientNavigated(location.key),
38+ caughtByCatchAll: matches.at(-1)?.id === "routes/not-found",
39+ });
40+ // Already loaded again once, or the reload never started: show the page.
41+ const tried = useMemo(() => reloadable && reloadedBefore(href), [reloadable, href]);
42+ // The error in full, for reporting it (lib/developer-mode.ts).
43+ const root = useRouteLoaderData("root") as { developer?: boolean } | undefined;
44+ const developer = useDeveloperMode(root?.developer) || import.meta.env.DEV;
45+ const [gaveUp, setGaveUp] = useState<string | null>(null);
46+ const reload = reloadable && !tried && gaveUp !== href;
47+ // No answer from g1t at all: keep trying, and the page comes back by itself.
48+ const offline = !reload && isNetworkFailure(error);
49+ useReconnect(offline);
50+ useEffect(() => {
51+ if (!reload) return;
52+ try {
53+ sessionStorage.setItem(RELOADED_KEY, href);
54+ } catch {
55+ // No session storage: reload anyway; a document load never asks again.
56+ }
57+ window.location.assign(href);
58+ const timer = window.setTimeout(() => setGaveUp(href), RELOAD_GIVE_UP_MS);
59+ return () => window.clearTimeout(timer);
60+ }, [reload, href]);
61+
62+ const space = contained ? "py-16" : "py-32";
63+ if (reload) {
64+ return (
65+ <PageMain className={`mx-auto max-w-xl px-4 ${space} text-center text-sm text-muted`} aria-busy="true">
66+ <title>Loading · g1t</title>
67+ Loading the latest version of g1t…
68+ </PageMain>
69+ );
70+ }
71+
72+ if (offline) {
73+ return (
74+ <PageMain className={`mx-auto max-w-xl px-4 ${space} text-center`} aria-busy="true">
75+ <title>Reconnecting · g1t</title>
76+ <h1 className="text-2xl font-semibold tracking-tight">Can't reach g1t</h1>
77+ <p className="mt-3 text-muted">Check your connection. This page loads by itself once g1t answers.</p>
78+ </PageMain>
79+ );
80+ }
81+
82+ let title = "Something went wrong";
83+ let details = contained ? "This page didn't load. The rest of g1t is fine; try again in a moment." : "An unexpected error occurred. Try again in a moment.";
84+
85+ if (isRouteErrorResponse(error)) {
86+ // The same page for something private and something missing.
87+ if (error.status === 404) {
88+ return (
89+ <>
90+ <title>Not found · g1t</title>
91+ <NotFound data={error.data} />
92+ </>
93+ );
94+ }
95+ if (typeof error.data === "string" && error.data) {
96+ details = error.data;
97+ }
98+ // A token asked for what needs a real sign-in (lib/website-token.ts).
99+ if (isNeedsSignIn(error.data)) {
100+ title = "This needs you to sign in";
101+ details = error.data.message;
102+ }
103+ }
104+
105+ return (
106+ <PageMain className={`mx-auto px-4 text-center ${developer ? `max-w-3xl ${contained ? "py-10" : "py-16"}` : `max-w-xl ${space}`}`}>
107+ <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
108+ <p className="mt-3 text-muted">{details}</p>
109+ <div className="mt-8 flex justify-center gap-2">
110+ <Button variant="outline" disabled={revalidator.state !== "idle"} onClick={() => void revalidator.revalidate()}>
111+ {revalidator.state !== "idle" ? "Trying again…" : "Try again"}
112+ </Button>
113+ {!contained && (
114+ <ButtonLink to="/" variant="ghost">
115+ Back to home
116+ </ButtonLink>
117+ )}
118+ </div>
119+ {developer ? (
120+ <DebugDetails error={error} />
121+ ) : (
122+ <p className="mt-6 text-xs text-faint">
123+ Turn on developer mode in{" "}
124+ <Link to="/settings/account#developer" className="underline underline-offset-4 hover:text-fg">
125+ Settings
126+ </Link>{" "}
127+ to see what went wrong.
128+ </p>
129+ )}
130+ </PageMain>
131+ );
132+}
+15−0
1+/**
2+ * A part of a page that fails to load shows as missing, with the page
3+ * still up: a loader reads what the page is about as usual (a failure
4+ * there is the page's error), and each part beside it through `soft`,
5+ * which gives null when it fails. The part then says it could not be
6+ * loaded (components/boundary.tsx `Unavailable`) and is back on the next
7+ * refresh. A 404 or a refusal is an answer, not a failure: read those as
8+ * the service gives them, before `soft` hides them.
9+ */
10+export function soft<T>(work: Promise<T>, what: string): Promise<T | null> {
11+ return work.catch((error) => {
12+ console.warn(`g1t: ${what} could not be loaded:`, error);
13+ return null;
14+ });
15+}
+2−117
9292 readDeveloperMode,
9393 useDeveloperMode,
9494 } from "./lib/developer-mode";
95−import { DebugDetails } from "./components/debug-details";
95+import { RouteError } from "./components/route-error";
9696 import { ConnectionToast } from "./components/connection-toast";
9797 import { isNetworkFailure } from "./lib/resilient-fetch";
9898 import { useReconnect } from "./lib/reconnect";
641641 }
642642
643643 export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
644− const location = useLocation();
645− const matches = useMatches();
646− const href = location.pathname + location.search + location.hash;
647− // A tab left open across a deploy: load the address again as a whole
648− // page, once, rather than show an error the reload fixes. A document load
649− // never reloads, so the server and the hydrating client render the same.
650− const reloadable = reloadFixes({
651− error,
652− status: isRouteErrorResponse(error) ? error.status : undefined,
653− clientNavigation: clientNavigated(location.key),
654− caughtByCatchAll: matches.at(-1)?.id === "routes/not-found",
655− });
656− // Already loaded again once, or the reload never started: show the page.
657− const tried = useMemo(
658− () => reloadable && reloadedBefore(href),
659− [reloadable, href],
660− );
661− // The error in full, for reporting it (lib/developer-mode.ts).
662− const developer =
663− useDeveloperMode(useRouteLoaderData<typeof loader>("root")?.developer) ||
664− import.meta.env.DEV;
665− const [gaveUp, setGaveUp] = useState<string | null>(null);
666− const reload = reloadable && !tried && gaveUp !== href;
667− // No answer from g1t at all: keep trying, and the page comes back by itself.
668− const offline = !reload && isNetworkFailure(error);
669− useReconnect(offline);
670− useEffect(() => {
671− if (!reload) return;
672− try {
673− sessionStorage.setItem(RELOADED_KEY, href);
674− } catch {
675− // No session storage: reload anyway; a document load never asks again.
676− }
677− window.location.assign(href);
678− const timer = window.setTimeout(() => setGaveUp(href), RELOAD_GIVE_UP_MS);
679− return () => window.clearTimeout(timer);
680− }, [reload, href]);
681− if (reload) {
682− return (
683− <PageMain
684− className="mx-auto max-w-xl px-4 py-32 text-center text-sm text-muted"
685− aria-busy="true"
686− >
687− <title>Loading · g1t</title>
688− Loading the latest version of g1t…
689− </PageMain>
690− );
691− }
692−
693− if (offline) {
694− return (
695− <PageMain
696− className="mx-auto max-w-xl px-4 py-32 text-center"
697− aria-busy="true"
698− >
699− <title>Reconnecting · g1t</title>
700− <h1 className="text-2xl font-semibold tracking-tight">
701− Can't reach g1t
702− </h1>
703− <p className="mt-3 text-muted">
704− Check your connection. This page loads by itself once g1t answers.
705− </p>
706− </PageMain>
707− );
708− }
709−
710− let title = "Something went wrong";
711− let details = "An unexpected error occurred. Try again in a moment.";
712−
713− if (isRouteErrorResponse(error)) {
714− // The same page for something private and something missing.
715− if (error.status === 404) {
716− return (
717− <>
718− <title>Not found · g1t</title>
719− <NotFound data={error.data} />
720− </>
721− );
722− }
723− if (typeof error.data === "string" && error.data) {
724− details = error.data;
725− }
726− // A token asked for what needs a real sign-in (lib/website-token.ts).
727− if (isNeedsSignIn(error.data)) {
728− title = "This needs you to sign in";
729− details = error.data.message;
730− }
731− }
732−
733− return (
734− <PageMain
735− className={`mx-auto px-4 text-center ${developer ? "max-w-3xl py-16" : "max-w-xl py-32"}`}
736− >
737− <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
738− <p className="mt-3 text-muted">{details}</p>
739− <div className="mt-8">
740− <ButtonLink to="/" variant="outline">
741− Back to home
742− </ButtonLink>
743− </div>
744− {developer ? (
745− <DebugDetails error={error} />
746− ) : (
747− <p className="mt-6 text-xs text-faint">
748− Turn on developer mode in{" "}
749− <Link
750− to="/settings/account#developer"
751− className="underline underline-offset-4 hover:text-fg"
752− >
753− Settings
754− </Link>{" "}
755− to see what went wrong.
756− </p>
757− )}
758− </PageMain>
759− );
644+ return <RouteError error={error} />;
760645 }
+249−240
1−import { type RouteConfig, index, route } from "@react-router/dev/routes";
1+import { type RouteConfig, index, layout, route } from "@react-router/dev/routes";
22
33 export default [
44 index("routes/home.tsx"),
2929 // Your own settings: one page each, so links can name the one they mean.
3030 // `/settings` goes to the first; an old link's `#anchor` to its page.
3131 route("settings", "routes/settings/layout.tsx", [
32− index("routes/settings/index.tsx"),
33− route("profile", "routes/settings/profile.tsx"),
34− route("emails", "routes/settings/emails.tsx"),
35− route("notifications", "routes/settings/notifications.tsx"),
36− // Invite a friend: your invites to g1t.
37− route("invites", "routes/settings/invites.tsx"),
38− route("keys", "routes/settings/keys.tsx"),
39− // Your access tokens: the list, a new one, and each one's page.
40− route("tokens", "routes/settings/tokens.tsx"),
41− route("tokens/new", "routes/settings/token-new.tsx"),
42− route("tokens/:id", "routes/settings/token.tsx"),
43− route("github", "routes/settings/github.tsx"),
44− route("applications", "routes/settings/applications.tsx"),
45− route("integrations", "routes/settings/integrations.tsx"),
46− route("two-factor", "routes/settings/two-factor.tsx"),
47− route("security-log", "routes/settings/security-log.tsx"),
48− route("account", "routes/settings/account.tsx"),
32+ // Its pages fail inside its frame (routes/page-boundary.tsx).
33+ layout("routes/page-boundary.tsx", { id: "routes/page-boundary/settings" }, [
34+ index("routes/settings/index.tsx"),
35+ route("profile", "routes/settings/profile.tsx"),
36+ route("emails", "routes/settings/emails.tsx"),
37+ route("notifications", "routes/settings/notifications.tsx"),
38+ // Invite a friend: your invites to g1t.
39+ route("invites", "routes/settings/invites.tsx"),
40+ route("keys", "routes/settings/keys.tsx"),
41+ // Your access tokens: the list, a new one, and each one's page.
42+ route("tokens", "routes/settings/tokens.tsx"),
43+ route("tokens/new", "routes/settings/token-new.tsx"),
44+ route("tokens/:id", "routes/settings/token.tsx"),
45+ route("github", "routes/settings/github.tsx"),
46+ route("applications", "routes/settings/applications.tsx"),
47+ route("integrations", "routes/settings/integrations.tsx"),
48+ route("two-factor", "routes/settings/two-factor.tsx"),
49+ route("security-log", "routes/settings/security-log.tsx"),
50+ route("account", "routes/settings/account.tsx"),
51+ ]),
4952 ]),
5053 // The account menu's header: name, primary email and invites left.
5154 route("settings/menu.json", "routes/settings-menu-json.ts"),
105108 route("install.ps1", "routes/install-script.ts", { id: "routes/install-ps1" }),
106109 // A workspace's own pages sit under `-`, which no repository can be named.
107110 route(":owner", "routes/workspace/layout.tsx", [
108− index("routes/workspace/overview.tsx"),
109− // Its pages, each in the sidebar: Overview (above), Projects, People, Insights; Teams and Packages below.
110− route("-/projects", "routes/workspace/projects.tsx"),
111− // People mode: the directory of people and each one's profile.
112− route("-/people", "routes/workspace/people.tsx"),
113− route("-/people/:username", "routes/workspace/people/person.tsx"),
114− // Workspace mode's Access: who is a member and their roles, the invitations sent, and who may do what.
115− route("-/members", "routes/workspace/members.tsx"),
116− route("-/members/invitations", "routes/workspace/member-invitations.tsx"),
117− route("-/permissions", "routes/workspace/permissions.tsx"),
118− route("-/insights", "routes/workspace/insights.tsx"),
119− // Pinning its projects, for the person signed in.
120− route("-/pins", "routes/workspace/pins.ts"),
121− // Getting started, for its owners: the guide's state, and dismissing
122− // it or skipping a step (components/onboarding.tsx).
123− route("-/onboarding", "routes/workspace/onboarding.ts"),
124− // Code's Overview: Mission control's code panels, for this workspace.
125− route("-/overview", "routes/workspace/code-overview.tsx"),
126− // The workspace itself, at a glance: members, plan and spend.
127− route("-/workspace", "routes/workspace/workspace-overview.tsx"),
128− // Teams: the list, a new one, and each team's pages.
129− route("-/teams", "routes/workspace/teams.tsx"),
130− route("-/teams/new", "routes/workspace/team-new.tsx"),
131− route("-/teams/:team", "routes/workspace/team/layout.tsx", [
132− index("routes/workspace/team/members.tsx"),
133− route("teams", "routes/workspace/team/teams.tsx"),
134− route("repositories", "routes/workspace/team/repositories.tsx"),
135− route("settings", "routes/workspace/team/settings.tsx"),
136− ]),
137− // The workspace's own access tokens: the list, a new one, and each one's page.
138− route("-/tokens", "routes/workspace/tokens.tsx"),
139− route("-/tokens/new", "routes/workspace/token-new.tsx"),
140− route("-/tokens/:id", "routes/workspace/token.tsx"),
141− // Its rules for members' personal access tokens, and approving them.
142− route("-/personal-access-tokens", "routes/workspace/personal-access-tokens.tsx"),
143− route("-/usage", "routes/workspace/usage.tsx"),
144− route("-/billing", "routes/workspace/billing.tsx"),
145− route("-/billing/entries", "routes/workspace/statement-entries.ts"),
146− route("-/billing/statement.csv", "routes/workspace/statement-csv.ts"),
147− // The workspace's AI: its AI providers, managed or self-hosted, and
148− // what Ask answers with. A base setting, not an integration.
149− route("-/ai", "routes/workspace/ai.tsx"),
150− // The integrations directory, and the setup pages for alerts and
151− // trackers (lib/integration-sections).
152− route("-/integrations", "routes/workspace/integrations-directory.tsx"),
153− route("-/integrations/:section", "routes/workspace/integrations.tsx"),
154− // Ask g1t, from the top bar: what it answers with, and each answer,
155− // streamed (components/ask-composer).
156− route("-/ask", "routes/workspace/ask.ts"),
157− route("-/webhooks", "routes/workspace/webhooks.tsx"),
158− // The workspace's own emoji, for chat (components/emoji).
159− route("-/emoji", "routes/workspace/emoji.tsx"),
160− route("-/secrets", "routes/workspace/secrets.tsx"),
161− route("-/runners", "routes/workspace/runners.tsx"),
162− route("-/actions", "routes/workspace/actions-settings.tsx"),
163− route("-/packages", "routes/workspace/packages.tsx"),
164− route("-/packages/:ecosystem/*", "routes/workspace/package.tsx"),
165− route("-/settings", "routes/workspace/settings.tsx"),
166− // What members may do in chat: channels, emoji, default channels.
167− route("-/settings/chat", "routes/workspace/chat-settings.tsx"),
168− route("-/repositories", "routes/workspace/repositories.tsx"),
169− // Chat mode: channels by name, direct messages by id, and what the page
170− // calls as it runs: the live socket and the JSON for sending and reading.
171− route("-/chat/live", "routes/workspace/chat/live.ts"),
172− route("-/chat/api", "routes/workspace/chat/api.ts"),
173− // A person's card in Chat: their profile and teams here.
174− route("-/chat/person/:username", "routes/workspace/chat/person.ts"),
175− // An issue or pull request's card, over its chip in chat and g1t's answers.
176− route("-/chat/ref", "routes/workspace/chat/ref.ts"),
177− route("-/chat", "routes/workspace/chat/layout.tsx", [
178− index("routes/workspace/chat/index.tsx"),
179− route("browse", "routes/workspace/chat/browse.tsx"),
180− route("dm/:id", "routes/workspace/chat/channel.tsx", { id: "routes/workspace/chat/dm" }),
181− route(":channel", "routes/workspace/chat/channel.tsx"),
182− ]),
183− // Artifacts mode (code says "folio"): what its
184− // pages call as they run (an artifact's live socket, JSON, comments,
185− // uploads, export), then Home, making one, templates, the trash, the
186− // spaces, and each artifact by its address. Addresses are flat and end
187− // in the id, so renaming or moving one keeps links working.
188− route("-/artifacts/live", "routes/workspace/folios/live.ts"),
189− route("-/artifacts/api", "routes/workspace/folios/api.ts"),
190− route("-/artifacts/threads/:folio/*", "routes/workspace/folios/threads.ts"),
191− route("-/artifacts/upload", "routes/workspace/folios/upload.ts"),
192− route("-/artifacts/export", "routes/workspace/folios/export.ts"),
193− route("-/artifacts", "routes/workspace/folios/layout.tsx", [
194− index("routes/workspace/folios/home.tsx"),
195− route("new/:kind", "routes/workspace/folios/new.tsx"),
196− route("templates", "routes/workspace/folios/templates.tsx"),
197− route("trash", "routes/workspace/folios/trash.tsx"),
198− // Artifacts possibly out of date, and a project's docs folder, read-only.
199− route("stale", "routes/workspace/folios/stale.tsx"),
200− route("spaces", "routes/workspace/folios/spaces.tsx"),
201− route("spaces/new", "routes/workspace/folios/space-new.tsx"),
202− route("spaces/:space", "routes/workspace/folios/space.tsx"),
203− route("spaces/:space/settings", "routes/workspace/folios/space-settings.tsx"),
204− route("repo/:repoOwner/:repoName/*", "routes/workspace/folios/repo-file.tsx"),
205− route(":folio", "routes/workspace/folios/folio.tsx"),
206− route(":folio/history", "routes/workspace/folios/history.tsx"),
111+ // Its pages fail inside its frame (routes/page-boundary.tsx).
112+ layout("routes/page-boundary.tsx", { id: "routes/page-boundary/workspace" }, [
113+ index("routes/workspace/overview.tsx"),
114+ // Its pages, each in the sidebar: Overview (above), Projects, People, Insights; Teams and Packages below.
115+ route("-/projects", "routes/workspace/projects.tsx"),
116+ // People mode: the directory of people and each one's profile.
117+ route("-/people", "routes/workspace/people.tsx"),
118+ route("-/people/:username", "routes/workspace/people/person.tsx"),
119+ // Workspace mode's Access: who is a member and their roles, the invitations sent, and who may do what.
120+ route("-/members", "routes/workspace/members.tsx"),
121+ route("-/members/invitations", "routes/workspace/member-invitations.tsx"),
122+ route("-/permissions", "routes/workspace/permissions.tsx"),
123+ route("-/insights", "routes/workspace/insights.tsx"),
124+ // Pinning its projects, for the person signed in.
125+ route("-/pins", "routes/workspace/pins.ts"),
126+ // Getting started, for its owners: the guide's state, and dismissing
127+ // it or skipping a step (components/onboarding.tsx).
128+ route("-/onboarding", "routes/workspace/onboarding.ts"),
129+ // Code's Overview: Mission control's code panels, for this workspace.
130+ route("-/overview", "routes/workspace/code-overview.tsx"),
131+ // The workspace itself, at a glance: members, plan and spend.
132+ route("-/workspace", "routes/workspace/workspace-overview.tsx"),
133+ // Teams: the list, a new one, and each team's pages.
134+ route("-/teams", "routes/workspace/teams.tsx"),
135+ route("-/teams/new", "routes/workspace/team-new.tsx"),
136+ route("-/teams/:team", "routes/workspace/team/layout.tsx", [
137+ index("routes/workspace/team/members.tsx"),
138+ route("teams", "routes/workspace/team/teams.tsx"),
139+ route("repositories", "routes/workspace/team/repositories.tsx"),
140+ route("settings", "routes/workspace/team/settings.tsx"),
141+ ]),
142+ // The workspace's own access tokens: the list, a new one, and each one's page.
143+ route("-/tokens", "routes/workspace/tokens.tsx"),
144+ route("-/tokens/new", "routes/workspace/token-new.tsx"),
145+ route("-/tokens/:id", "routes/workspace/token.tsx"),
146+ // Its rules for members' personal access tokens, and approving them.
147+ route("-/personal-access-tokens", "routes/workspace/personal-access-tokens.tsx"),
148+ route("-/usage", "routes/workspace/usage.tsx"),
149+ route("-/billing", "routes/workspace/billing.tsx"),
150+ route("-/billing/entries", "routes/workspace/statement-entries.ts"),
151+ route("-/billing/statement.csv", "routes/workspace/statement-csv.ts"),
152+ // The workspace's AI: its AI providers, managed or self-hosted, and
153+ // what Ask answers with. A base setting, not an integration.
154+ route("-/ai", "routes/workspace/ai.tsx"),
155+ // The integrations directory, and the setup pages for alerts and
156+ // trackers (lib/integration-sections).
157+ route("-/integrations", "routes/workspace/integrations-directory.tsx"),
158+ route("-/integrations/:section", "routes/workspace/integrations.tsx"),
159+ // Ask g1t, from the top bar: what it answers with, and each answer,
160+ // streamed (components/ask-composer).
161+ route("-/ask", "routes/workspace/ask.ts"),
162+ route("-/webhooks", "routes/workspace/webhooks.tsx"),
163+ // The workspace's own emoji, for chat (components/emoji).
164+ route("-/emoji", "routes/workspace/emoji.tsx"),
165+ route("-/secrets", "routes/workspace/secrets.tsx"),
166+ route("-/runners", "routes/workspace/runners.tsx"),
167+ route("-/actions", "routes/workspace/actions-settings.tsx"),
168+ route("-/packages", "routes/workspace/packages.tsx"),
169+ route("-/packages/:ecosystem/*", "routes/workspace/package.tsx"),
170+ route("-/settings", "routes/workspace/settings.tsx"),
171+ // What members may do in chat: channels, emoji, default channels.
172+ route("-/settings/chat", "routes/workspace/chat-settings.tsx"),
173+ route("-/repositories", "routes/workspace/repositories.tsx"),
174+ // Chat mode: channels by name, direct messages by id, and what the page
175+ // calls as it runs: the live socket and the JSON for sending and reading.
176+ route("-/chat/live", "routes/workspace/chat/live.ts"),
177+ route("-/chat/api", "routes/workspace/chat/api.ts"),
178+ // A person's card in Chat: their profile and teams here.
179+ route("-/chat/person/:username", "routes/workspace/chat/person.ts"),
180+ // An issue or pull request's card, over its chip in chat and g1t's answers.
181+ route("-/chat/ref", "routes/workspace/chat/ref.ts"),
182+ route("-/chat", "routes/workspace/chat/layout.tsx", [
183+ index("routes/workspace/chat/index.tsx"),
184+ route("browse", "routes/workspace/chat/browse.tsx"),
185+ route("dm/:id", "routes/workspace/chat/channel.tsx", { id: "routes/workspace/chat/dm" }),
186+ route(":channel", "routes/workspace/chat/channel.tsx"),
187+ ]),
188+ // Artifacts mode (code says "folio"): what its
189+ // pages call as they run (an artifact's live socket, JSON, comments,
190+ // uploads, export), then Home, making one, templates, the trash, the
191+ // spaces, and each artifact by its address. Addresses are flat and end
192+ // in the id, so renaming or moving one keeps links working.
193+ route("-/artifacts/live", "routes/workspace/folios/live.ts"),
194+ route("-/artifacts/api", "routes/workspace/folios/api.ts"),
195+ route("-/artifacts/threads/:folio/*", "routes/workspace/folios/threads.ts"),
196+ route("-/artifacts/upload", "routes/workspace/folios/upload.ts"),
197+ route("-/artifacts/export", "routes/workspace/folios/export.ts"),
198+ route("-/artifacts", "routes/workspace/folios/layout.tsx", [
199+ index("routes/workspace/folios/home.tsx"),
200+ route("new/:kind", "routes/workspace/folios/new.tsx"),
201+ route("templates", "routes/workspace/folios/templates.tsx"),
202+ route("trash", "routes/workspace/folios/trash.tsx"),
203+ // Artifacts possibly out of date, and a project's docs folder, read-only.
204+ route("stale", "routes/workspace/folios/stale.tsx"),
205+ route("spaces", "routes/workspace/folios/spaces.tsx"),
206+ route("spaces/new", "routes/workspace/folios/space-new.tsx"),
207+ route("spaces/:space", "routes/workspace/folios/space.tsx"),
208+ route("spaces/:space/settings", "routes/workspace/folios/space-settings.tsx"),
209+ route("repo/:repoOwner/:repoName/*", "routes/workspace/folios/repo-file.tsx"),
210+ route(":folio", "routes/workspace/folios/folio.tsx"),
211+ route(":folio/history", "routes/workspace/folios/history.tsx"),
212+ ]),
213+ // Home, the workspace's front page (`-/today` leads there), where it
214+ // marks your visit, and every app in it you can use, with pinning them
215+ // to your rail.
216+ route("-/home", "routes/workspace/home.tsx"),
217+ route("-/home/seen", "routes/workspace/home-seen.ts"),
218+ route("-/today", "routes/workspace/moved.ts", { id: "routes/workspace/moved-today" }),
219+ route("-/apps", "routes/workspace/apps.tsx"),
220+ // What Code's pages say to a member without Code.
221+ route("-/code-access", "routes/workspace/code-access.tsx"),
222+ route("-/security", "routes/workspace/security.tsx"),
223+ route("-/security/patterns", "routes/workspace/security-patterns.tsx"),
224+ route("-/security/bypass-requests", "routes/workspace/security-bypass.tsx"),
225+ route("-/security/settings", "routes/workspace/security-settings.tsx"),
226+ route("-/audit", "routes/workspace/audit.tsx"),
227+ route("-/audit/export", "routes/workspace/audit-export.ts"),
228+ route("-/guardrails", "routes/workspace/guardrails.tsx"),
229+ route("-/rules", "routes/workspace/rules.tsx"),
230+ route("-/rules/insights", "routes/workspace/rules.tsx", { id: "routes/workspace/rules-insights" }),
231+ route("-/rules/:id", "routes/workspace/ruleset.tsx"),
232+ // What the workspace will have across its projects.
233+ route("-/soon/:feature", "routes/workspace/soon.tsx"),
207234 ]),
208− // Home, the workspace's front page (`-/today` leads there), where it
209− // marks your visit, and every app in it you can use, with pinning them
210− // to your rail.
211− route("-/home", "routes/workspace/home.tsx"),
212− route("-/home/seen", "routes/workspace/home-seen.ts"),
213− route("-/today", "routes/workspace/moved.ts", { id: "routes/workspace/moved-today" }),
214− route("-/apps", "routes/workspace/apps.tsx"),
215− // What Code's pages say to a member without Code.
216− route("-/code-access", "routes/workspace/code-access.tsx"),
217− route("-/security", "routes/workspace/security.tsx"),
218− route("-/security/patterns", "routes/workspace/security-patterns.tsx"),
219− route("-/security/bypass-requests", "routes/workspace/security-bypass.tsx"),
220− route("-/security/settings", "routes/workspace/security-settings.tsx"),
221− route("-/audit", "routes/workspace/audit.tsx"),
222− route("-/audit/export", "routes/workspace/audit-export.ts"),
223− route("-/guardrails", "routes/workspace/guardrails.tsx"),
224− route("-/rules", "routes/workspace/rules.tsx"),
225− route("-/rules/insights", "routes/workspace/rules.tsx", { id: "routes/workspace/rules-insights" }),
226− route("-/rules/:id", "routes/workspace/ruleset.tsx"),
227− // What the workspace will have across its projects.
228− route("-/soon/:feature", "routes/workspace/soon.tsx"),
229235 ]),
230236 // An issue or pull request's card, over a `#12` in a commit message or on a timeline.
231237 route(":owner/:repo/ref/:number", "routes/repo/ref-card.ts"),
259265 // 1:1 project of a repository has the repository's name, so every
260266 // repository address below keeps working.
261267 route(":owner/:repo", "routes/repo/layout.tsx", [
262− index("routes/repo/overview.tsx"),
263− route("code", "routes/repo/code.tsx"),
264− route("tree/:ref/*", "routes/repo/tree.tsx"),
265− route("blob/:ref/*", "routes/repo/blob.tsx"),
266− route("archive/*", "routes/repo/archive.ts"),
267− route("commits", "routes/repo/commits.tsx"),
268− // Its tabs are paths: `branches/yours`, `branches/stale`, ….
269− route("branches/:tab?", "routes/repo/branches.tsx"),
270− route("tags", "routes/repo/tags.tsx"),
271− route("releases", "routes/repo/releases.tsx"),
272− route("releases/new", "routes/repo/release-new.tsx"),
273− route("releases/tag/*", "routes/repo/release.tsx"),
274− route("contributors", "routes/repo/contributors.tsx"),
275− route("activity", "routes/repo/activity.tsx"),
276− route("stargazers", "routes/repo/stargazers.tsx"),
277− // Forking it into a workspace, and the forks there are.
278− route("fork", "routes/repo/fork.tsx"),
279− route("forks", "routes/repo/forks.tsx"),
280− route("compare/*", "routes/repo/compare.tsx", { id: "routes/repo/compare-range" }),
281− route("compare", "routes/repo/compare.tsx"),
282− route("commit/:hash", "routes/repo/commit.tsx"),
283− // A check run reported on a commit: its report, annotations and buttons.
284− route("checks/:id", "routes/repo/check-run.tsx"),
285− route("labels", "routes/repo/labels.tsx"),
286− route("milestones", "routes/repo/milestones.tsx"),
287− route("milestones/:number", "routes/repo/milestone.tsx"),
288− route("issues", "routes/repo/issues.tsx"),
289− route("issues/new", "routes/repo/issue-new.tsx"),
290− route("issues/:number", "routes/repo/issue.tsx"),
291− route("pulls", "routes/repo/pulls.tsx"),
292− route("pulls/new", "routes/repo/pull-new.tsx"),
293− // Its tabs are paths: `pull/12/session`, `pull/12/files`.
294− route("pull/:number/:tab?", "routes/repo/pull.tsx"),
295− route("queue", "routes/repo/queue.tsx"),
296− // Who is working on what, live: claims, harness sessions, the queue, coordination events.
297− route("work", "routes/repo/work.tsx"),
298− route("work/sessions/:id", "routes/repo/work-session.tsx"),
299− route("usage", "routes/repo/usage.tsx"),
300− route("sessions", "routes/repo/sessions.tsx"),
301− route("sessions/:number", "routes/repo/session.tsx"),
302− route("actions", "routes/repo/actions.tsx"),
303− route("actions/runs/:id", "routes/repo/actions-run.tsx"),
304− route("actions/runs/:id/artifacts/:name", "routes/repo/actions-artifact.ts"),
305− route("actions/jobs/:job/log", "routes/repo/actions-log.ts"),
306− route("actions/jobs/:job/log.txt", "routes/repo/actions-job-log-download.ts"),
307− route("actions/runs/:id/logs.zip", "routes/repo/actions-logs-download.ts"),
308− route("actions/workflows/:file/badge.svg", "routes/repo/actions-badge.ts"),
309− route("deployments", "routes/repo/deployments.tsx"),
310− route("deployments/:id", "routes/repo/deployment.tsx"),
311− // Observability: what the project's running software did (services/telemetry).
312− route("observability", "routes/repo/observability.tsx"),
313− route("observability/requests", "routes/repo/observability-requests.tsx"),
314− route("observability/requests/:id", "routes/repo/observability-request.tsx"),
315− route("observability/logs", "routes/repo/observability-logs.tsx"),
316− route("observability/traces", "routes/repo/observability-traces.tsx"),
317− route("observability/traces/:trace", "routes/repo/observability-trace.tsx"),
318− route("observability/errors", "routes/repo/observability-errors.tsx"),
319− route("observability/metrics", "routes/repo/observability-metrics.tsx"),
320− route("observability/settings", "routes/repo/observability-settings.tsx"),
321− // The live tail's socket, forwarded to the project's store.
322− route("observability/live", "routes/repo/observability-live.ts"),
323− // Security: an overview, then a page for each part. Old links to
324− // `security?tab=…` are sent on by the overview.
325− route("security", "routes/repo/security-overview.tsx"),
326− route("security/secret-scanning", "routes/repo/security-secrets.tsx"),
327− route("security/secret-scanning/patterns", "routes/repo/security-patterns.tsx"),
328− route("security/secret-scanning/:id", "routes/repo/security-secret.tsx"),
329− route("security/code-scanning", "routes/repo/security-code.tsx"),
330− route("security/code-scanning/setup", "routes/repo/security-code-setup.ts"),
331− route("security/code-scanning/:number", "routes/repo/security-code-alert.tsx"),
332− route("security/vulnerabilities", "routes/repo/security.tsx"),
333− route("security/dependency-graph", "routes/repo/security-graph.tsx"),
334− route("security/dependency-graph/sbom.json", "routes/repo/security-sbom.ts"),
335− route("security/dependency-updates", "routes/repo/security-updates.tsx"),
336− route("security/pulls/:number", "routes/repo/security-pull.tsx"),
337− route("security/settings", "routes/repo/security-settings.tsx"),
338− // Reporting: the policy's pull request, advisories, and private vulnerability reports.
339− route("security/policy/setup", "routes/repo/security-policy-setup.ts"),
340− route("security/advisories", "routes/repo/security-advisories.tsx"),
341− route("security/advisories/new", "routes/repo/security-advisory-edit.tsx", { id: "routes/repo/security-advisory-new" }),
342− route("security/advisories/:id", "routes/repo/security-advisory.tsx"),
343− route("security/advisories/:id/edit", "routes/repo/security-advisory-edit.tsx"),
344− route("security/reports", "routes/repo/security-reports.tsx"),
345− route("security/report", "routes/repo/security-report.tsx"),
346− route("settings", "routes/repo/settings.tsx"),
347− route("settings/repository", "routes/repo/settings-repository.tsx"),
348− route("settings/access", "routes/repo/settings-access.tsx"),
349− route("settings/keys", "routes/repo/settings-deploy-keys.tsx"),
350− route("settings/branches", "routes/repo/settings-branches.tsx"),
351− route("settings/rules", "routes/repo/settings-rules.tsx"),
352− route("settings/rules/insights", "routes/repo/settings-rules.tsx", { id: "routes/repo/settings-rules-insights" }),
353− route("settings/rules/:id", "routes/repo/settings-ruleset.tsx"),
354− route("settings/webhooks", "routes/repo/webhooks.tsx"),
355− route("settings/secrets", "routes/repo/secrets.tsx"),
356− route("settings/actions", "routes/repo/settings-actions.tsx"),
357− route("settings/environments", "routes/repo/settings-environments.tsx"),
358− route("settings/runners", "routes/repo/settings-runners.tsx"),
359− route("settings/deployments", "routes/repo/settings-deployments.tsx"),
360− route("settings/domains", "routes/repo/settings-domains.tsx"),
361− route("settings/guardrails", "routes/repo/settings-guardrails.tsx"),
362− // What the project will have: one page for each Soon in its menu.
363− route("soon/:feature", "routes/repo/soon.tsx"),
268+ // Its pages fail inside its frame (routes/page-boundary.tsx).
269+ layout("routes/page-boundary.tsx", { id: "routes/page-boundary/repo" }, [
270+ index("routes/repo/overview.tsx"),
271+ route("code", "routes/repo/code.tsx"),
272+ route("tree/:ref/*", "routes/repo/tree.tsx"),
273+ route("blob/:ref/*", "routes/repo/blob.tsx"),
274+ route("archive/*", "routes/repo/archive.ts"),
275+ route("commits", "routes/repo/commits.tsx"),
276+ // Its tabs are paths: `branches/yours`, `branches/stale`, ….
277+ route("branches/:tab?", "routes/repo/branches.tsx"),
278+ route("tags", "routes/repo/tags.tsx"),
279+ route("releases", "routes/repo/releases.tsx"),
280+ route("releases/new", "routes/repo/release-new.tsx"),
281+ route("releases/tag/*", "routes/repo/release.tsx"),
282+ route("contributors", "routes/repo/contributors.tsx"),
283+ route("activity", "routes/repo/activity.tsx"),
284+ route("stargazers", "routes/repo/stargazers.tsx"),
285+ // Forking it into a workspace, and the forks there are.
286+ route("fork", "routes/repo/fork.tsx"),
287+ route("forks", "routes/repo/forks.tsx"),
288+ route("compare/*", "routes/repo/compare.tsx", { id: "routes/repo/compare-range" }),
289+ route("compare", "routes/repo/compare.tsx"),
290+ route("commit/:hash", "routes/repo/commit.tsx"),
291+ // A check run reported on a commit: its report, annotations and buttons.
292+ route("checks/:id", "routes/repo/check-run.tsx"),
293+ route("labels", "routes/repo/labels.tsx"),
294+ route("milestones", "routes/repo/milestones.tsx"),
295+ route("milestones/:number", "routes/repo/milestone.tsx"),
296+ route("issues", "routes/repo/issues.tsx"),
297+ route("issues/new", "routes/repo/issue-new.tsx"),
298+ route("issues/:number", "routes/repo/issue.tsx"),
299+ route("pulls", "routes/repo/pulls.tsx"),
300+ route("pulls/new", "routes/repo/pull-new.tsx"),
301+ // Its tabs are paths: `pull/12/session`, `pull/12/files`.
302+ route("pull/:number/:tab?", "routes/repo/pull.tsx"),
303+ route("queue", "routes/repo/queue.tsx"),
304+ // Who is working on what, live: claims, harness sessions, the queue, coordination events.
305+ route("work", "routes/repo/work.tsx"),
306+ route("work/sessions/:id", "routes/repo/work-session.tsx"),
307+ route("usage", "routes/repo/usage.tsx"),
308+ route("sessions", "routes/repo/sessions.tsx"),
309+ route("sessions/:number", "routes/repo/session.tsx"),
310+ route("actions", "routes/repo/actions.tsx"),
311+ route("actions/runs/:id", "routes/repo/actions-run.tsx"),
312+ route("actions/runs/:id/artifacts/:name", "routes/repo/actions-artifact.ts"),
313+ route("actions/jobs/:job/log", "routes/repo/actions-log.ts"),
314+ route("actions/jobs/:job/log.txt", "routes/repo/actions-job-log-download.ts"),
315+ route("actions/runs/:id/logs.zip", "routes/repo/actions-logs-download.ts"),
316+ route("actions/workflows/:file/badge.svg", "routes/repo/actions-badge.ts"),
317+ route("deployments", "routes/repo/deployments.tsx"),
318+ route("deployments/:id", "routes/repo/deployment.tsx"),
319+ // Observability: what the project's running software did (services/telemetry).
320+ route("observability", "routes/repo/observability.tsx"),
321+ route("observability/requests", "routes/repo/observability-requests.tsx"),
322+ route("observability/requests/:id", "routes/repo/observability-request.tsx"),
323+ route("observability/logs", "routes/repo/observability-logs.tsx"),
324+ route("observability/traces", "routes/repo/observability-traces.tsx"),
325+ route("observability/traces/:trace", "routes/repo/observability-trace.tsx"),
326+ route("observability/errors", "routes/repo/observability-errors.tsx"),
327+ route("observability/metrics", "routes/repo/observability-metrics.tsx"),
328+ route("observability/settings", "routes/repo/observability-settings.tsx"),
329+ // The live tail's socket, forwarded to the project's store.
330+ route("observability/live", "routes/repo/observability-live.ts"),
331+ // Security: an overview, then a page for each part. Old links to
332+ // `security?tab=…` are sent on by the overview.
333+ route("security", "routes/repo/security-overview.tsx"),
334+ route("security/secret-scanning", "routes/repo/security-secrets.tsx"),
335+ route("security/secret-scanning/patterns", "routes/repo/security-patterns.tsx"),
336+ route("security/secret-scanning/:id", "routes/repo/security-secret.tsx"),
337+ route("security/code-scanning", "routes/repo/security-code.tsx"),
338+ route("security/code-scanning/setup", "routes/repo/security-code-setup.ts"),
339+ route("security/code-scanning/:number", "routes/repo/security-code-alert.tsx"),
340+ route("security/vulnerabilities", "routes/repo/security.tsx"),
341+ route("security/dependency-graph", "routes/repo/security-graph.tsx"),
342+ route("security/dependency-graph/sbom.json", "routes/repo/security-sbom.ts"),
343+ route("security/dependency-updates", "routes/repo/security-updates.tsx"),
344+ route("security/pulls/:number", "routes/repo/security-pull.tsx"),
345+ route("security/settings", "routes/repo/security-settings.tsx"),
346+ // Reporting: the policy's pull request, advisories, and private vulnerability reports.
347+ route("security/policy/setup", "routes/repo/security-policy-setup.ts"),
348+ route("security/advisories", "routes/repo/security-advisories.tsx"),
349+ route("security/advisories/new", "routes/repo/security-advisory-edit.tsx", { id: "routes/repo/security-advisory-new" }),
350+ route("security/advisories/:id", "routes/repo/security-advisory.tsx"),
351+ route("security/advisories/:id/edit", "routes/repo/security-advisory-edit.tsx"),
352+ route("security/reports", "routes/repo/security-reports.tsx"),
353+ route("security/report", "routes/repo/security-report.tsx"),
354+ route("settings", "routes/repo/settings.tsx"),
355+ route("settings/repository", "routes/repo/settings-repository.tsx"),
356+ route("settings/access", "routes/repo/settings-access.tsx"),
357+ route("settings/keys", "routes/repo/settings-deploy-keys.tsx"),
358+ route("settings/branches", "routes/repo/settings-branches.tsx"),
359+ route("settings/rules", "routes/repo/settings-rules.tsx"),
360+ route("settings/rules/insights", "routes/repo/settings-rules.tsx", { id: "routes/repo/settings-rules-insights" }),
361+ route("settings/rules/:id", "routes/repo/settings-ruleset.tsx"),
362+ route("settings/webhooks", "routes/repo/webhooks.tsx"),
363+ route("settings/secrets", "routes/repo/secrets.tsx"),
364+ route("settings/actions", "routes/repo/settings-actions.tsx"),
365+ route("settings/environments", "routes/repo/settings-environments.tsx"),
366+ route("settings/runners", "routes/repo/settings-runners.tsx"),
367+ route("settings/deployments", "routes/repo/settings-deployments.tsx"),
368+ route("settings/domains", "routes/repo/settings-domains.tsx"),
369+ route("settings/guardrails", "routes/repo/settings-guardrails.tsx"),
370+ // What the project will have: one page for each Soon in its menu.
371+ route("soon/:feature", "routes/repo/soon.tsx"),
372+ ]),
364373 ]),
365374 // Anything else: a 404 that still knows who is signed in.
366375 route("*", "routes/not-found.tsx"),
+19−0
1+import { Outlet } from "react-router";
2+
3+import type { Route } from "./+types/page-boundary";
4+import { RouteError } from "../components/route-error";
5+
6+/**
7+ * Around the pages of a repository, a workspace and your settings
8+ * (routes.ts): a page that fails shows its error where it would have been,
9+ * inside its frame, so the header, tabs and sidebar its layout drew stay
10+ * and the rest of g1t is a click away. Without it the error would replace
11+ * the layout too, which a layout's own boundary does.
12+ */
13+export default function PageBoundary() {
14+ return <Outlet />;
15+}
16+
17+export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
18+ return <RouteError error={error} contained />;
19+}

Comments

Sign in to comment.