flagon-io/g1t

public

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

g1t/apps/web/app/lib/project-nav.ts

96 lines2,964 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

A flat project menu with Code first, views as tabs, and Stripe for every owner1/**
2 * A project's pages and the tabs within them, in one place: the sidebar
3 * lists the pages, and a page with more than one view shows them as tabs
4 * across its top, the way GitHub and Vercel do. Soon tabs come from the
5 * roadmap and open its page for them, under the same tabs.
6 */
7import { type RoadmapItem, roadmapIn } from "./roadmap";
8
9export type Tab = {
10 label: string;
11 /** Under the project, without a leading slash; "" is its overview. */
12 path: string;
13 /** Other paths under which this tab is the current one. */
14 also?: string[];
15 soon?: boolean;
16 about?: string;
17 /** Members only. */
18 members?: boolean;
19};
20
21export type Section = {
22 key: RoadmapItem["section"] | "Workflows" | "Settings";
23 tabs: Tab[];
24};
25
26function soon(section: RoadmapItem["section"]): Tab[] {
27 return roadmapIn(section).map((item) => ({ label: item.title, path: `soon/${item.key}`, soon: true, about: item.summary }));
28}
29
30export const SECTIONS: Section[] = [
31 {
32 key: "Code",
33 tabs: [
34 { label: "Files", path: "code", also: ["tree", "blob"] },
35 { label: "Commits", path: "commits", also: ["commit"] },
36 ...soon("Code"),
37 ],
38 },
39 {
40 key: "Issues",
41 tabs: [
42 { label: "Issues", path: "issues" },
43 { label: "Outcomes", path: "plans", members: true },
44 ...soon("Issues"),
45 ],
46 },
47 {
48 key: "Agents",
Agents and memory, checks and conflicts, profiles, slug renames, custom domains49 tabs: [
50 { label: "At work", path: "agents" },
51 { label: "Sessions", path: "sessions" },
52 { label: "Memory", path: "memory", members: true },
53 ...soon("Agents"),
54 ],
A flat project menu with Code first, views as tabs, and Stripe for every owner55 },
56 {
57 key: "Deployments",
58 tabs: [{ label: "Deployments", path: "deployments", members: true }, ...soon("Deployments")],
59 },
Observability gets its own page; tabs never overflow60 { key: "Observability", tabs: soon("Observability") },
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API61 {
62 key: "Security",
63 tabs: [{ label: "Overview", path: "security", members: true }, ...soon("Security")],
64 },
A flat project menu with Code first, views as tabs, and Stripe for every owner65 { key: "Insights", tabs: soon("Insights") },
66];
67
68/** Pull requests and the merge queue share a page's tabs too. */
69export const PULLS: Section = {
70 key: "Code",
71 tabs: [
72 { label: "Pull requests", path: "pulls", also: ["pull"] },
73 { label: "Merge queue", path: "queue" },
74 ],
75};
76
77function matches(rest: string, tab: Tab): boolean {
78 return [tab.path, ...(tab.also ?? [])].some((path) => path !== "" && (rest === path || rest.startsWith(`${path}/`)));
79}
80
81/**
82 * The tabs for the page at `rest` (the path under the project), if it
83 * belongs to a page with more than one view.
84 */
85export function tabsFor(rest: string, member: boolean): Tab[] | null {
86 for (const section of [...SECTIONS, PULLS]) {
87 const visible = section.tabs.filter((tab) => member || !tab.members);
88 if (visible.length > 1 && visible.some((tab) => matches(rest, tab))) return visible;
89 }
90 return null;
91}
92
93/** The first page of a section, for its sidebar link: a built one if any. */
94export function entryOf(key: Section["key"]): Tab | undefined {
95 return SECTIONS.find((section) => section.key === key)?.tabs[0];
96}