Skip to content

Compare changes

Choose two branches to see what one has that the other does not, then open a pull request for it.

Open a pull request

3 commits

182 files+214−1040/182 viewed
+1−1
99 - **Pixels:** each is its own square, 0.8 of its cell, centred, with square
1010 corners.
1111 - **Colour:** G and T are `#ededef` (`--g1t-fg`) and the 1 is lavender
12− `#b6a8ff` (`--g1t-merged`), on the very dark gray base `#0f0f11`
12+ `#b6a8ff` (`--g1t-accent`, the brand accent), on the very dark gray base `#0f0f11`
1313 (`--g1t-bg`). On light backgrounds the letters are `#161618` and the 1 is
1414 `#6b56e8`. Printed in one ink, everything is that ink.
1515 - **Icon:** the 1 alone, centred in a square, for favicons, app icons and
+2−1
4747 (
4848 "Workspaces",
4949 "A workspace owns repositories and is the first part of their address. People and agents work in workspaces.",
50− &[Op::CreateWorkspace, Op::UpdateWorkspace, Op::DeleteWorkspace],
50+ &[Op::GetWorkspace, Op::CreateWorkspace, Op::UpdateWorkspace, Op::DeleteWorkspace],
5151 ),
5252 (
5353 "Invites",
360360 fn title(op: Op) -> &'static str {
361361 match op {
362362 Op::Whoami => "Get the current user",
363+ Op::GetWorkspace => "Get a workspace",
363364 Op::CreateWorkspace => "Create a workspace",
364365 Op::DeleteWorkspace => "Delete a workspace",
365366 Op::UpdateWorkspace => "Update a workspace",
+53−6
1616 use g1t_contracts::repos::{CreateArgs, GetArgs, ListArgs as ListReposArgs, Repo, RepoPath};
1717 use g1t_contracts::teams::{
1818 CreateTeamArgs, DeleteTeamArgs, ListTeamsArgs, RemoveTeamMemberArgs, RemoveTeamRepoArgs, ReviewAlgorithm,
19− ReviewAssignment, SetTeamMemberArgs, SetTeamRepoArgs, Team, TeamArgs, TeamRole, TeamVisibility, UpdateTeamArgs,
19+ ReviewAssignment, SetTeamCreationArgs, SetTeamMemberArgs, SetTeamRepoArgs, Team, TeamArgs, TeamCreation, TeamRole,
20+ TeamVisibility, UpdateTeamArgs,
2021 UserTeamsArgs,
2122 };
2223 use g1t_contracts::security::{
8788 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
8889 pub enum Op {
8990 Whoami,
91+ GetWorkspace,
9092 CreateWorkspace,
9193 DeleteWorkspace,
9294 UpdateWorkspace,
623625 }
624626
625627 impl Op {
626− pub const ALL: [Op; 204] = [
628+ pub const ALL: [Op; 205] = [
627629 Op::Whoami,
630+ Op::GetWorkspace,
628631 Op::CreateWorkspace,
629632 Op::DeleteWorkspace,
630633 Op::UpdateWorkspace,
838841 pub fn name(self) -> &'static str {
839842 match self {
840843 Op::Whoami => "whoami",
844+ Op::GetWorkspace => "get_workspace",
841845 Op::CreateWorkspace => "create_workspace",
842846 Op::DeleteWorkspace => "delete_workspace",
843847 Op::UpdateWorkspace => "update_workspace",
10531057 Op::DeleteWorkspace => {
10541058 "Delete a workspace and everything in it. Owners only, signed in as a person, and confirm must be the workspace's slug. Billing must be able to settle it: no unpaid invoice, no prepaid credit left, and no usage this month still being metered; what it owes is charged to its card at once and its plan ends. Its repositories, projects and apps go with it at once, nobody can reach it, and its access tokens stop working. It is kept for 30 days, when g1t's support can restore it as it was; then it is purged, with its webhooks, integrations and workspace secrets. Its statements, invoices and audit log are kept. The slug is never given to another workspace; the person whose username it is may create it again once it is purged. Some workspaces, such as Flagon's, can never be deleted."
10551059 }
1060+ Op::GetWorkspace => {
1061+ "One workspace you belong to: its name, description and member count, what every member gets on each of its repositories (base_permission), and who may create its teams (team_creation: members or owners). Members only."
1062+ }
10561063 Op::UpdateWorkspace => {
1057− "Change a workspace's display name and description, and what every member gets on each of its repositories (base_permission: none, read, write or admin). Only the fields given are changed; give at least one. An empty name falls back to the slug, which this never changes (that is a rename, on Settings); an empty description clears it. Owners only, signed in as a person. Returns the workspace as it is now."
1064+ "Change a workspace's display name and description, what every member gets on each of its repositories (base_permission: none, read, write or admin), and who may create its teams (team_creation: members or owners). Only the fields given are changed; give at least one. An empty name falls back to the slug, which this never changes (that is a rename, on Settings); an empty description clears it. Owners only, signed in as a person. Returns the workspace as it is now."
10581065 }
10591066 Op::ListRepos => "Repositories you can see, optionally filtered by a search query.",
10601067 Op::GetRepo => "One repository's details.",
14361443 "One team, by its slug, as list_teams describes it. A secret team is found only by its own people and the workspace's owners; anyone else is told it does not exist. Members of the workspace only."
14371444 }
14381445 Op::CreateTeam => {
1439− "Create a team in a workspace. Any member may create one, and becomes its first maintainer; `members` adds more people by username, each a member of the workspace. `slug` is made from the name unless you give one: lowercase letters, digits and single hyphens. `visibility` is `visible` (the default: every member sees it) or `secret` (only its people and the owners). A team under a `parent` inherits the parent's roles on repositories, and a mention or review request for the parent reaches it too; giving it a parent needs an owner, or a maintainer of the parent. Secret teams cannot be nested. People only, signed in or with a personal access token. Returns the team."
1446+ "Create a team in a workspace. Any member may create one, unless the workspace's `team_creation` is `owners` (then only owners may: see update_workspace), and becomes its first maintainer; `members` adds more people by username, each a member of the workspace. `slug` is made from the name unless you give one: lowercase letters, digits and single hyphens. `visibility` is `visible` (the default: every member sees it) or `secret` (only its people and the owners). A team under a `parent` inherits the parent's roles on repositories, and a mention or review request for the parent reaches it too; giving it a parent needs an owner, or a maintainer of the parent. Secret teams cannot be nested. People only, signed in or with a personal access token. Returns the team."
14401447 }
14411448 Op::UpdateTeam => {
14421449 "Change a team's `name`, `slug`, `description`, `visibility`, `parent` (an empty string takes it out from under its parent), `notify` or `review_assignment`. Only the fields given change; give at least one. A new slug changes how it is mentioned, @workspace/slug. Owners of the workspace and the team's maintainers. People only. Returns the team as it is now."
15121519 let states = json!({ "type": "string", "enum": ["open", "closed"] });
15131520 match self {
15141521 Op::Whoami => object(json!({}), &[]),
1522+ Op::GetWorkspace => object(json!({ "workspace": workspace_schema() }), &["workspace"]),
15151523 Op::CreateWorkspace => object(
15161524 json!({
15171525 "slug": {
16211629 "enum": g1t_contracts::access::BasePermission::ALL.map(|base| base.as_str()),
16221630 "description": "What every member gets on each repository: none, read, write or admin. Needs the access:admin scope as well.",
16231631 },
1632+ "team_creation": {
1633+ "type": "string",
1634+ "enum": g1t_contracts::teams::TeamCreation::ALL.map(|setting| setting.as_str()),
1635+ "description": "Who may create the workspace's teams: members (any member, the default) or owners (owners only).",
1636+ },
16241637 }),
16251638 &["workspace"],
16261639 ),
28342847 !matches!(
28352848 self,
28362849 Op::Whoami
2850+ | Op::GetWorkspace
28372851 | Op::CreateWorkspace
28382852 | Op::DeleteWorkspace
28392853 | Op::UpdateWorkspace
30803094
30813095 match self {
30823096 Op::Whoami => ok(&actor()),
3097+ Op::GetWorkspace => {
3098+ // Its settings are its members' business.
3099+ if actor().role_in(&workspace()).is_none() {
3100+ return failed(FailureCode::NotFound, "Workspace not found.");
3101+ }
3102+ match g1t_kit::call::<_, Option<Workspace>>(identity, "get_workspace", &json!({ "slug": workspace() })).await? {
3103+ Some(found) => ok(&found),
3104+ None => failed(FailureCode::NotFound, "Workspace not found."),
3105+ }
3106+ }
30833107 Op::CreateWorkspace => {
30843108 pass(
30853109 identity,
31893213 None => return failed(FailureCode::Invalid, "base_permission is none, read, write or admin."),
31903214 },
31913215 };
3216+ let creation = match input.get("team_creation").filter(|value| !value.is_null()) {
3217+ None => None,
3218+ Some(value) => match value.as_str().and_then(TeamCreation::parse) {
3219+ Some(setting) => Some(setting),
3220+ None => return failed(FailureCode::Invalid, "team_creation is members or owners."),
3221+ },
3222+ };
31923223 let (name, description) = (optional_text(input, "name"), optional_text(input, "description"));
3193− if base.is_none() && name.is_none() && description.is_none() {
3194− return failed(FailureCode::Invalid, "Give name, description or base_permission to change.");
3224+ if base.is_none() && creation.is_none() && name.is_none() && description.is_none() {
3225+ return failed(FailureCode::Invalid, "Give name, description, base_permission or team_creation to change.");
31953226 }
31963227 let found = || async {
31973228 g1t_kit::call::<_, Option<Workspace>>(identity, "get_workspace", &json!({ "slug": workspace() })).await
32323263 return Ok(Outcome::Fail(failure));
32333264 }
32343265 }
3266+ if let Some(setting) = creation {
3267+ let set: Outcome<TeamCreation> = call(
3268+ identity,
3269+ "set_team_creation",
3270+ &SetTeamCreationArgs {
3271+ actor: actor(),
3272+ slug: workspace(),
3273+ team_creation: setting,
3274+ surface: Some(services.audit.surface),
3275+ },
3276+ )
3277+ .await?;
3278+ if let Outcome::Fail(failure) = set {
3279+ return Ok(Outcome::Fail(failure));
3280+ }
3281+ }
32353282 match found().await? {
32363283 Some(workspace) => ok(&workspace),
32373284 None => failed(FailureCode::NotFound, "Workspace not found."),
+25−6
6666 "description": null,
6767 "created_at": "2026-10-04T16:02:51.337Z",
6868 "member_count": 1,
69− "base_permission": "write"
69+ "base_permission": "write",
70+ "team_creation": "members"
7071 },
71− "notes": "`base_permission` is what every member gets on each of its repositories: `write` until an owner changes it with `PATCH /workspaces/{workspace}` or `PUT /workspaces/{workspace}/base_permission`. See [Access and roles](/guides/access-and-roles/)."
72+ "notes": "`base_permission` is what every member gets on each of its repositories: `write` until an owner changes it with `PATCH /workspaces/{workspace}` or `PUT /workspaces/{workspace}/base_permission`. See [Access and roles](/guides/access-and-roles/). `team_creation` is who may create its teams: `members` (any member) until an owner sets `owners` with `PATCH /workspaces/{workspace}`."
7273 },
74+ "get_workspace": {
75+ "params": {
76+ "workspace": "acme-labs"
77+ },
78+ "response": {
79+ "id": "wsp_01m43teqa9em6bje0bhvdj4jkb",
80+ "slug": "acme-labs",
81+ "name": "Acme Labs",
82+ "description": "Rockets, and the software that flies them.",
83+ "created_at": "2026-10-04T16:02:51.337Z",
84+ "member_count": 3,
85+ "base_permission": "write",
86+ "team_creation": "members"
87+ },
88+ "notes": "`team_creation` is who may create its teams: `members` (any member, the default) or `owners`. Change it, and the rest, with [`PATCH /workspaces/{workspace}`](/reference/api/workspaces/update-workspace/). `404` for anyone who is not a member. See [Workspaces](/guides/workspaces/)."
89+ },
7390 "update_workspace": {
7491 "params": {
7592 "workspace": "acme-labs"
7693 },
7794 "request": {
7895 "name": "Acme Labs",
79− "description": "Rockets, and the software that flies them."
96+ "description": "Rockets, and the software that flies them.",
97+ "team_creation": "owners"
8098 },
8199 "response": {
82100 "id": "wsp_01m43teqa9em6bje0bhvdj4jkb",
85103 "description": "Rockets, and the software that flies them.",
86104 "created_at": "2026-10-04T16:02:51.337Z",
87105 "member_count": 3,
88− "base_permission": "write"
106+ "base_permission": "write",
107+ "team_creation": "owners"
89108 },
90− "notes": "Only the fields given change. `name` is the display name; the slug, the first part of the workspace's addresses, stays as it is. `base_permission` needs the `access:admin` scope as well as `workspace:admin`; [`set_base_permission`](/reference/api/access/set-base-permission/) sets it alone. Refused with `403` for anyone but an owner signed in as a person. Recorded in the [audit log](/guides/audit-log/). See [Workspaces](/guides/workspaces/)."
109+ "notes": "Only the fields given change. `name` is the display name; the slug, the first part of the workspace's addresses, stays as it is. `base_permission` needs the `access:admin` scope as well as `workspace:admin`; [`set_base_permission`](/reference/api/access/set-base-permission/) sets it alone. `team_creation` is `members` (any member may create a team, the default) or `owners`; teams already made stay. Refused with `403` for anyone but an owner signed in as a person. Recorded in the [audit log](/guides/audit-log/). See [Workspaces](/guides/workspaces/)."
91110 },
92111 "delete_workspace": {
93112 "request": {
44514470 "created_at": "2026-10-06T15:02:11.480Z",
44524471 "updated_at": "2026-10-06T15:02:11.480Z"
44534472 },
4454− "notes": "You become the team's maintainer. `slug` is made from `name` when left out (`Web & Mobile` becomes `web-mobile`), and `409` says one is taken. A workspace has at most 500 teams, nested at most 8 deep; `name` is at most 80 characters and `description` 280. A `secret` team cannot have a parent or child teams. Refused with `403` for an agent's or a workspace's token. See [Teams](/guides/teams/)."
4473+ "notes": "You become the team's maintainer. Refused with `403` for a member when the workspace's `team_creation` is `owners`. `slug` is made from `name` when left out (`Web & Mobile` becomes `web-mobile`), and `409` says one is taken. A workspace has at most 500 teams, nested at most 8 deep; `name` is at most 80 characters and `description` 280. A `secret` team cannot have a parent or child teams. Refused with `403` for an agent's or a workspace's token. See [Teams](/guides/teams/)."
44554474 },
44564475 "get_team": {
44574476 "params": {
+1−1
105105 sent["codeOwners"] = code_owners.clone();
106106 }
107107 match op {
108− Op::CreateWorkspace | Op::UpdateWorkspace => through::<g1t_contracts::identity::Workspace>(op, sent),
108+ Op::GetWorkspace | Op::CreateWorkspace | Op::UpdateWorkspace => through::<g1t_contracts::identity::Workspace>(op, sent),
109109 Op::ListRepos => through::<Vec<repos::Repo>>(op, sent),
110110 Op::Search => through::<search::SearchResults>(op, sent),
111111 Op::GetRepo
+1−0
3131 pub const ROUTES: &[Route] = &[
3232 route("GET", "/user", Op::Whoami, &[]),
3333 route("POST", "/workspaces", Op::CreateWorkspace, &[]),
34+ route("GET", "/workspaces/:workspace", Op::GetWorkspace, &[]),
3435 route("DELETE", "/workspaces/:workspace", Op::DeleteWorkspace, &[]),
3536 route("GET", "/user/emails", Op::ListEmails, &[]),
3637 route("POST", "/user/emails", Op::AddEmail, &[]),
+2−1
270270 description: "Workspaces own repositories (g1t.sh/{workspace}/{repo}): create, update or delete one, invite members, connect integrations and model providers, and keep your own pinned projects at the top of its sidebar.",
271271 default_action: None,
272272 actions: &[
273+ a("get", Op::GetWorkspace, "A workspace's details and settings"),
273274 a("create", Op::CreateWorkspace, "Create a workspace"),
274275 a("delete", Op::DeleteWorkspace, "Delete a workspace and everything in it (support can restore it for 30 days)"),
275− a("update", Op::UpdateWorkspace, "Change its name, description or base permission"),
276+ a("update", Op::UpdateWorkspace, "Change its name, description, base permission or who may create teams"),
276277 a("list_invites", Op::ListWorkspaceInvites, "Its invites"),
277278 a("invite_member", Op::InviteMember, "Invite an email address"),
278279 a("revoke_invite", Op::RevokeWorkspaceInvite, "Revoke a pending invite"),
+1−1
1919 y={p.y * 10 + 1}
2020 width="8"
2121 height="8"
22− fill={p.one ? 'var(--g1t-merged)' : 'currentColor'}
22+ fill={p.one ? 'var(--g1t-accent)' : 'currentColor'}
2323 />
2424 ))}
2525 </svg>
+1−0
2929 | `repo.collaborator_added`, `repo.collaborator_role_changed`, `repo.collaborator_removed` | Someone was given a role on it, had it changed, or lost it. See [access and roles](/guides/access-and-roles/). |
3030 | `repo.invitation_created`, `repo.invitation_revoked` | Someone was invited to it, or an invitation was withdrawn. |
3131 | `workspace.base_permission_changed` | An owner changed what members get on every repository. |
32+| `workspace.team_creation_changed` | An owner changed who can create teams. See [who can create teams](/guides/teams/#who-can-create-teams). |
3233 | `team.created`, `team.edited`, `team.deleted` | A [team](/guides/teams/) was created, changed or deleted. |
3334 | `team.member_added`, `team.member_role_changed`, `team.member_removed` | Someone was added to a team, made its maintainer or a member, or taken out of it. |
3435 | `team.repo_added`, `team.repo_role_changed`, `team.repo_removed` | A team was given a role on a repository, had it changed, or lost it. |
+1−1
359359 | `account:write` | Change your email addresses, make invites, answer invitations and pin projects |
360360 | `notifications:read` | See your [inbox](/guides/inbox/), its threads, and what you subscribe to and watch |
361361 | `notifications:write` | Mark notifications read, done, saved or snoozed, subscribe to threads and watch repositories |
362−| `workspace:read` | Read workspace invites, integrations, model routes and [teams](/guides/teams/) |
362+| `workspace:read` | Read workspace settings, invites, integrations, model routes and [teams](/guides/teams/) |
363363 | `workspace:admin` | Create and delete workspaces, invite members, manage teams, connect integrations |
364364 | `billing:read` | See a workspace's [usage, budget, AI credit and invoices](/guides/usage-and-billing/) |
365365 | `billing:write` | Change a workspace's budget and buy AI credit. Only owners, as people: a workspace's own token and g1t's agents never change billing, whatever their scopes. Not in any preset but full access. |
+3−3
2626
2727 A workspace's page, `g1t.sh/<workspace>`, shows your pinned projects and
2828 its most active ones, each with where it is deployed, its latest build, and
29−its open issues and pull requests. Its **Projects** tab lists every one,
30−with search, filters and sorting; see
31−[the Projects tab](/guides/workspaces/#the-projects-tab). The sidebar keeps
29+its open issues and pull requests. **All projects** in the sidebar lists
30+every one, with search, filters and sorting; see
31+[the Projects page](/guides/workspaces/#the-projects-page). The sidebar keeps
3232 your [pinned and recent projects](/guides/workspaces/#pinned-and-recent-projects).
3333
3434 ## A project's pages
+25−5
1515 ## Create a team
1616
1717 Any member of the workspace with a confirmed email address can create a
18−team, and becomes its first maintainer.
18+team, and becomes its first maintainer, unless an owner has set
19+[who can create teams](#who-can-create-teams) to owners only.
1920
20−1. Open **Teams** in the sidebar, or the **Teams** tab of the workspace's
21− page: `g1t.sh/<workspace>/-/teams`.
21+1. Open **Teams** in the sidebar: `g1t.sh/<workspace>/-/teams`.
2222 2. Choose **New team**, or go to `g1t.sh/<workspace>/-/teams/new`. The
2323 **+** menu in the top bar has **New team** too, for the workspace the
2424 sidebar is on.
3939 | Description | Up to 280 characters. |
4040 | Teams in one workspace | Up to 500. |
4141
42−The **Teams** tab lists the teams you can see, yours first, then by
42+The **Teams** page lists the teams you can see, yours first, then by
4343 name, with a search box that matches names and slugs. A person's teams
44−also show beside them on the workspace's **People** tab.
44+also show beside them on the workspace's **People** page.
45+
46+### Who can create teams
47+
48+An owner chooses who can create the workspace's teams, under **Settings**,
49+**General**, **Teams**:
50+
51+| Who can create teams | |
52+| --- | --- |
53+| **Any member** | Every member with a confirmed email address. The default. |
54+| **Owners only** | Only the workspace's owners. Members see **Only owners can create teams in this workspace.** on the Teams page instead of **New team**, and **New team** in the **+** menu is greyed out. |
55+
56+Teams that already exist stay as they are, and their maintainers still
57+manage them. The change is recorded in the [audit log](/guides/audit-log/)
58+as `workspace.team_creation_changed`.
59+
60+Through the API, set `team_creation` to `members` or `owners` with
61+[`PATCH /workspaces/{workspace}`](/reference/api/workspaces/update-workspace/)
62+(the `workspace` tool's `update` action over MCP); the workspace you get
63+back, and [`GET /workspaces/{workspace}`](/reference/api/workspaces/get-workspace/),
64+include it. Creating a team when you may not is refused with `403`.
4565
4666 ## Visibility
4767
+36−21
270270 | Owner | Everything a member can, and manage members, the base permission, the workspace's access tokens, its details, and billing: the plan, card checks, prepayment and limits. Admin on every repository, and the only ones who can transfer and delete them; see [access and roles](/guides/access-and-roles/). |
271271
272272 Whoever creates a workspace is its owner. An owner adds people on the
273−workspace's **People**, `g1t.sh/<workspace>/-/people` (a tab of the
274−workspace's page, and in the sidebar):
273+workspace's **People**, `g1t.sh/<workspace>/-/people` (in the sidebar):
275274
276275 - **By username**: someone already on g1t joins at once, as a member.
277276 - **By email address**: g1t emails an invite that only that address can
298297 ## The workspace's page
299298
300299 A workspace's own page, `g1t.sh/<workspace>`, has its icon, name, address
301−and description at the top, and tabs under them:
300+and description at the top, then its overview: your
301+[pinned projects](#pinned-and-recent-projects), then the most active ones,
302+the pull requests in progress across them, and **All projects**. Members
303+also see a **Usage** card with this month's spend, and who belongs.
302304
303−| Tab | Address | Who | |
305+The workspace's other pages each have a heading of their own and a row in
306+[the sidebar](#the-sidebar), lit while you are on them. The trail in the
307+top bar, such as *acme / Projects*, leads back to the workspace's page.
308+
309+| Page | Address | Who | |
304310 | --- | --- | --- | --- |
305−| **Overview** | `g1t.sh/<workspace>` | Everyone | Your [pinned projects](#pinned-and-recent-projects), then the most active ones, the pull requests in progress across them, and **All projects**. Members also see a **Usage** card with this month's spend, and who belongs. |
306−| **Projects** | `/-/projects` | Everyone | Every project you can see, with their count. See [the Projects tab](#the-projects-tab). |
307−| [**Packages**](/guides/packages/) | `/-/packages` | Everyone | What the workspace publishes. |
311+| **Overview** | `g1t.sh/<workspace>` | Everyone | The page above. |
312+| **Projects** | `/-/projects` | Everyone | Every project you can see. See [the Projects page](#the-projects-page). |
313+| [**Packages**](/guides/packages/) | `/-/packages` | Everyone | What the workspace publishes. A visitor opens it from **Packages** on the workspace's page. |
308314 | [**Teams**](/guides/teams/) | `/-/teams` | Members | Groups of members given roles on repositories together, mentioned as `@workspace/team` and asked to review together. Each team has its own page at `/-/teams/<team>`. |
309−| **People** | `/-/people` | Members | Who belongs, with their count. Owners add and remove people here. |
315+| **People** | `/-/people` | Members | Who belongs. Owners add and remove people here. |
310316 | **Insights** | `/-/insights` | Members | Coming soon: how the whole workspace delivers. |
311317 | **Settings** | `/-/settings` | Owners | How the workspace is set up and connected (below). |
312318
317323
318324 Older addresses still work: `/-/members` opens People, and
319325 `g1t.sh/<workspace>?tab=projects` (or `repositories`, `packages`,
320−`people`) opens that tab.
326+`teams`, `people`, `insights` or `settings`) opens that page.
321327
322−### The Projects tab
328+### The Projects page
323329
324−The Projects tab is made for workspaces with hundreds of projects:
330+The Projects page, `g1t.sh/<workspace>/-/projects`, is made for workspaces with hundreds of projects:
325331
326332 - **Find a project** matches every word you type in a project's name, its
327333 address or its description. Press <kbd>/</kbd> anywhere on the page to
353359 [Explore](https://g1t.sh/explore), public projects from all of g1t, is in
354360 the top bar, beside **Docs**.
355361
356−In order, it lists **Mission control**; the workspace's
357−[projects](#pinned-and-recent-projects); under **Workspace**, the places work
358−happens across them (**Agent fleet**, **Context**, **Memory**, **Security**
359−and [**Packages**](/guides/packages/), with **Boards** and **Roadmap**
360−soon); then **People**, [**Teams**](/guides/teams/), **Usage**, what g1t's runs have
361−cost (see [usage and billing](/guides/usage-and-billing/)), **Support** and
362−**Settings**. An item with an arrow opens a list of its own in the sidebar:
362+It has two parts, a rule apart. Above the rule is what is yours in every
363+workspace: **Mission control**, your **Inbox** with how many items are
364+unread, and the repositories **Shared with you** in workspaces you do not
365+belong to. Below it, under the workspace's name, is the workspace:
366+
367+1. **Overview**, the [workspace's page](#the-workspaces-page).
368+2. Its [projects](#pinned-and-recent-projects), ending with **All projects**.
369+3. The places work happens across them: **Agent fleet**, **Context**,
370+ **Memory**, **Security** and [**Packages**](/guides/packages/), with
371+ **Insights**, **Boards** and **Roadmap** soon.
372+4. **People**, [**Teams**](/guides/teams/), **Usage**, what g1t's runs have
373+ cost (see [usage and billing](/guides/usage-and-billing/)), **Support**
374+ and **Settings**.
375+
376+One row is lit wherever you are: **Teams** on a team's pages, **Packages**
377+on a package's, and **Settings** on every page it opens. An item with an arrow opens a list of its own in the sidebar:
363378 **Settings** slides over to how the workspace is set up and connected, and
364379 the row at the top, **‹ Settings**, slides back:
365380
366381 | Settings | Who | |
367382 | --- | --- | --- |
368−| **General** | Owners | The icon, the display name, a one-line description and the address (the slug). |
383+| **General** | Owners | The icon, the display name, a one-line description, the address (the slug), [who can create teams](/guides/teams/#who-can-create-teams), and [data residency](#data-residency). |
369384 | **Repositories** | Members | The workspace's repositories. Owners also see **Recently deleted**, where a [deleted repository](/guides/managing-repositories/#restore-a-repository) can be restored, or purged, for 30 days. |
370385 | **Access tokens** | Members | The workspace's own tokens. Owners create and delete them. |
371386 | **Guardrails** | Members | What agents may do and spend across the workspace. Owners change them. |
396411
397412 - **Pinned**: the projects you pinned, in your order, up to eight a
398413 workspace. Pin one with **Pin** on its page, or the pin on its row of the
399− Projects tab or its card on the Overview. Drag a pinned project to move
414+ Projects page or its card on the Overview. Drag a pinned project to move
400415 it, or hold <kbd>Alt</kbd> and press the up or down arrow.
401416 - **Recent**: the projects you opened last that you have not pinned, up to
402417 five.
403−- **All projects**, with how many there are, opens the Projects tab.
418+- **All projects**, with how many there are, opens the Projects page.
404419
405420 Pins and recent projects are yours: nobody else sees them, and each
406421 workspace has its own. ⌘K finds any project in the workspace, pinned or not.
+3−2
510510 | --- | --- | --- | --- |
511511 | [`list`](/reference/api/teams/list-teams/) | The workspace's teams you can see, yours first; `query` narrows by name or slug. Members only. | `workspace` | `workspace:read` |
512512 | [`get`](/reference/api/teams/get-team/) | One team: its `visibility`, `parent`, `notify`, `review_assignment`, counts, your `viewer_role` and whether you may change it (`can_manage`). | `workspace`, `team` | `workspace:read` |
513−| [`create`](/reference/api/teams/create-team/) | Create a team; you become its maintainer. `slug` is made from `name` unless given; `visibility`, `parent`, `notify`, and `members` to add by username. | `workspace`, `name` | `workspace:admin` |
513+| [`create`](/reference/api/teams/create-team/) | Create a team; you become its maintainer. Members may, unless the workspace's `team_creation` is `owners`. `slug` is made from `name` unless given; `visibility`, `parent`, `notify`, and `members` to add by username. | `workspace`, `name` | `workspace:admin` |
514514 | [`update`](/reference/api/teams/update-team/) | Change its `name`, `slug`, `description`, `visibility`, `parent` (`""` for none), `notify` or `review_assignment`. Owners and its maintainers. | `workspace`, `team` | `workspace:admin` |
515515 | [`delete`](/reference/api/teams/delete-team/) | Delete it; its child teams move up to its parent, and the roles it gave go. Owners and its maintainers. | `workspace`, `team` | `workspace:admin` |
516516 | [`list_members`](/reference/api/teams/list-team-members/) | Its people and their `role` (`member` or `maintainer`); with `include_child_teams`, its child teams' people too, each with `via`. | `workspace`, `team` | `workspace:read` |
532532
533533 | Action | What it does | Required | Scope |
534534 | --- | --- | --- | --- |
535+| [`get`](/reference/api/workspaces/get-workspace/) | One workspace you belong to: its name, description and member count, its `base_permission` and `team_creation`. Members only. | `workspace` | `workspace:read` |
535536 | [`create`](/reference/api/workspaces/create-workspace/) | Create a workspace. | `slug` | `workspace:admin` |
536−| [`update`](/reference/api/workspaces/update-workspace/) | Change its display name and description, and with the `access:admin` scope too, its `base_permission`. Only the fields given change; the slug never does. Owners only. | `workspace` | `workspace:admin` |
537+| [`update`](/reference/api/workspaces/update-workspace/) | Change its display name and description, who may create its teams (`team_creation`: `members` or `owners`), and with the `access:admin` scope too, its `base_permission`. Only the fields given change; the slug never does. Owners only. | `workspace` | `workspace:admin` |
537538 | [`delete`](/reference/api/workspaces/delete-workspace/) | Delete an empty workspace whose billing is settled; `confirm` is its slug. Owners only. See [deleting a workspace](/guides/workspaces/#delete-a-workspace). | `workspace`, `confirm` | `workspace:admin` |
538539 | [`list_invites`](/reference/api/invites/list-workspace-invites/) | A workspace's invites. Owners only. | `workspace` | `workspace:read` |
539540 | [`invite_member`](/reference/api/invites/invite-member/) | Invite an address into a workspace, with an invite bound to it. Owners only. | `workspace`, `email` | `workspace:admin` |
+0−0

Binary or large file; its contents are not shown.

+3−3
5151 y={p.y * 10 + 1}
5252 width="8"
5353 height="8"
54− fill={p.one ? 'var(--g1t-merged)' : 'currentColor'}
54+ fill={p.one ? 'var(--g1t-accent)' : 'currentColor'}
5555 />
5656 ))}
5757 </svg>
9696 --scalar-color-2: var(--g1t-muted);
9797 --scalar-color-3: var(--g1t-faint);
9898 --scalar-color-accent: #cfc6ff;
99− --scalar-background-accent: color-mix(in srgb, var(--g1t-merged) 12%, transparent);
99+ --scalar-background-accent: color-mix(in srgb, var(--g1t-accent) 12%, transparent);
100100 --scalar-button-1: var(--g1t-fg);
101101 --scalar-button-1-color: var(--g1t-bg);
102102 --scalar-button-1-hover: #ffffff;
103− --scalar-color-green: var(--g1t-accent);
103+ --scalar-color-green: var(--g1t-success);
104104 --scalar-color-blue: var(--g1t-info);
105105 --scalar-color-red: var(--g1t-danger);
106106 --scalar-color-orange: var(--g1t-warn);
+10−10
99 --sl-font-mono: var(--g1t-font-mono);
1010
1111 /* Lavender is the docs' accent: links, the current page, highlights. */
12− --sl-color-accent-low: color-mix(in srgb, var(--g1t-merged) 14%, var(--g1t-bg));
13− --sl-color-accent: var(--g1t-merged);
12+ --sl-color-accent-low: color-mix(in srgb, var(--g1t-accent) 14%, var(--g1t-bg));
13+ --sl-color-accent: var(--g1t-accent);
1414 --sl-color-accent-high: #d9d1ff;
1515
1616 --sl-color-white: var(--g1t-fg);
130130 background: var(--g1t-surface);
131131 color: var(--g1t-fg);
132132 font-weight: 500;
133− box-shadow: inset 2px 0 0 var(--g1t-merged);
133+ box-shadow: inset 2px 0 0 var(--g1t-accent);
134134 }
135135
136136 /* --- Content -------------------------------------------------------------- */
178178 }
179179 .sl-markdown-content a:not(.card, .sl-link-button, .sl-link-card) {
180180 color: #cfc6ff;
181− text-decoration-color: color-mix(in srgb, var(--g1t-merged) 45%, transparent);
181+ text-decoration-color: color-mix(in srgb, var(--g1t-accent) 45%, transparent);
182182 text-underline-offset: 0.2em;
183183 }
184184 .sl-markdown-content a:not(.card, .sl-link-button, .sl-link-card):hover {
185− text-decoration-color: var(--g1t-merged);
185+ text-decoration-color: var(--g1t-accent);
186186 }
187187 .sl-markdown-content :not(pre) > code {
188188 border-radius: 0.3rem;
251251 background: color-mix(in srgb, var(--g1t-info) 7%, var(--g1t-bg));
252252 }
253253 .starlight-aside--tip {
254− border-color: var(--g1t-accent);
255− background: color-mix(in srgb, var(--g1t-accent) 6%, var(--g1t-bg));
254+ border-color: var(--g1t-success);
255+ background: color-mix(in srgb, var(--g1t-success) 6%, var(--g1t-bg));
256256 }
257257 .starlight-aside--caution {
258258 border-color: var(--g1t-warn);
346346 color: var(--g1t-fg);
347347 }
348348 .sl-markdown-content .card .icon {
349− color: var(--g1t-merged);
350− background: color-mix(in srgb, var(--g1t-merged) 12%, transparent);
349+ color: var(--g1t-accent);
350+ background: color-mix(in srgb, var(--g1t-accent) 12%, transparent);
351351 border: 0;
352352 border-radius: 0.5rem;
353353 padding: 0.3rem;
413413 }
414414 .g1t-method-get,
415415 .g1t-method[data-method='get'] {
416− --method: var(--g1t-accent);
416+ --method: var(--g1t-success);
417417 }
418418 .g1t-method-post,
419419 .g1t-method[data-method='post'] {
+7−3
2020 --color-faint: var(--g1t-faint);
2121
2222 --color-accent: var(--g1t-accent);
23+ --color-accent-hover: var(--g1t-accent-hover);
2324 --color-accent-dim: var(--g1t-accent-dim);
2425
26+ --color-success: var(--g1t-success);
27+ --color-success-dim: var(--g1t-success-dim);
28+
2529 --color-info: var(--g1t-info);
2630 --color-merged: var(--g1t-merged);
2731 --color-warn: var(--g1t-warn);
3741 }
3842
3943 ::selection {
40− background: color-mix(in srgb, var(--color-merged) 35%, transparent);
44+ background: color-mix(in srgb, var(--color-accent) 35%, transparent);
4145 }
4246
4347 @layer base {
4448 :focus-visible {
45− outline: 2px solid var(--color-merged);
49+ outline: 2px solid var(--color-accent);
4650 outline-offset: 2px;
4751 }
4852
5761 }
5862
5963 :root {
60− accent-color: var(--color-merged);
64+ accent-color: var(--color-accent);
6165 }
6266
6367 /* Figures line up in columns of money. */
+8−8
185185 );
186186 }
187187 return (
188− <section id="review" className={`scroll-mt-20 rounded-lg border p-4 sm:p-5 ${danger ? "border-warn/40 bg-warn/5" : "border-merged/40 bg-merged/5"}`}>
188+ <section id="review" className={`scroll-mt-20 rounded-lg border p-4 sm:p-5 ${danger ? "border-warn/40 bg-warn/5" : "border-accent/40 bg-accent/5"}`}>
189189 <h2 className="font-semibold tracking-tight">{title}</h2>
190190 <div className="mt-3">{body}</div>
191191 <form method="post" action={`${pathname}#${RESULT_ANCHOR[review.intent] ?? "top"}`} className="mt-4 flex flex-wrap items-center gap-2">
462462 /** A radio drawn as one button of a segmented row; still a plain radio without CSS. */
463463 function Segment({ name, value, checked, children }: { name: string; value: string; checked: boolean; children: ReactNode }) {
464464 return (
465− <label className="flex-1 cursor-pointer border-r border-line px-3 py-1.5 text-center text-sm whitespace-nowrap text-muted transition-colors last:border-r-0 hover:bg-raised hover:text-fg has-checked:bg-merged has-checked:font-medium has-checked:text-bg has-focus-visible:outline-2 has-focus-visible:-outline-offset-2 has-focus-visible:outline-merged">
465+ <label className="flex-1 cursor-pointer border-r border-line px-3 py-1.5 text-center text-sm whitespace-nowrap text-muted transition-colors last:border-r-0 hover:bg-raised hover:text-fg has-checked:bg-accent has-checked:font-medium has-checked:text-bg has-focus-visible:outline-2 has-focus-visible:-outline-offset-2 has-focus-visible:outline-accent">
466466 <input type="radio" name={name} value={value} defaultChecked={checked} className="sr-only" />
467467 {children}
468468 </label>
534534 {CREDIT_KINDS.map((option) => (
535535 <label
536536 key={option.value}
537− className="flex cursor-pointer gap-2.5 rounded-md border border-line bg-bg px-3 py-2.5 transition-colors hover:border-line-strong has-checked:border-merged/60 has-checked:bg-merged/8"
537+ className="flex cursor-pointer gap-2.5 rounded-md border border-line bg-bg px-3 py-2.5 transition-colors hover:border-line-strong has-checked:border-accent/60 has-checked:bg-accent/8"
538538 >
539539 <input type="radio" name="kind" value={option.value} defaultChecked={kind === option.value} className="mt-0.5" required />
540540 <span>
633633 <Badge tone="lavender">{kindLabel(grant.kind)}</Badge>
634634 <Badge tone={state.tone}>{state.label}</Badge>
635635 {showWorkspace && (
636− <Link to={`/workspaces/${encodeURIComponent(grant.workspace)}#credits`} className="font-mono text-xs text-merged hover:underline">
636+ <Link to={`/workspaces/${encodeURIComponent(grant.workspace)}#credits`} className="font-mono text-xs text-accent hover:underline">
637637 {grant.workspace}
638638 </Link>
639639 )}
859859 {entry.creditKind ? `Credit · ${kindLabel(entry.creditKind)}` : (ENTRY_KIND[entry.kind] ?? entry.kind)}
860860 </Badge>
861861 {showWorkspace && entry.workspace && (
862− <Link to={`/workspaces/${encodeURIComponent(entry.workspace)}`} className="font-mono text-xs text-merged hover:underline">
862+ <Link to={`/workspaces/${encodeURIComponent(entry.workspace)}`} className="font-mono text-xs text-accent hover:underline">
863863 {entry.workspace}
864864 </Link>
865865 )}
882882 .join(" · ")}
883883 </p>
884884 </td>
885− <td className={`tabular px-4 py-2.5 text-right whitespace-nowrap sm:pr-5 ${entry.amountMicros > 0 ? "text-accent" : "text-fg-soft"}`}>
885+ <td className={`tabular px-4 py-2.5 text-right whitespace-nowrap sm:pr-5 ${entry.amountMicros > 0 ? "text-success" : "text-fg-soft"}`}>
886886 {usd(entry.amountMicros, { signed: true })}
887887 </td>
888888 </tr>
908908 ) : (
909909 <ol className="space-y-3">
910910 {audit.map((entry) => (
911− <li key={entry.id} className="border-l-2 border-merged/40 pl-3">
911+ <li key={entry.id} className="border-l-2 border-accent/40 pl-3">
912912 <p className="flex flex-wrap items-center gap-x-2 text-sm">
913− <span className="font-medium text-merged">{actionLabel(entry.action)}</span>
913+ <span className="font-medium text-accent">{actionLabel(entry.action)}</span>
914914 <span className="text-xs text-faint">
915915 <When at={entry.createdAt} time />
916916 </span>
+2−2
99 import { usd } from "~/lib/money";
1010
1111 /** The two series: what was charged (lavender) beside what it cost g1t (gray). */
12−const CHARGED = "var(--g1t-merged)";
12+const CHARGED = "var(--g1t-accent)";
1313 const COST = "#7a7a84";
1414
1515 function Legend() {
131131 <td className="py-1.5 pr-3 text-right">{usd(month.chargedMicros)}</td>
132132 <td className="py-1.5 pr-3 text-right">{usd(month.plansMicros ?? 0)}</td>
133133 <td className="py-1.5 pr-3 text-right text-muted">{usd(month.costMicros)}</td>
134− <td className="py-1.5 pr-3 text-right text-merged">{usd(month.givenMicros ?? 0)}</td>
134+ <td className="py-1.5 pr-3 text-right text-accent">{usd(month.givenMicros ?? 0)}</td>
135135 <td className={`py-1.5 pr-3 text-right ${margin.micros < 0 ? "text-danger" : "text-fg-soft"}`}>
136136 {usd(margin.micros)}
137137 {margin.percent != null && <span className="text-faint"> {margin.percent}%</span>}
+2−2
1212
1313 export function chip(active: boolean) {
1414 return `inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs whitespace-nowrap transition-colors ${
15− active ? "border-merged/50 bg-merged/10 text-merged" : "border-line text-muted hover:border-line-strong hover:text-fg"
15+ active ? "border-accent/50 bg-accent/10 text-accent" : "border-line text-muted hover:border-line-strong hover:text-fg"
1616 }`;
1717 }
1818
6262 key={tab.page}
6363 to={costsHref(tab.page, range)}
6464 aria-current={tab.page === page ? "page" : undefined}
65− className={`-mb-px border-b-2 pb-2 text-sm ${tab.page === page ? "border-merged text-fg" : "border-transparent text-muted hover:text-fg"}`}
65+ className={`-mb-px border-b-2 pb-2 text-sm ${tab.page === page ? "border-accent text-fg" : "border-transparent text-muted hover:text-fg"}`}
6666 >
6767 {tab.title}
6868 </Link>
+1−1
88 import { type DayFigures, marginPercent, percentLabel } from "~/lib/costs";
99 import { usd } from "~/lib/money";
1010
11−const CHARGED = "var(--g1t-merged)";
11+const CHARGED = "var(--g1t-accent)";
1212 const COST = "#7a7a84";
1313 const LOSS = "var(--g1t-danger)";
1414
+1−1
4949
5050 /** One timer: a label, a figure, and a line under it. */
5151 export function Timer({ label, value, hint, tone }: { label: string; value: ReactNode; hint?: ReactNode; tone?: "danger" | "warn" | "mint" }) {
52− const color = tone === "danger" ? "text-danger" : tone === "warn" ? "text-warn" : tone === "mint" ? "text-accent" : "text-fg";
52+ const color = tone === "danger" ? "text-danger" : tone === "warn" ? "text-warn" : tone === "mint" ? "text-success" : "text-fg";
5353 return (
5454 <div className="min-w-0 px-4 py-3">
5555 <p className="text-xs text-muted">{label}</p>
+2−2
2929 y={p.y * 10 + 1}
3030 width="8"
3131 height="8"
32− fill={p.one ? "var(--g1t-merged)" : "currentColor"}
32+ fill={p.one ? "var(--g1t-accent)" : "currentColor"}
3333 />
3434 ));
3535 }
5555 <Pixels of={WORD} />
5656 </svg>
5757 </span>
58− <span className="rounded-full bg-merged/12 px-2 py-0.5 font-mono text-[0.7rem] font-semibold tracking-wide text-merged ring-1 ring-merged/35 ring-inset">
58+ <span className="rounded-full bg-accent/12 px-2 py-0.5 font-mono text-[0.7rem] font-semibold tracking-wide text-accent ring-1 ring-accent/35 ring-inset">
5959 sudo
6060 </span>
6161 </span>
+2−2
169169 return (
170170 <ol className="mt-4 space-y-3">
171171 {sorted.map((note) => (
172− <li key={note.id} className="border-l-2 border-merged/40 pl-3">
172+ <li key={note.id} className="border-l-2 border-accent/40 pl-3">
173173 <p className="text-sm break-words whitespace-pre-line text-fg-soft">{note.text}</p>
174174 <p className="mt-1 flex flex-wrap items-center gap-x-2 text-xs text-faint">
175175 <StaffName email={note.by} me={me} />
200200 function StripeLink({ url, children }: { url: string | null; children: ReactNode }) {
201201 if (!url || !url.startsWith("https://")) return null;
202202 return (
203− <a href={url} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-merged hover:underline">
203+ <a href={url} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-accent hover:underline">
204204 {children}
205205 </a>
206206 );
+4−4
9292 return (
9393 <span
9494 title={label}
95− className="shrink-0 rounded-full bg-merged/15 px-1.5 py-px text-[0.6875rem] font-semibold text-merged tabular-nums ring-1 ring-merged/30"
95+ className="shrink-0 rounded-full bg-accent/15 px-1.5 py-px text-[0.6875rem] font-semibold text-accent tabular-nums ring-1 ring-accent/30"
9696 >
9797 {count > 99 ? "99+" : count}
9898 <span className="sr-only"> {label}</span>
127127 current ? "bg-raised font-medium text-fg" : "text-muted hover:bg-raised/60 hover:text-fg"
128128 }`}
129129 >
130− <NavGlyph icon={item.icon} className={`shrink-0 ${current ? "text-merged" : "text-faint group-hover:text-muted"}`} />
130+ <NavGlyph icon={item.icon} className={`shrink-0 ${current ? "text-accent" : "text-faint group-hover:text-muted"}`} />
131131 <span className="min-w-0 grow truncate">{item.label}</span>
132132 <CountPill count={countFor([item], counts)} label={countLabel(item)} />
133133 {item.soon && <SoonPill />}
145145 aria-current={current ? "page" : undefined}
146146 className={`relative flex h-8 items-center gap-2 rounded-md pr-2 pl-[2.375rem] text-[0.8125rem] transition-colors ${
147147 current
148− ? "bg-raised font-medium text-fg before:absolute before:top-1.5 before:bottom-1.5 before:left-[1.1875rem] before:z-10 before:w-px before:bg-merged"
148+ ? "bg-raised font-medium text-fg before:absolute before:top-1.5 before:bottom-1.5 before:left-[1.1875rem] before:z-10 before:w-px before:bg-accent"
149149 : item.soon
150150 ? "text-faint hover:bg-raised/60 hover:text-muted"
151151 : "text-muted hover:bg-raised/60 hover:text-fg"
240240 <div className="border-t border-line bg-raised/40 px-4 py-3">
241241 {email && (
242242 <p title="Signed in through Cloudflare Access" className="flex items-center gap-2 text-[0.8125rem]">
243− <ShieldCheck size={15} className="shrink-0 text-merged" />
243+ <ShieldCheck size={15} className="shrink-0 text-accent" />
244244 <span className="min-w-0 truncate font-mono text-xs text-fg-soft">{email}</span>
245245 </p>
246246 )}
+8−8
3434 }
3535
3636 const CONTROL =
37− "w-full rounded-md border border-line bg-bg px-3 py-2 text-sm outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-merged/60";
37+ "w-full rounded-md border border-line bg-bg px-3 py-2 text-sm outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-accent/60";
3838
3939 /** Tells password managers that a field is not a login. */
4040 const NOT_A_CREDENTIAL = {
6464
6565 const BUTTON_VARIANTS: Record<Variant, string> = {
6666 primary: "bg-fg text-bg hover:bg-white",
67− lavender: "bg-merged text-bg hover:bg-[#c8bdff]",
67+ lavender: "bg-accent text-bg hover:bg-accent-hover",
6868 quiet: "border border-line text-fg/80 hover:border-line-strong hover:bg-surface hover:text-fg",
6969 danger: "border border-danger/40 text-danger hover:border-danger/70 hover:bg-danger/10",
7070 };
117117 }
118118
119119 const NOTICE = {
120− ok: { icon: CheckCircle2, className: "border-accent/30 bg-accent/8 text-accent" },
120+ ok: { icon: CheckCircle2, className: "border-success/30 bg-success/8 text-success" },
121121 error: { icon: AlertTriangle, className: "border-danger/30 bg-danger/8 text-danger" },
122122 warn: { icon: AlertTriangle, className: "border-warn/30 bg-warn/8 text-warn" },
123− info: { icon: Info, className: "border-merged/30 bg-merged/8 text-merged" },
123+ info: { icon: Info, className: "border-accent/30 bg-accent/8 text-accent" },
124124 } as const;
125125
126126 export function Notice({ tone, children }: { tone: keyof typeof NOTICE; children: ReactNode }) {
137137 export function Badge({ tone = "plain", children }: { tone?: "plain" | "lavender" | "mint" | "warn" | "danger" | "info"; children: ReactNode }) {
138138 const tones = {
139139 plain: "border-line text-muted",
140− lavender: "border-merged/35 bg-merged/10 text-merged",
141− mint: "border-accent/30 bg-accent/8 text-accent",
140+ lavender: "border-accent/35 bg-accent/10 text-accent",
141+ mint: "border-success/30 bg-success/8 text-success",
142142 warn: "border-warn/35 bg-warn/10 text-warn",
143143 danger: "border-danger/35 bg-danger/10 text-danger",
144144 info: "border-info/35 bg-info/10 text-info",
197197 }
198198
199199 const STATE = {
200− ok: { label: "OK", fill: "var(--g1t-accent)", text: "text-accent" },
200+ ok: { label: "OK", fill: "var(--g1t-success)", text: "text-success" },
201201 warning: { label: "Warning", fill: "var(--g1t-warn)", text: "text-warn" },
202202 stopped: { label: "Stopped", fill: "var(--g1t-danger)", text: "text-danger" },
203203 } as const;
236236
237237 /** A label and a figure, for the strip of totals over a page. */
238238 export function Stat({ label, value, hint, tone }: { label: string; value: ReactNode; hint?: ReactNode; tone?: "danger" | "warn" | "mint" }) {
239− const color = tone === "danger" ? "text-danger" : tone === "warn" ? "text-warn" : tone === "mint" ? "text-accent" : "text-fg";
239+ const color = tone === "danger" ? "text-danger" : tone === "warn" ? "text-warn" : tone === "mint" ? "text-success" : "text-fg";
240240 return (
241241 <div className="rounded-lg border border-line bg-surface px-4 py-3">
242242 <p className="text-xs text-muted">{label}</p>
+1−1
7272 <ul className="list-disc space-y-1 pl-5 text-sm text-muted">
7373 <li>
7474 Disputes and declined cards are signals in{" "}
75− <Link to={reachOutHref({ kind: "declined" })} className="text-merged hover:underline">
75+ <Link to={reachOutHref({ kind: "declined" })} className="text-accent hover:underline">
7676 Reach out
7777 </Link>
7878 . Stripe Radar's early fraud warnings are not in sudo yet.
+2−2
102102 {before && (
103103 <p className="mt-4 text-xs text-faint">
104104 Changes before <When at={before} time />.{" "}
105− <Link to={auditHref({ by, action })} className="text-merged hover:underline">
105+ <Link to={auditHref({ by, action })} className="text-accent hover:underline">
106106 Back to the newest
107107 </Link>
108108 </p>
130130 </p>
131131 <div className="min-w-0 grow">
132132 <p className="flex flex-wrap items-center gap-x-2 text-sm">
133− <span className={`font-medium ${stripe ? "text-info" : "text-merged"}`}>{actionLabel(entry.action)}</span>
133+ <span className={`font-medium ${stripe ? "text-info" : "text-accent"}`}>{actionLabel(entry.action)}</span>
134134 {path && (
135135 <Link to={path} className="inline-flex items-center gap-0.5 text-fg-soft hover:text-fg hover:underline hover:underline-offset-4">
136136 <span className={entry.account.startsWith("ws_") ? "font-mono text-xs" : ""}>{accountName(entry.account, nameMap)}</span>
+4−4
211211 </Field>
212212 <div className="flex flex-col justify-end gap-2">
213213 <label className="flex items-center gap-2 text-sm text-muted">
214− <input type="checkbox" name="scaleToOwn" className="accent-[var(--g1t-merged)]" /> Scale to g1t's count
214+ <input type="checkbox" name="scaleToOwn" className="accent-[var(--g1t-accent)]" /> Scale to g1t's count
215215 </label>
216216 <div className="flex gap-2">
217217 <Button type="submit">Save mapping</Button>
233233 <input type="hidden" name="intent" value="settings" />
234234 <Field label="Apply small moves on their own">
235235 <label className="flex h-[38px] items-center gap-2 text-sm text-fg-soft">
236− <input type="checkbox" name="autoApply" defaultChecked={report.settings.autoApply} className="accent-[var(--g1t-merged)]" /> On
236+ <input type="checkbox" name="autoApply" defaultChecked={report.settings.autoApply} className="accent-[var(--g1t-accent)]" /> On
237237 </label>
238238 </Field>
239239 <Field label="Up to, either way, %">
259259 </Field>
260260 <Field label="Card fee on AI credit bought by card">
261261 <label className="flex h-[38px] items-center gap-2 text-sm text-fg-soft">
262− <input type="checkbox" name="cardFee" defaultChecked={report.settings.cardFee ?? true} className="accent-[var(--g1t-merged)]" /> Passed on as its own line
262+ <input type="checkbox" name="cardFee" defaultChecked={report.settings.cardFee ?? true} className="accent-[var(--g1t-accent)]" /> Passed on as its own line
263263 </label>
264264 </Field>
265265 <div className="sm:col-span-2 lg:col-span-4">
291291 </div>
292292 </div>
293293 <p className="tabular mt-1.5 text-sm">
294− Cost {unitDollars(p.currentCostMicros)} → <span className={rise ? "text-warn" : "text-accent"}>{unitDollars(p.proposedCostMicros)}</span> per {p.unit || "unit"}{" "}
294+ Cost {unitDollars(p.currentCostMicros)} → <span className={rise ? "text-warn" : "text-success"}>{unitDollars(p.proposedCostMicros)}</span> per {p.unit || "unit"}{" "}
295295 <span className="text-faint">({percentLabel(p.changePercent, { signed: true })})</span>
296296 </p>
297297 <p className="mt-1 text-xs text-muted">{p.reason}</p>
+1−1
169169
170170 function CapMeter({ label, used, cap, hint }: { label: string; used: number; cap: number; hint: ReactNode }) {
171171 const percent = capPercent(used, cap);
172− const tone = cap > 0 && used >= cap ? "bg-danger" : percent >= 75 ? "bg-warn" : "bg-merged";
172+ const tone = cap > 0 && used >= cap ? "bg-danger" : percent >= 75 ? "bg-warn" : "bg-accent";
173173 return (
174174 <div className="rounded-md border border-line px-4 py-3">
175175 <div className="flex flex-wrap items-baseline justify-between gap-2">
+1−1
293293 function StripeLink({ url }: { url: string | null }) {
294294 if (!url || !url.startsWith("https://")) return <span className="text-faint">—</span>;
295295 return (
296− <a href={url} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-merged hover:underline">
296+ <a href={url} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-accent hover:underline">
297297 Stripe
298298 <ExternalLink size={12} />
299299 </a>
+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

+0−0

Binary or large file; its contents are not shown.

This change is too large to show in full.