Skip to content
250 linesCodeBlameRaw

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.

g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.1/**
The Marketplace is where a workspace finds what to add, reached from Apps: the agent catalog of roles with who already does each job and Add to workspace; every integration with whether it's connected; g1t's own extensions, listed as Soon; and requests, where members ask and owners add or turn down, each told by a notification. Underneath, an extension is a manifest (publisher and tier, source and version from a tag, scopes, the domains its data goes to, its page on g1tusercontent.com, no price) and an install pins its version with an off switch, a budget and the audit log. Apps lists only what was added from the Marketplace, with pins; g1t's own pages stay in their sidebars, and AI Gateway joins Workspace's. The marketplace guide says how.2 * The dock's built-in apps, the apps a workspace installed from the
3 * Marketplace, and the ones each person pins to their dock.
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.4 *
5 * Built-in apps (Today, Chat, Notifications, Agents, Code, Artifacts,
The Marketplace is where a workspace finds what to add, reached from Apps: the agent catalog of roles with who already does each job and Add to workspace; every integration with whether it's connected; g1t's own extensions, listed as Soon; and requests, where members ask and owners add or turn down, each told by a notification. Underneath, an extension is a manifest (publisher and tier, source and version from a tag, scopes, the domains its data goes to, its page on g1tusercontent.com, no price) and an install pins its version with an off switch, a budget and the audit log. Apps lists only what was added from the Marketplace, with pins; g1t's own pages stay in their sidebars, and AI Gateway joins Workspace's. The marketplace guide says how.6 * People and Workspace) are always in the dock; their own pages (Projects,
7 * Usage, Teams and the rest) are in their sidebars. Apps are what the
8 * workspace added from the Marketplace: today, the integrations it
9 * connected, each opening its page. Each person pins the ones they want.
10 *
11 * An installed app's key says what it is: `int-<connector>` for an
12 * integration (@g1t/contracts/connectors), and later `ext-<extension>`.
13 * A key that names nothing installable (a built-in page pinned before
14 * Apps were the Marketplace's) is dropped wherever pins are read.
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.15 *
The apps you pin to your dock are kept with your account, per workspace and in your order, so the dock is the same on every device: identity keeps them in dock_pins and answers dock_pins and set_dock_pins, the dock reads them with the rest of the page, pins kept only on this device carry over with your first change, this device's copy still draws the dock when identity can't be reached, a pin that can't be saved says so, and they go when you or the workspace do; the workspaces guide says how.16 * Pins are each person's own, per workspace, in the order they set, and
17 * kept with their account by the identity service (`dock_pins`), so the
18 * dock is the same on every device (lib/dock.server.ts). A cookie
19 * (`g1t_dock`) keeps a copy on each device: the dock still draws from it
20 * when identity cannot be reached, and pins from before they were kept
21 * with the account carry over from it until the first change.
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.22 * No Workers or React imports, so it can be tested under Node.
23 */
The Marketplace is where a workspace finds what to add, reached from Apps: the agent catalog of roles with who already does each job and Add to workspace; every integration with whether it's connected; g1t's own extensions, listed as Soon; and requests, where members ask and owners add or turn down, each told by a notification. Underneath, an extension is a manifest (publisher and tier, source and version from a tag, scopes, the domains its data goes to, its page on g1tusercontent.com, no price) and an install pins its version with an off switch, a budget and the audit log. Apps lists only what was added from the Marketplace, with pins; g1t's own pages stay in their sidebars, and AI Gateway joins Workspace's. The marketplace guide says how.24import type { ExtensionInstall } from "@g1t/contracts";
25import { CONNECTORS, type Connector, connectorPath } from "@g1t/contracts/connectors";
26import { extensionById } from "@g1t/contracts/marketplace";
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.27
28/** The apps that are always in the dock. */
29export type BuiltinApp = "today" | "chat" | "notifications" | "agents" | "code" | "artifacts" | "people" | "workspace";
30
The Marketplace is where a workspace finds what to add, reached from Apps: the agent catalog of roles with who already does each job and Add to workspace; every integration with whether it's connected; g1t's own extensions, listed as Soon; and requests, where members ask and owners add or turn down, each told by a notification. Underneath, an extension is a manifest (publisher and tier, source and version from a tag, scopes, the domains its data goes to, its page on g1tusercontent.com, no price) and an install pins its version with an off switch, a budget and the audit log. Apps lists only what was added from the Marketplace, with pins; g1t's own pages stay in their sidebars, and AI Gateway joins Workspace's. The marketplace guide says how.31export type BuiltinInfo = {
32 key: BuiltinApp;
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.33 name: string;
34 /** Its page in the workspace `slug`. */
35 path: (slug: string) => string;
36 /** Part of Code: only for members with Code access. */
37 code?: boolean;
38};
39
40const under = (page: string) => (slug: string) => `/${slug}/-/${page}`;
41
The Marketplace is where a workspace finds what to add, reached from Apps: the agent catalog of roles with who already does each job and Add to workspace; every integration with whether it's connected; g1t's own extensions, listed as Soon; and requests, where members ask and owners add or turn down, each told by a notification. Underneath, an extension is a manifest (publisher and tier, source and version from a tag, scopes, the domains its data goes to, its page on g1tusercontent.com, no price) and an install pins its version with an off switch, a budget and the audit log. Apps lists only what was added from the Marketplace, with pins; g1t's own pages stay in their sidebars, and AI Gateway joins Workspace's. The marketplace guide says how.42/** The dock's built-in apps, in its order. */
43export const BUILTINS: BuiltinInfo[] = [
44 { key: "today", name: "Today", path: under("today") },
45 { key: "chat", name: "Chat", path: under("chat") },
46 { key: "notifications", name: "Notifications", path: () => "/notifications" },
47 { key: "agents", name: "Agents", path: under("agents") },
48 { key: "code", name: "Code", path: under("overview"), code: true },
49 { key: "artifacts", name: "Artifacts", path: under("artifacts") },
50 { key: "people", name: "People", path: under("people") },
51 { key: "workspace", name: "Workspace", path: under("workspace") },
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.52];
53
The Marketplace is where a workspace finds what to add, reached from Apps: the agent catalog of roles with who already does each job and Add to workspace; every integration with whether it's connected; g1t's own extensions, listed as Soon; and requests, where members ask and owners add or turn down, each told by a notification. Underneath, an extension is a manifest (publisher and tier, source and version from a tag, scopes, the domains its data goes to, its page on g1tusercontent.com, no price) and an install pins its version with an off switch, a budget and the audit log. Apps lists only what was added from the Marketplace, with pins; g1t's own pages stay in their sidebars, and AI Gateway joins Workspace's. The marketplace guide says how.54/** The built-in app `key`. */
55export function appOf(key: BuiltinApp): BuiltinInfo {
56 return BUILTINS.find((app) => app.key === key)!;
57}
58
59/** An installed app's key, as pins and forms carry it: `int-sentry`. */
60export type PinnableApp = string;
61
62/** An app installed from the Marketplace. */
63export type InstalledApp = {
64 key: PinnableApp;
65 name: string;
66 /** What it is, in a few words. */
67 about: string;
68 /** The Marketplace listing it was installed from: `integration:sentry`. */
69 listing: string;
70 /** The connector behind it, for its mark. */
71 connector: Pick<Connector, "id" | "name" | "provider">;
72 /** Its page in the workspace `slug`. */
73 path: (slug: string) => string;
74 /**
75 * Whether the person can open it. Every member can open an integration's
76 * page; an app limited to some people shows Request access to the rest.
77 */
78 usable: boolean;
79};
80
81/** Where an integration is looked after: its setup page, by kind, or its own page. */
82const SECTION_BY_CATEGORY: Partial<Record<Connector["category"], string>> = { ai: "models", monitoring: "alerts", issues: "trackers" };
83
84function integrationPath(connector: Connector): (slug: string) => string {
85 const section = connector.provider ? SECTION_BY_CATEGORY[connector.category] : undefined;
86 if (section) return under(`integrations/${section}`);
87 const href = connector.href?.workspace;
88 return href ? (slug) => connectorPath(href, slug) : under("integrations");
89}
90
91/** Identity keeps keys of at most this many characters (MAX_DOCK_APP_KEY). */
92const MAX_KEY = 32;
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.93
The Marketplace is where a workspace finds what to add, reached from Apps: the agent catalog of roles with who already does each job and Add to workspace; every integration with whether it's connected; g1t's own extensions, listed as Soon; and requests, where members ask and owners add or turn down, each told by a notification. Underneath, an extension is a manifest (publisher and tier, source and version from a tag, scopes, the domains its data goes to, its page on g1tusercontent.com, no price) and an install pins its version with an off switch, a budget and the audit log. Apps lists only what was added from the Marketplace, with pins; g1t's own pages stay in their sidebars, and AI Gateway joins Workspace's. The marketplace guide says how.94/** The installed-app key for an integration: `int-sentry`. */
95export function integrationAppKey(connectorId: string): PinnableApp {
96 return `int-${connectorId}`;
97}
98
99/**
100 * The app a key names, from the catalog alone (no service is asked), or
101 * null when it names nothing that can be installed: a retired key, a
102 * built-in page, or a connector that is not available for a workspace.
103 */
104export function installedAppOf(key: string): InstalledApp | null {
105 if (key.length > MAX_KEY) return null;
106 if (/^ext-[a-z][a-z0-9-]*$/.test(key)) {
107 const extension = extensionById(key.slice(4));
108 if (!extension || extension.status !== "available") return null;
109 return {
110 key,
111 name: extension.name,
112 about: extension.tagline,
113 listing: `extension:${extension.id}`,
114 connector: { id: extension.id, name: extension.name, provider: undefined },
115 // Its own page, in a sandboxed frame, is the extension host's to serve; until then, its listing.
116 path: (slug) => `/${slug}/-/marketplace/extensions/${extension.id}`,
117 usable: true,
118 };
119 }
120 if (!/^int-[a-z0-9][a-z0-9-]*$/.test(key)) return null;
121 const connector = CONNECTORS.find((c) => c.id === key.slice(4));
122 if (!connector || connector.status !== "available" || !connector.scopes.includes("workspace")) return null;
123 return {
124 key,
125 name: connector.name,
126 about: connector.description,
127 listing: `integration:${connector.id}`,
128 connector: { id: connector.id, name: connector.name, provider: connector.provider },
129 path: integrationPath(connector),
130 usable: true,
131 };
132}
133
134/** Whether `key` names an app that can be pinned. */
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.135export function isPinnable(key: string): key is PinnableApp {
The Marketplace is where a workspace finds what to add, reached from Apps: the agent catalog of roles with who already does each job and Add to workspace; every integration with whether it's connected; g1t's own extensions, listed as Soon; and requests, where members ask and owners add or turn down, each told by a notification. Underneath, an extension is a manifest (publisher and tier, source and version from a tag, scopes, the domains its data goes to, its page on g1tusercontent.com, no price) and an install pins its version with an off switch, a budget and the audit log. Apps lists only what was added from the Marketplace, with pins; g1t's own pages stay in their sidebars, and AI Gateway joins Workspace's. The marketplace guide says how.136 return installedAppOf(key) != null;
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.137}
138
The Marketplace is where a workspace finds what to add, reached from Apps: the agent catalog of roles with who already does each job and Add to workspace; every integration with whether it's connected; g1t's own extensions, listed as Soon; and requests, where members ask and owners add or turn down, each told by a notification. Underneath, an extension is a manifest (publisher and tier, source and version from a tag, scopes, the domains its data goes to, its page on g1tusercontent.com, no price) and an install pins its version with an off switch, a budget and the audit log. Apps lists only what was added from the Marketplace, with pins; g1t's own pages stay in their sidebars, and AI Gateway joins Workspace's. The marketplace guide says how.139/**
140 * The apps the workspace installed, from what it has connected (by
141 * connector id, lib/connected.server.ts), in catalog order. `manage`, when
142 * a connection has one, is where its app opens.
143 */
144export function installedApps(
145 connected: Record<string, { manage: string | null }>,
146 installs: readonly Pick<ExtensionInstall, "listing" | "enabled">[] = [],
147): InstalledApp[] {
148 const integrations = CONNECTORS.filter((connector) => connected[connector.id]).map((connector) => {
149 const app = installedAppOf(integrationAppKey(connector.id));
150 const manage = connected[connector.id]?.manage;
151 return app && manage ? { ...app, path: () => manage } : app;
152 });
153 // Extensions switched off (the kill switch) aren't apps until they're on again.
154 const extensions = installs.filter((install) => install.enabled).map((install) => installedAppOf(`ext-${install.listing.replace(/^extension:/, "")}`));
155 return [...extensions, ...integrations].filter((app): app is InstalledApp => app != null);
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.156}
157
The Marketplace is where a workspace finds what to add, reached from Apps: the agent catalog of roles with who already does each job and Add to workspace; every integration with whether it's connected; g1t's own extensions, listed as Soon; and requests, where members ask and owners add or turn down, each told by a notification. Underneath, an extension is a manifest (publisher and tier, source and version from a tag, scopes, the domains its data goes to, its page on g1tusercontent.com, no price) and an install pins its version with an off switch, a budget and the audit log. Apps lists only what was added from the Marketplace, with pins; g1t's own pages stay in their sidebars, and AI Gateway joins Workspace's. The marketplace guide says how.158/** An app as a loader sends it: everything but its path, which is worked out for the workspace. */
159export type InstalledAppData = Omit<InstalledApp, "path"> & { href: string };
160
161export function appData(app: InstalledApp, slug: string): InstalledAppData {
162 const { path, ...rest } = app;
163 return { ...rest, href: path(slug) };
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.164}
165
The apps you pin to your dock are kept with your account, per workspace and in your order, so the dock is the same on every device: identity keeps them in dock_pins and answers dock_pins and set_dock_pins, the dock reads them with the rest of the page, pins kept only on this device carry over with your first change, this device's copy still draws the dock when identity can't be reached, a pin that can't be saved says so, and they go when you or the workspace do; the workspaces guide says how.166/** The cookie that keeps this device's copy of each person's pins, per workspace. */
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.167export const DOCK_COOKIE = "g1t_dock";
168
The apps you pin to your dock are kept with your account, per workspace and in your order, so the dock is the same on every device: identity keeps them in dock_pins and answers dock_pins and set_dock_pins, the dock reads them with the rest of the page, pins kept only on this device carry over with your first change, this device's copy still draws the dock when identity can't be reached, a pin that can't be saved says so, and they go when you or the workspace do; the workspaces guide says how.169/**
170 * Most workspaces the cookie remembers pins for, and most pins in each.
171 * Identity keeps up to 24 (`MAX_DOCK_PINS`); this stays under it.
172 */
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.173const MAX_WORKSPACES = 20;
174export const MAX_APP_PINS = 12;
175
The apps you pin to your dock are kept with your account, per workspace and in your order, so the dock is the same on every device: identity keeps them in dock_pins and answers dock_pins and set_dock_pins, the dock reads them with the rest of the page, pins kept only on this device carry over with your first change, this device's copy still draws the dock when identity can't be reached, a pin that can't be saved says so, and they go when you or the workspace do; the workspaces guide says how.176/**
177 * Pins as identity keeps them, read against the apps there are: keys that
178 * are not an app people pin (one since retired, say) are dropped, repeats
179 * too, in the order they were set.
180 */
181export function pinsFrom(keys: readonly string[]): PinnableApp[] {
182 return [...new Set(keys.filter(isPinnable))].slice(0, MAX_APP_PINS);
183}
184
185/**
186 * The pins to draw in the workspace `slug`: the account's when identity
187 * has them (`stored`), else this device's cookie, for pins never saved to
188 * the account or when identity could not be asked.
189 */
190export function pinsToShow(stored: readonly string[] | null | undefined, cookie: string | null | undefined, slug: string): PinnableApp[] {
191 return stored ? pinsFrom(stored) : pinsIn(cookie, slug);
192}
193
g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.194/** Every workspace's pins, from the cookie's value: `acme:projects.usage,beta:teams`. */
195export function readDock(value: string | null | undefined): Record<string, PinnableApp[]> {
196 const dock: Record<string, PinnableApp[]> = {};
197 for (const entry of (value ?? "").split(",")) {
198 const [slug, keys] = entry.split(":");
199 if (!slug || !/^[a-z0-9-]+$/.test(slug) || keys === undefined) continue;
200 const pins = [...new Set(keys.split(".").filter(isPinnable))].slice(0, MAX_APP_PINS);
201 dock[slug] = pins;
202 }
203 return dock;
204}
205
206/** The pins in the workspace `slug`, in the order they were pinned. */
207export function pinsIn(value: string | null | undefined, slug: string): PinnableApp[] {
208 return readDock(value)[slug.toLowerCase()] ?? [];
209}
210
211/** The cookie's value with the workspace `slug`'s pins set to `pins`: the latest workspace first. */
212export function writeDock(value: string | null | undefined, slug: string, pins: PinnableApp[]): string {
213 const dock = readDock(value);
214 const key = slug.toLowerCase();
215 delete dock[key];
216 const entries = [[key, [...new Set(pins.filter(isPinnable))].slice(0, MAX_APP_PINS)] as const, ...Object.entries(dock)].slice(0, MAX_WORKSPACES);
217 return entries.map(([name, keys]) => `${name}:${keys.join(".")}`).join(",");
218}
219
220/** One change to someone's pins, as the pin buttons post it; null when it asks for nothing that makes sense. */
221export function appPinFromForm(form: FormData): { app: PinnableApp; pinned: boolean } | null {
222 const app = String(form.get("app") ?? "");
223 const intent = form.get("intent");
224 if (!isPinnable(app) || (intent !== "pin" && intent !== "unpin")) return null;
225 return { app, pinned: intent === "pin" };
226}
227
228/** The pins after pinning or unpinning `app`: a new pin goes at the end. */
229export function withPin(pins: PinnableApp[], app: PinnableApp, pinned: boolean): PinnableApp[] {
230 const rest = pins.filter((key) => key !== app);
231 return pinned ? [...rest, app] : rest;
232}
233
234/** The Set-Cookie header that keeps the dock, for a year. */
235export function dockCookie(value: string, secure: boolean): string {
236 return `${DOCK_COOKIE}=${encodeURIComponent(value)}; Path=/; Max-Age=31536000; SameSite=Lax${secure ? "; Secure" : ""}`;
237}
238
239/** The cookie that remembers whether the in-context sidebar is folded away on a computer. */
240export const SIDEBAR_COOKIE = "g1t_sidebar";
241
242/** Whether the sidebar is folded away, from the cookie's value. */
243export function sidebarClosed(value: string | null | undefined): boolean {
244 return value === "closed";
245}
246
247/** The cookie, as the page sets it when the sidebar is folded away or brought back. */
248export function sidebarCookie(closed: boolean, secure: boolean): string {
249 return `${SIDEBAR_COOKIE}=${closed ? "closed" : "open"}; Path=/; Max-Age=31536000; SameSite=Lax${secure ? "; Secure" : ""}`;
250}