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.
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| 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 | import { pagePath, underWorkspace, workspacePage, workspaceRedirect } from "./workspace-nav.ts"; |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 5 | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 6 | test("a path is one of the workspace's pages, or none", () => { |
| 7 | assert.equal(workspacePage("/acme", "acme"), "overview"); | |
| 8 | assert.equal(workspacePage("/Acme/", "acme"), "overview"); | |
| 9 | assert.equal(workspacePage("/acme.data", "acme"), "overview"); | |
| 10 | assert.equal(workspacePage("/acme/-/projects", "acme"), "projects"); | |
| 11 | assert.equal(workspacePage("/acme/-/people", "acme"), "people"); | |
| 12 | assert.equal(workspacePage("/acme/-/packages", "acme"), "packages"); | |
| 13 | assert.equal(workspacePage("/acme/-/teams", "acme"), "teams"); | |
| 14 | assert.equal(workspacePage("/acme/-/insights", "acme"), "insights"); | |
| 15 | // One package or team has a page of its own; settings and the rest have headings. | |
| 16 | assert.equal(workspacePage("/acme/-/packages/npm/web", "acme"), null); | |
| 17 | assert.equal(workspacePage("/acme/-/teams/web", "acme"), null); | |
| 18 | assert.equal(workspacePage("/acme/-/settings", "acme"), null); | |
| 19 | assert.equal(workspacePage("/acme/-/agents", "acme"), null); | |
| 20 | assert.equal(workspacePage("/acme/web", "acme"), null); | |
| 21 | assert.equal(workspacePage("/other", "acme"), null); | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 22 | }); |
| 23 | ||
| 24 | test("old addresses go to where their pages are now", () => { | |
| People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how. | 25 | // Members and invites is a page of People's again. |
| 26 | assert.equal(workspaceRedirect("/acme/-/members"), null); | |
| The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how. | 27 | assert.equal(workspaceRedirect("/acme/-/today", "?x=1"), "/acme/-/home?x=1"); |
| 28 | // Home is the front page itself: not an old address. | |
| 29 | assert.equal(workspaceRedirect("/acme/-/home"), null); | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 30 | // Code's Overview lives at -/overview now: not an old address. |
| 31 | assert.equal(workspaceRedirect("/acme/-/overview"), null); | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 32 | assert.equal(workspaceRedirect("/acme/-/soon/teams"), "/acme/-/teams"); |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 33 | // The Marketplace, Customize and Agents are gone: their old addresses lead nowhere. |
| 34 | assert.equal(workspaceRedirect("/acme/-/marketplace/agents"), null); | |
| 35 | assert.equal(workspaceRedirect("/acme/-/marketplace/extensions"), null); | |
| 36 | assert.equal(workspaceRedirect("/acme/-/agents/skills"), null); | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 37 | assert.equal(workspaceRedirect("/acme/-/soon/insights", "?x=1"), "/acme/-/insights?x=1"); |
| Merge branch 'rm-boards' | 38 | assert.equal(workspaceRedirect("/acme/-/soon/uptime"), null); |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 39 | assert.equal(workspaceRedirect("/acme/-/people"), null); |
| 40 | assert.equal(workspaceRedirect("/acme/web"), null); | |
| 41 | }); | |
| 42 | ||
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 43 | test("?tab= opens that page, keeping the rest of the query", () => { |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 44 | assert.equal(workspaceRedirect("/acme", "?tab=repositories"), "/acme/-/projects"); |
| 45 | assert.equal(workspaceRedirect("/acme", "?tab=projects&q=api"), "/acme/-/projects?q=api"); | |
| People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how. | 46 | assert.equal(workspaceRedirect("/acme", "?tab=members"), "/acme/-/members"); |
| 47 | assert.equal(workspaceRedirect("/acme", "?tab=people"), "/acme/-/people"); | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 48 | assert.equal(workspaceRedirect("/acme", "?tab=overview"), "/acme"); |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 49 | assert.equal(workspaceRedirect("/acme", "?tab=settings"), "/acme/-/settings"); |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 50 | assert.equal(workspaceRedirect("/acme", "?tab=nonsense"), null); |
| 51 | assert.equal(workspaceRedirect("/acme", ""), null); | |
| 52 | }); | |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 53 | |
| 54 | test("a click's data request is for the same page as a full load", () => { | |
| 55 | assert.equal(pagePath("/acme/-/insights.data"), "/acme/-/insights"); | |
| 56 | assert.equal(pagePath("/acme/-/insights/"), "/acme/-/insights"); | |
| The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how. | 57 | assert.equal(workspaceRedirect("/acme/-/today.data", "?_routes=routes%2Fworkspace%2Fmoved-today"), "/acme/-/home"); |
| 58 | assert.equal(workspaceRedirect("/acme/-/today.data", "?_routes=x&q=ada"), "/acme/-/home?q=ada"); | |
| Billing: credits with a kind and expiry, discounts instead of comped, and safer charging | 59 | }); |
| Merge branch 'worktree-agent-a8385d293d42c913a' | 60 | |
| 61 | test("an alias or old name leads to the same page under the workspace", () => { | |
| 62 | assert.equal(underWorkspace("/g1t", "", "flagon-io"), "/flagon-io"); | |
| 63 | assert.equal(underWorkspace("/g1t/g1t/issues", "?q=is%3Aopen", "flagon-io"), "/flagon-io/g1t/issues?q=is%3Aopen"); | |
| 64 | assert.equal(underWorkspace("/g1t/g1t/blob/main/README.md", "", "flagon-io"), "/flagon-io/g1t/blob/main/README.md"); | |
| 65 | assert.equal(underWorkspace("/g1t/-/people/", "", "flagon-io"), "/flagon-io/-/people"); | |
| 66 | }); | |
| 67 | ||
| 68 | test("a click's data request for an alias leads to the page, not its data", () => { | |
| 69 | assert.equal(underWorkspace("/g1t.data", "?_routes=routes%2Fworkspace%2Flayout", "flagon-io"), "/flagon-io"); | |
| 70 | assert.equal( | |
| 71 | underWorkspace("/g1t/g1t/pulls.data", "?_routes=routes%2Frepo%2Flayout&state=closed", "flagon-io"), | |
| 72 | "/flagon-io/g1t/pulls?state=closed", | |
| 73 | ); | |
| 74 | }); | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 75 | |
| The left-hand navigation takes less room and reads at a glance. The dock becomes a rail: a 56-pixel column of icons with the g1t mark on top, the current app marked by a filled square, hover hints naming each with its shortcut, the apps you pinned in a scrolling band, and Apps, People, Workspace and your account at the foot. Each mode's sidebar sits flush against it, 240 pixels wide, opening on Search or jump to and listing its groups under small labels in 36-pixel rows; Ctrl B hides it and the page grows into the room. Every mode's list is built on the shadcn Sidebar now in the kit, so Workspace, Code, People, Notifications, account settings, Chat, Artifacts and Agents all share one frame; the phone keeps its bottom bar and drawer. The workspaces guide describes the rail, the sidebar and the page. | 76 | test("the rail's mode follows the address", async () => { |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 77 | const { modeOf, modeHome, homePath } = await import("./workspace-nav.ts"); |
| The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how. | 78 | assert.equal(modeOf("/", "acme"), "home"); |
| 79 | assert.equal(modeOf("/acme", "acme"), "home"); | |
| 80 | assert.equal(modeOf("/acme/-/home", "acme"), "home"); | |
| Chat controls, public profiles, shadcn selects, and no Docs tab in a project | 81 | // g1t's own public pages light no mode. |
| 82 | assert.equal(modeOf("/explore", "acme"), "site"); | |
| 83 | assert.equal(modeOf("/u/ada", "acme"), "site"); | |
| 84 | assert.equal(modeOf("/search.data", "acme"), "site"); | |
| 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. | 85 | assert.equal(modeOf("/notifications", "acme"), "notifications"); |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 86 | assert.equal(modeOf("/settings/emails", "acme"), "account"); |
| Chat and workspace agents: channels, DMs and named agents you talk to | 87 | assert.equal(modeOf("/acme/-/chat/general", "acme"), "chat"); |
| 88 | assert.equal(modeOf("/acme/-/chat/dm/c1.data", "acme"), "chat"); | |
| Memory is gone: people's own harnesses keep their own memory (CLAUDE.md, AGENTS.md, auto-memory), so g1t's project and workspace notes that agents recalled and remembered only duplicated them. The recall and remember operations, their /repos/{owner}/{name}/memory routes, the memory MCP tool and its lines in the MCP instructions, the Memory section of the API reference and OpenAPI document (and the CLI's table generated from it) are removed, as are the work service's memory methods, the Memory pages of a project and of a workspace with their sidebar links, Home's Decisions card (which read decisions out of workspace memory), the Memory guide (its address and the old agent-memory, agents-and-memory and context-hub ones now lead to Connect a coding agent) and every mention in the docs, README, llms.txt, the landing page, the privacy policy and the Ask bench. Decision records, g1t why and g1t work stay as they were. memory:read and memory:write are retired as agents:run and the models scopes were: tokens and OAuth grants that hold them still parse and keep working for their other scopes, but no preset (the Agent preset and the OAuth default included), token form, mobile sign-in or hosted session hands them out and nothing needs them; looking up a ticket (search ticket, get_context), which needed memory:read, needs issues:read. The memories table stays in work's D1 untouched and is no longer read or written, except that purging a repository or deleting a workspace still deletes its old rows. Checked: cargo test for g1t-contracts (308), g1t-api (201), g1t (102 and 41), g1t-work (103) and g1t-integrations (65), cargo check of the workspace, the OpenAPI document and ops table regenerated, every workspace's typecheck, the site's 901 tests and a production build, the contracts' 48 tests, the phone app's 11, the deploy tool's 92, and the docs' tests and build. | 89 | // Agents mode and Memory are gone: their old addresses fall to Code's not-found page. |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 90 | assert.equal(modeOf("/acme/-/agents", "acme"), "code"); |
| 91 | assert.equal(modeOf("/acme/-/memory", "acme"), "code"); | |
| Artifacts is a mode in the rail, the shell and the phone's tab bar in place of Docs: its home lists everything you can open by day or as cards under All, Yours and Shared with you, with search, filters and tiles to start a doc (slides, designs and dashboards say they are coming soon), and its sidebar holds favorites, spaces, Private, Shared, projects' docs and the trash. | 92 | assert.equal(modeOf("/acme/-/artifacts", "acme"), "artifacts"); |
| 93 | assert.equal(modeOf("/acme/-/artifacts/spaces/general", "acme"), "artifacts"); | |
| 94 | // Docs mode is gone: its old addresses are nobody's mode, so they fall to Code's not-found page. | |
| 95 | assert.equal(modeOf("/acme/-/docs", "acme"), "code"); | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 96 | assert.equal(modeOf("/acme/-/overview", "acme"), "code"); |
| 97 | assert.equal(modeOf("/acme/-/projects", "acme"), "code"); | |
| 98 | assert.equal(modeOf("/acme/-/security", "acme"), "code"); | |
| 99 | assert.equal(modeOf("/acme/-/security/settings", "acme"), "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. | 100 | assert.equal(modeOf("/acme/-/people", "acme"), "people"); |
| 101 | assert.equal(modeOf("/acme/-/teams/web", "acme"), "people"); | |
| People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how. | 102 | assert.equal(modeOf("/acme/-/people/ana", "acme"), "people"); |
| Workspace is the workspace's settings, in one place. Its sidebar is grouped, General, Access, Money, Compute, Code, Agents, Chat, Artifacts, Security and Integrations, every page one click away with no settings inside settings, the groups folding and the owner-only pages hidden from members; what is not here yet is marked Soon with a hint. Members is a list with search and filters for role, two-factor and team, and Invite opens a dialog: who, by username, name or email, their role and a note that goes into the invitation; nothing is filled in inline any more. Invitations is its own page with All, Pending, Accepted, Declined, Expired and Revoked filters that say how many, and each row's menu can copy the link, send it again or revoke it; identity learned to send an invitation again and to carry the note. Permissions gathers every rule about who may do what, by part: Code's base permission and member privileges, who creates teams, who creates channels, and, marked Soon with what applies today, forking private repositories, adding agents to conversations, messaging agents directly, creating spaces and default sharing, creating workspace agents and raising budgets, deploying to production and publishing packages. General lost what moved. The permissions guide is new, and the workspaces, people and teams, access, chat, teams, authentication and billing guides say where things are now. | 103 | // Members, invitations and permissions are the workspace's settings. |
| 104 | assert.equal(modeOf("/acme/-/members", "acme"), "workspace"); | |
| 105 | assert.equal(modeOf("/acme/-/members/invitations", "acme"), "workspace"); | |
| 106 | assert.equal(modeOf("/acme/-/permissions", "acme"), "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. | 107 | assert.equal(modeOf("/acme/-/apps", "acme"), "apps"); |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 108 | assert.equal(modeOf("/acme/-/billing", "acme"), "workspace"); |
| 109 | assert.equal(modeOf("/acme/-/tokens", "acme"), "workspace"); | |
| 110 | assert.equal(modeOf("/acme/-/workspace", "acme"), "workspace"); | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 111 | assert.equal(modeOf("/acme/web/pulls", "acme"), "code"); |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 112 | assert.equal(modeOf("/new", "acme"), "code"); |
| 113 | // Another workspace's chat is not this one's mode; its repositories are Code's. | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 114 | assert.equal(modeOf("/other/-/chat", "acme"), "code"); |
| The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how. | 115 | assert.equal(modeHome("home", "acme"), "/acme/-/home"); |
| 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. | 116 | assert.equal(modeHome("notifications", "acme"), "/notifications"); |
| 117 | assert.equal(modeHome("people", "acme"), "/acme/-/people"); | |
| 118 | assert.equal(modeHome("apps", "acme"), "/acme/-/apps"); | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 119 | assert.equal(modeHome("code", "acme"), "/acme/-/overview"); |
| 120 | assert.equal(modeHome("workspace", "acme"), "/acme/-/workspace"); | |
| The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how. | 121 | assert.equal(homePath("acme"), "/acme/-/home"); |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 122 | assert.equal(homePath("acme", "?tab=landed&_routes=x"), "/acme/-/overview?tab=landed"); |
| Chat and workspace agents: channels, DMs and named agents you talk to | 123 | }); |
| 124 | ||
| Workspace is the workspace's settings, in one place. Its sidebar is grouped, General, Access, Money, Compute, Code, Agents, Chat, Artifacts, Security and Integrations, every page one click away with no settings inside settings, the groups folding and the owner-only pages hidden from members; what is not here yet is marked Soon with a hint. Members is a list with search and filters for role, two-factor and team, and Invite opens a dialog: who, by username, name or email, their role and a note that goes into the invitation; nothing is filled in inline any more. Invitations is its own page with All, Pending, Accepted, Declined, Expired and Revoked filters that say how many, and each row's menu can copy the link, send it again or revoke it; identity learned to send an invitation again and to carry the note. Permissions gathers every rule about who may do what, by part: Code's base permission and member privileges, who creates teams, who creates channels, and, marked Soon with what applies today, forking private repositories, adding agents to conversations, messaging agents directly, creating spaces and default sharing, creating workspace agents and raising budgets, deploying to production and publishing packages. General lost what moved. The permissions guide is new, and the workspaces, people and teams, access, chat, teams, authentication and billing guides say where things are now. | 125 | test("Workspace mode's sidebar knows which group and page an address is", async () => { |
| 126 | const { WORKSPACE_SETTINGS_GROUPS, modeOf, workspaceSettingsPage } = await import("./workspace-nav.ts"); | |
| 127 | assert.deepEqual(workspaceSettingsPage("/acme/-/settings", "acme"), { group: "general", page: "settings" }); | |
| 128 | assert.deepEqual(workspaceSettingsPage("/acme/-/settings/chat", "acme"), { group: "chat", page: "chat-settings" }); | |
| 129 | assert.deepEqual(workspaceSettingsPage("/acme/-/members", "acme"), { group: "access", page: "members" }); | |
| 130 | assert.deepEqual(workspaceSettingsPage("/acme/-/members/invitations.data", "acme"), { group: "access", page: "invitations" }); | |
| 131 | assert.deepEqual(workspaceSettingsPage("/acme/-/permissions", "acme"), { group: "access", page: "permissions" }); | |
| 132 | assert.deepEqual(workspaceSettingsPage("/acme/-/rules/rs_1", "acme"), { group: "security", page: "rules" }); | |
| 133 | assert.deepEqual(workspaceSettingsPage("/acme/-/security/settings", "acme"), { group: "security", page: "security-settings" }); | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 134 | assert.deepEqual(workspaceSettingsPage("/acme/-/usage", "acme"), { group: "money", page: "usage" }); |
| Workspace is the workspace's settings, in one place. Its sidebar is grouped, General, Access, Money, Compute, Code, Agents, Chat, Artifacts, Security and Integrations, every page one click away with no settings inside settings, the groups folding and the owner-only pages hidden from members; what is not here yet is marked Soon with a hint. Members is a list with search and filters for role, two-factor and team, and Invite opens a dialog: who, by username, name or email, their role and a note that goes into the invitation; nothing is filled in inline any more. Invitations is its own page with All, Pending, Accepted, Declined, Expired and Revoked filters that say how many, and each row's menu can copy the link, send it again or revoke it; identity learned to send an invitation again and to carry the note. Permissions gathers every rule about who may do what, by part: Code's base permission and member privileges, who creates teams, who creates channels, and, marked Soon with what applies today, forking private repositories, adding agents to conversations, messaging agents directly, creating spaces and default sharing, creating workspace agents and raising budgets, deploying to production and publishing packages. General lost what moved. The permissions guide is new, and the workspaces, people and teams, access, chat, teams, authentication and billing guides say where things are now. | 135 | // The overview, Code's security page and another workspace are no settings page. |
| 136 | assert.equal(workspaceSettingsPage("/acme/-/workspace", "acme"), null); | |
| 137 | assert.equal(workspaceSettingsPage("/acme/-/security", "acme"), null); | |
| 138 | assert.equal(workspaceSettingsPage("/other/-/settings", "acme"), null); | |
| 139 | // Every real page is in Workspace mode, and every key is unique. | |
| 140 | const keys = new Set<string>(); | |
| 141 | for (const group of WORKSPACE_SETTINGS_GROUPS) { | |
| 142 | for (const page of group.pages) { | |
| 143 | assert.ok(!keys.has(page.key), `${page.key} twice`); | |
| 144 | keys.add(page.key); | |
| Workspace settings promise only what exists: the Soon rows Deployments, Project defaults, Spaces and sharing and Applications are gone from Workspace mode's sidebar, with the Artifacts group they left empty, and the sidebar no longer draws Soon rows at all. None had a plan behind it. Permissions loses its five locked Soon settings (forking private repositories, who can create spaces, default sharing, who can deploy to production, who can publish packages), so every setting on it saves. The permissions and workspaces guides say the same. Checked: the site's typecheck, its tests and a production build, the deploy tool's tests and the docs build. | 145 | assert.equal(modeOf(`/acme/-/${page.path}`, "acme"), "workspace", page.path); |
| Workspace is the workspace's settings, in one place. Its sidebar is grouped, General, Access, Money, Compute, Code, Agents, Chat, Artifacts, Security and Integrations, every page one click away with no settings inside settings, the groups folding and the owner-only pages hidden from members; what is not here yet is marked Soon with a hint. Members is a list with search and filters for role, two-factor and team, and Invite opens a dialog: who, by username, name or email, their role and a note that goes into the invitation; nothing is filled in inline any more. Invitations is its own page with All, Pending, Accepted, Declined, Expired and Revoked filters that say how many, and each row's menu can copy the link, send it again or revoke it; identity learned to send an invitation again and to carry the note. Permissions gathers every rule about who may do what, by part: Code's base permission and member privileges, who creates teams, who creates channels, and, marked Soon with what applies today, forking private repositories, adding agents to conversations, messaging agents directly, creating spaces and default sharing, creating workspace agents and raising budgets, deploying to production and publishing packages. General lost what moved. The permissions guide is new, and the workspaces, people and teams, access, chat, teams, authentication and billing guides say where things are now. | 146 | } |
| 147 | } | |
| 148 | }); | |
| 149 | ||
| Chat and workspace agents: channels, DMs and named agents you talk to | 150 | test("a member without Code access is sent around Code's pages", async () => { |
| 151 | const { codeGate } = await import("./workspace-nav.ts"); | |
| 152 | const none = ["acme"]; | |
| The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how. | 153 | assert.equal(codeGate("/", "", none, "acme"), "/acme/-/home"); |
| Chat and workspace agents: channels, DMs and named agents you talk to | 154 | assert.equal(codeGate("/", "", none, "other"), null); |
| The front page is Home: where you're needed comes first, however long ago it started, with what's at stake and how long it has waited; then what happened since you were last here, in words, with the agent work that settled, what landed, what deployed, what was decided and what was spent, switchable to the last 24 hours or 7 days; then what's running now. Your last visit is kept with your account, so it's the same on every device; Today's old address leads here. The home guide says how. | 155 | assert.equal(codeGate("/acme", "", none, "acme"), "/acme/-/home"); |
| Chat and workspace agents: channels, DMs and named agents you talk to | 156 | assert.equal(codeGate("/acme/web/pull/3", "?x=1", none, "acme"), "/acme/-/code-access?from=%2Facme%2Fweb%2Fpull%2F3%3Fx%3D1"); |
| 157 | assert.equal(codeGate("/acme/-/projects", "", none, "acme"), "/acme/-/code-access?from=%2Facme%2F-%2Fprojects"); | |
| 158 | assert.equal(codeGate("/acme/-/chat/general", "", none, "acme"), null); | |
| 159 | assert.equal(codeGate("/acme/-/code-access", "", none, "acme"), null); | |
| 160 | assert.equal(codeGate("/other/web", "", none, "acme"), null); | |
| 161 | assert.equal(codeGate("/acme/web", "", [], "acme"), null); | |
| 162 | }); | |
| Merge branch 'worktree-agent-aefe9db9851cfafd8' | 163 | |
| 164 | test("g1t beyond your workspaces is browsed, and whose it is", async () => { | |
| 165 | const { browsingOf, shellMode } = await import("./workspace-nav.ts"); | |
| 166 | const viewer = { slug: "acme", memberships: ["acme", "Side-Co"], shared: [{ namespace: "friend", name: "notes" }], username: "ada" }; | |
| 167 | // Explore and search are nobody's. | |
| 168 | assert.deepEqual(browsingOf("/explore", viewer), { owner: null }); | |
| 169 | assert.deepEqual(browsingOf("/explore.data", viewer), { owner: null }); | |
| 170 | assert.deepEqual(browsingOf("/search", viewer), { owner: null }); | |
| 171 | // Someone's profile is theirs; your own is no one's being browsed. | |
| 172 | assert.deepEqual(browsingOf("/u/grace", viewer), { owner: { kind: "person", name: "grace" } }); | |
| 173 | assert.deepEqual(browsingOf("/u/Ada", viewer), { owner: null }); | |
| 174 | // Another workspace, its pages and its projects. | |
| 175 | assert.deepEqual(browsingOf("/other", viewer), { owner: { kind: "workspace", name: "other", repo: null } }); | |
| 176 | assert.deepEqual(browsingOf("/other/-/projects", viewer), { owner: { kind: "workspace", name: "other", repo: null } }); | |
| 177 | assert.deepEqual(browsingOf("/other/site/pulls/3", viewer), { owner: { kind: "workspace", name: "other", repo: "site" } }); | |
| 178 | // Your workspaces are never browsed, the one you are in or another (the shell switches to it). | |
| 179 | assert.equal(browsingOf("/acme/web", viewer), null); | |
| 180 | assert.equal(browsingOf("/acme/-/projects", viewer), null); | |
| 181 | assert.equal(browsingOf("/side-co/api", viewer), null); | |
| 182 | // A project shared with you is Code's; the rest of its workspace is browsed. | |
| 183 | assert.equal(browsingOf("/friend/notes/issues", viewer), null); | |
| 184 | assert.deepEqual(browsingOf("/friend/other", viewer), { owner: { kind: "workspace", name: "friend", repo: "other" } }); | |
| 185 | // g1t's own pages and files are no one's. | |
| 186 | for (const path of ["/", "/settings/emails", "/notifications", "/new", "/policies/terms", "/workspaces/new", "/invitations", "/robots.txt", "/.well-known/g1t.json", "/u"]) { | |
| 187 | assert.equal(browsingOf(path, viewer), null, path); | |
| 188 | } | |
| 189 | // Without a workspace there is no rail, and nothing is browsed. | |
| 190 | assert.equal(browsingOf("/explore", { slug: null, memberships: [] }), null); | |
| 191 | // The shell's mode: browse beyond your workspaces, the address's mode otherwise. | |
| 192 | assert.equal(shellMode("/explore", viewer), "browse"); | |
| 193 | assert.equal(shellMode("/u/grace", viewer), "browse"); | |
| 194 | assert.equal(shellMode("/other/site", viewer), "browse"); | |
| 195 | assert.equal(shellMode("/acme/web", viewer), "code"); | |
| 196 | assert.equal(shellMode("/friend/notes", viewer), "code"); | |
| 197 | assert.equal(shellMode("/acme/-/chat", viewer), "chat"); | |
| 198 | assert.equal(shellMode("/policies", viewer), "site"); | |
| 199 | }); |
This file's history is long; its oldest lines are credited to the oldest commit read.