Compare changes
Choose two branches to see what one has that the other does not, then open a pull request for it.
3 commits
| 9 | 9 | - **Pixels:** each is its own square, 0.8 of its cell, centred, with square | |
| 10 | 10 | corners. | |
| 11 | 11 | - **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` | |
| 13 | 13 | (`--g1t-bg`). On light backgrounds the letters are `#161618` and the 1 is | |
| 14 | 14 | `#6b56e8`. Printed in one ink, everything is that ink. | |
| 15 | 15 | - **Icon:** the 1 alone, centred in a square, for favicons, app icons and |
| 47 | 47 | ( | |
| 48 | 48 | "Workspaces", | |
| 49 | 49 | "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], | |
| 51 | 51 | ), | |
| 52 | 52 | ( | |
| 53 | 53 | "Invites", | |
| 360 | 360 | fn title(op: Op) -> &'static str { | |
| 361 | 361 | match op { | |
| 362 | 362 | Op::Whoami => "Get the current user", | |
| 363 | + | Op::GetWorkspace => "Get a workspace", | |
| 363 | 364 | Op::CreateWorkspace => "Create a workspace", | |
| 364 | 365 | Op::DeleteWorkspace => "Delete a workspace", | |
| 365 | 366 | Op::UpdateWorkspace => "Update a workspace", |
| 16 | 16 | use g1t_contracts::repos::{CreateArgs, GetArgs, ListArgs as ListReposArgs, Repo, RepoPath}; | |
| 17 | 17 | use g1t_contracts::teams::{ | |
| 18 | 18 | 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, | |
| 20 | 21 | UserTeamsArgs, | |
| 21 | 22 | }; | |
| 22 | 23 | use g1t_contracts::security::{ | |
| 87 | 88 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | |
| 88 | 89 | pub enum Op { | |
| 89 | 90 | Whoami, | |
| 91 | + | GetWorkspace, | |
| 90 | 92 | CreateWorkspace, | |
| 91 | 93 | DeleteWorkspace, | |
| 92 | 94 | UpdateWorkspace, | |
| 623 | 625 | } | |
| 624 | 626 | ||
| 625 | 627 | impl Op { | |
| 626 | − | pub const ALL: [Op; 204] = [ | |
| 628 | + | pub const ALL: [Op; 205] = [ | |
| 627 | 629 | Op::Whoami, | |
| 630 | + | Op::GetWorkspace, | |
| 628 | 631 | Op::CreateWorkspace, | |
| 629 | 632 | Op::DeleteWorkspace, | |
| 630 | 633 | Op::UpdateWorkspace, | |
| 838 | 841 | pub fn name(self) -> &'static str { | |
| 839 | 842 | match self { | |
| 840 | 843 | Op::Whoami => "whoami", | |
| 844 | + | Op::GetWorkspace => "get_workspace", | |
| 841 | 845 | Op::CreateWorkspace => "create_workspace", | |
| 842 | 846 | Op::DeleteWorkspace => "delete_workspace", | |
| 843 | 847 | Op::UpdateWorkspace => "update_workspace", | |
| 1053 | 1057 | Op::DeleteWorkspace => { | |
| 1054 | 1058 | "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." | |
| 1055 | 1059 | } | |
| 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 | + | } | |
| 1056 | 1063 | 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." | |
| 1058 | 1065 | } | |
| 1059 | 1066 | Op::ListRepos => "Repositories you can see, optionally filtered by a search query.", | |
| 1060 | 1067 | Op::GetRepo => "One repository's details.", | |
| 1436 | 1443 | "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." | |
| 1437 | 1444 | } | |
| 1438 | 1445 | 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." | |
| 1440 | 1447 | } | |
| 1441 | 1448 | Op::UpdateTeam => { | |
| 1442 | 1449 | "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." | |
| 1512 | 1519 | let states = json!({ "type": "string", "enum": ["open", "closed"] }); | |
| 1513 | 1520 | match self { | |
| 1514 | 1521 | Op::Whoami => object(json!({}), &[]), | |
| 1522 | + | Op::GetWorkspace => object(json!({ "workspace": workspace_schema() }), &["workspace"]), | |
| 1515 | 1523 | Op::CreateWorkspace => object( | |
| 1516 | 1524 | json!({ | |
| 1517 | 1525 | "slug": { | |
| 1621 | 1629 | "enum": g1t_contracts::access::BasePermission::ALL.map(|base| base.as_str()), | |
| 1622 | 1630 | "description": "What every member gets on each repository: none, read, write or admin. Needs the access:admin scope as well.", | |
| 1623 | 1631 | }, | |
| 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 | + | }, | |
| 1624 | 1637 | }), | |
| 1625 | 1638 | &["workspace"], | |
| 1626 | 1639 | ), | |
| 2834 | 2847 | !matches!( | |
| 2835 | 2848 | self, | |
| 2836 | 2849 | Op::Whoami | |
| 2850 | + | | Op::GetWorkspace | |
| 2837 | 2851 | | Op::CreateWorkspace | |
| 2838 | 2852 | | Op::DeleteWorkspace | |
| 2839 | 2853 | | Op::UpdateWorkspace | |
| 3080 | 3094 | ||
| 3081 | 3095 | match self { | |
| 3082 | 3096 | 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 | + | } | |
| 3083 | 3107 | Op::CreateWorkspace => { | |
| 3084 | 3108 | pass( | |
| 3085 | 3109 | identity, | |
| 3189 | 3213 | None => return failed(FailureCode::Invalid, "base_permission is none, read, write or admin."), | |
| 3190 | 3214 | }, | |
| 3191 | 3215 | }; | |
| 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 | + | }; | |
| 3192 | 3223 | 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."); | |
| 3195 | 3226 | } | |
| 3196 | 3227 | let found = || async { | |
| 3197 | 3228 | g1t_kit::call::<_, Option<Workspace>>(identity, "get_workspace", &json!({ "slug": workspace() })).await | |
| 3232 | 3263 | return Ok(Outcome::Fail(failure)); | |
| 3233 | 3264 | } | |
| 3234 | 3265 | } | |
| 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 | + | } | |
| 3235 | 3282 | match found().await? { | |
| 3236 | 3283 | Some(workspace) => ok(&workspace), | |
| 3237 | 3284 | None => failed(FailureCode::NotFound, "Workspace not found."), |
| 66 | 66 | "description": null, | |
| 67 | 67 | "created_at": "2026-10-04T16:02:51.337Z", | |
| 68 | 68 | "member_count": 1, | |
| 69 | − | "base_permission": "write" | |
| 69 | + | "base_permission": "write", | |
| 70 | + | "team_creation": "members" | |
| 70 | 71 | }, | |
| 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}`." | |
| 72 | 73 | }, | |
| 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 | + | }, | |
| 73 | 90 | "update_workspace": { | |
| 74 | 91 | "params": { | |
| 75 | 92 | "workspace": "acme-labs" | |
| 76 | 93 | }, | |
| 77 | 94 | "request": { | |
| 78 | 95 | "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" | |
| 80 | 98 | }, | |
| 81 | 99 | "response": { | |
| 82 | 100 | "id": "wsp_01m43teqa9em6bje0bhvdj4jkb", | |
| 85 | 103 | "description": "Rockets, and the software that flies them.", | |
| 86 | 104 | "created_at": "2026-10-04T16:02:51.337Z", | |
| 87 | 105 | "member_count": 3, | |
| 88 | − | "base_permission": "write" | |
| 106 | + | "base_permission": "write", | |
| 107 | + | "team_creation": "owners" | |
| 89 | 108 | }, | |
| 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/)." | |
| 91 | 110 | }, | |
| 92 | 111 | "delete_workspace": { | |
| 93 | 112 | "request": { | |
| 4451 | 4470 | "created_at": "2026-10-06T15:02:11.480Z", | |
| 4452 | 4471 | "updated_at": "2026-10-06T15:02:11.480Z" | |
| 4453 | 4472 | }, | |
| 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/)." | |
| 4455 | 4474 | }, | |
| 4456 | 4475 | "get_team": { | |
| 4457 | 4476 | "params": { |
| 105 | 105 | sent["codeOwners"] = code_owners.clone(); | |
| 106 | 106 | } | |
| 107 | 107 | 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), | |
| 109 | 109 | Op::ListRepos => through::<Vec<repos::Repo>>(op, sent), | |
| 110 | 110 | Op::Search => through::<search::SearchResults>(op, sent), | |
| 111 | 111 | Op::GetRepo |
| 31 | 31 | pub const ROUTES: &[Route] = &[ | |
| 32 | 32 | route("GET", "/user", Op::Whoami, &[]), | |
| 33 | 33 | route("POST", "/workspaces", Op::CreateWorkspace, &[]), | |
| 34 | + | route("GET", "/workspaces/:workspace", Op::GetWorkspace, &[]), | |
| 34 | 35 | route("DELETE", "/workspaces/:workspace", Op::DeleteWorkspace, &[]), | |
| 35 | 36 | route("GET", "/user/emails", Op::ListEmails, &[]), | |
| 36 | 37 | route("POST", "/user/emails", Op::AddEmail, &[]), |
| 270 | 270 | 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.", | |
| 271 | 271 | default_action: None, | |
| 272 | 272 | actions: &[ | |
| 273 | + | a("get", Op::GetWorkspace, "A workspace's details and settings"), | |
| 273 | 274 | a("create", Op::CreateWorkspace, "Create a workspace"), | |
| 274 | 275 | 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"), | |
| 276 | 277 | a("list_invites", Op::ListWorkspaceInvites, "Its invites"), | |
| 277 | 278 | a("invite_member", Op::InviteMember, "Invite an email address"), | |
| 278 | 279 | a("revoke_invite", Op::RevokeWorkspaceInvite, "Revoke a pending invite"), |
| 19 | 19 | y={p.y * 10 + 1} | |
| 20 | 20 | width="8" | |
| 21 | 21 | height="8" | |
| 22 | − | fill={p.one ? 'var(--g1t-merged)' : 'currentColor'} | |
| 22 | + | fill={p.one ? 'var(--g1t-accent)' : 'currentColor'} | |
| 23 | 23 | /> | |
| 24 | 24 | ))} | |
| 25 | 25 | </svg> |
| 29 | 29 | | `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/). | | |
| 30 | 30 | | `repo.invitation_created`, `repo.invitation_revoked` | Someone was invited to it, or an invitation was withdrawn. | | |
| 31 | 31 | | `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). | | |
| 32 | 33 | | `team.created`, `team.edited`, `team.deleted` | A [team](/guides/teams/) was created, changed or deleted. | | |
| 33 | 34 | | `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. | | |
| 34 | 35 | | `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. | |
| 359 | 359 | | `account:write` | Change your email addresses, make invites, answer invitations and pin projects | | |
| 360 | 360 | | `notifications:read` | See your [inbox](/guides/inbox/), its threads, and what you subscribe to and watch | | |
| 361 | 361 | | `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/) | | |
| 363 | 363 | | `workspace:admin` | Create and delete workspaces, invite members, manage teams, connect integrations | | |
| 364 | 364 | | `billing:read` | See a workspace's [usage, budget, AI credit and invoices](/guides/usage-and-billing/) | | |
| 365 | 365 | | `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. | |
| 26 | 26 | ||
| 27 | 27 | A workspace's page, `g1t.sh/<workspace>`, shows your pinned projects and | |
| 28 | 28 | 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 | |
| 32 | 32 | your [pinned and recent projects](/guides/workspaces/#pinned-and-recent-projects). | |
| 33 | 33 | ||
| 34 | 34 | ## A project's pages |
| 15 | 15 | ## Create a team | |
| 16 | 16 | ||
| 17 | 17 | 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. | |
| 19 | 20 | ||
| 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`. | |
| 22 | 22 | 2. Choose **New team**, or go to `g1t.sh/<workspace>/-/teams/new`. The | |
| 23 | 23 | **+** menu in the top bar has **New team** too, for the workspace the | |
| 24 | 24 | sidebar is on. | |
| 39 | 39 | | Description | Up to 280 characters. | | |
| 40 | 40 | | Teams in one workspace | Up to 500. | | |
| 41 | 41 | ||
| 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 | |
| 43 | 43 | 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`. | |
| 45 | 65 | ||
| 46 | 66 | ## Visibility | |
| 47 | 67 |
| 270 | 270 | | 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/). | | |
| 271 | 271 | ||
| 272 | 272 | 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): | |
| 275 | 274 | ||
| 276 | 275 | - **By username**: someone already on g1t joins at once, as a member. | |
| 277 | 276 | - **By email address**: g1t emails an invite that only that address can | |
| 298 | 297 | ## The workspace's page | |
| 299 | 298 | ||
| 300 | 299 | 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. | |
| 302 | 304 | ||
| 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 | | | |
| 304 | 310 | | --- | --- | --- | --- | | |
| 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. | | |
| 308 | 314 | | [**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. | | |
| 310 | 316 | | **Insights** | `/-/insights` | Members | Coming soon: how the whole workspace delivers. | | |
| 311 | 317 | | **Settings** | `/-/settings` | Owners | How the workspace is set up and connected (below). | | |
| 312 | 318 | ||
| 317 | 323 | ||
| 318 | 324 | Older addresses still work: `/-/members` opens People, and | |
| 319 | 325 | `g1t.sh/<workspace>?tab=projects` (or `repositories`, `packages`, | |
| 320 | − | `people`) opens that tab. | |
| 326 | + | `teams`, `people`, `insights` or `settings`) opens that page. | |
| 321 | 327 | ||
| 322 | − | ### The Projects tab | |
| 328 | + | ### The Projects page | |
| 323 | 329 | ||
| 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: | |
| 325 | 331 | ||
| 326 | 332 | - **Find a project** matches every word you type in a project's name, its | |
| 327 | 333 | address or its description. Press <kbd>/</kbd> anywhere on the page to | |
| 353 | 359 | [Explore](https://g1t.sh/explore), public projects from all of g1t, is in | |
| 354 | 360 | the top bar, beside **Docs**. | |
| 355 | 361 | ||
| 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: | |
| 363 | 378 | **Settings** slides over to how the workspace is set up and connected, and | |
| 364 | 379 | the row at the top, **‹ Settings**, slides back: | |
| 365 | 380 | ||
| 366 | 381 | | Settings | Who | | | |
| 367 | 382 | | --- | --- | --- | | |
| 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). | | |
| 369 | 384 | | **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. | | |
| 370 | 385 | | **Access tokens** | Members | The workspace's own tokens. Owners create and delete them. | | |
| 371 | 386 | | **Guardrails** | Members | What agents may do and spend across the workspace. Owners change them. | | |
| 396 | 411 | ||
| 397 | 412 | - **Pinned**: the projects you pinned, in your order, up to eight a | |
| 398 | 413 | 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 | |
| 400 | 415 | it, or hold <kbd>Alt</kbd> and press the up or down arrow. | |
| 401 | 416 | - **Recent**: the projects you opened last that you have not pinned, up to | |
| 402 | 417 | 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. | |
| 404 | 419 | ||
| 405 | 420 | Pins and recent projects are yours: nobody else sees them, and each | |
| 406 | 421 | workspace has its own. ⌘K finds any project in the workspace, pinned or not. |
| 510 | 510 | | --- | --- | --- | --- | | |
| 511 | 511 | | [`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` | | |
| 512 | 512 | | [`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` | | |
| 514 | 514 | | [`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` | | |
| 515 | 515 | | [`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` | | |
| 516 | 516 | | [`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` | | |
| 532 | 532 | ||
| 533 | 533 | | Action | What it does | Required | Scope | | |
| 534 | 534 | | --- | --- | --- | --- | | |
| 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` | | |
| 535 | 536 | | [`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` | | |
| 537 | 538 | | [`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` | | |
| 538 | 539 | | [`list_invites`](/reference/api/invites/list-workspace-invites/) | A workspace's invites. Owners only. | `workspace` | `workspace:read` | | |
| 539 | 540 | | [`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` | |
Binary or large file; its contents are not shown.
| 51 | 51 | y={p.y * 10 + 1} | |
| 52 | 52 | width="8" | |
| 53 | 53 | height="8" | |
| 54 | − | fill={p.one ? 'var(--g1t-merged)' : 'currentColor'} | |
| 54 | + | fill={p.one ? 'var(--g1t-accent)' : 'currentColor'} | |
| 55 | 55 | /> | |
| 56 | 56 | ))} | |
| 57 | 57 | </svg> | |
| 96 | 96 | --scalar-color-2: var(--g1t-muted); | |
| 97 | 97 | --scalar-color-3: var(--g1t-faint); | |
| 98 | 98 | --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); | |
| 100 | 100 | --scalar-button-1: var(--g1t-fg); | |
| 101 | 101 | --scalar-button-1-color: var(--g1t-bg); | |
| 102 | 102 | --scalar-button-1-hover: #ffffff; | |
| 103 | − | --scalar-color-green: var(--g1t-accent); | |
| 103 | + | --scalar-color-green: var(--g1t-success); | |
| 104 | 104 | --scalar-color-blue: var(--g1t-info); | |
| 105 | 105 | --scalar-color-red: var(--g1t-danger); | |
| 106 | 106 | --scalar-color-orange: var(--g1t-warn); |
| 9 | 9 | --sl-font-mono: var(--g1t-font-mono); | |
| 10 | 10 | ||
| 11 | 11 | /* 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); | |
| 14 | 14 | --sl-color-accent-high: #d9d1ff; | |
| 15 | 15 | ||
| 16 | 16 | --sl-color-white: var(--g1t-fg); | |
| 130 | 130 | background: var(--g1t-surface); | |
| 131 | 131 | color: var(--g1t-fg); | |
| 132 | 132 | font-weight: 500; | |
| 133 | − | box-shadow: inset 2px 0 0 var(--g1t-merged); | |
| 133 | + | box-shadow: inset 2px 0 0 var(--g1t-accent); | |
| 134 | 134 | } | |
| 135 | 135 | ||
| 136 | 136 | /* --- Content -------------------------------------------------------------- */ | |
| 178 | 178 | } | |
| 179 | 179 | .sl-markdown-content a:not(.card, .sl-link-button, .sl-link-card) { | |
| 180 | 180 | 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); | |
| 182 | 182 | text-underline-offset: 0.2em; | |
| 183 | 183 | } | |
| 184 | 184 | .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); | |
| 186 | 186 | } | |
| 187 | 187 | .sl-markdown-content :not(pre) > code { | |
| 188 | 188 | border-radius: 0.3rem; | |
| 251 | 251 | background: color-mix(in srgb, var(--g1t-info) 7%, var(--g1t-bg)); | |
| 252 | 252 | } | |
| 253 | 253 | .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)); | |
| 256 | 256 | } | |
| 257 | 257 | .starlight-aside--caution { | |
| 258 | 258 | border-color: var(--g1t-warn); | |
| 346 | 346 | color: var(--g1t-fg); | |
| 347 | 347 | } | |
| 348 | 348 | .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); | |
| 351 | 351 | border: 0; | |
| 352 | 352 | border-radius: 0.5rem; | |
| 353 | 353 | padding: 0.3rem; | |
| 413 | 413 | } | |
| 414 | 414 | .g1t-method-get, | |
| 415 | 415 | .g1t-method[data-method='get'] { | |
| 416 | − | --method: var(--g1t-accent); | |
| 416 | + | --method: var(--g1t-success); | |
| 417 | 417 | } | |
| 418 | 418 | .g1t-method-post, | |
| 419 | 419 | .g1t-method[data-method='post'] { |
| 20 | 20 | --color-faint: var(--g1t-faint); | |
| 21 | 21 | ||
| 22 | 22 | --color-accent: var(--g1t-accent); | |
| 23 | + | --color-accent-hover: var(--g1t-accent-hover); | |
| 23 | 24 | --color-accent-dim: var(--g1t-accent-dim); | |
| 24 | 25 | ||
| 26 | + | --color-success: var(--g1t-success); | |
| 27 | + | --color-success-dim: var(--g1t-success-dim); | |
| 28 | + | ||
| 25 | 29 | --color-info: var(--g1t-info); | |
| 26 | 30 | --color-merged: var(--g1t-merged); | |
| 27 | 31 | --color-warn: var(--g1t-warn); | |
| 37 | 41 | } | |
| 38 | 42 | ||
| 39 | 43 | ::selection { | |
| 40 | − | background: color-mix(in srgb, var(--color-merged) 35%, transparent); | |
| 44 | + | background: color-mix(in srgb, var(--color-accent) 35%, transparent); | |
| 41 | 45 | } | |
| 42 | 46 | ||
| 43 | 47 | @layer base { | |
| 44 | 48 | :focus-visible { | |
| 45 | − | outline: 2px solid var(--color-merged); | |
| 49 | + | outline: 2px solid var(--color-accent); | |
| 46 | 50 | outline-offset: 2px; | |
| 47 | 51 | } | |
| 48 | 52 | ||
| 57 | 61 | } | |
| 58 | 62 | ||
| 59 | 63 | :root { | |
| 60 | − | accent-color: var(--color-merged); | |
| 64 | + | accent-color: var(--color-accent); | |
| 61 | 65 | } | |
| 62 | 66 | ||
| 63 | 67 | /* Figures line up in columns of money. */ |
| 185 | 185 | ); | |
| 186 | 186 | } | |
| 187 | 187 | 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"}`}> | |
| 189 | 189 | <h2 className="font-semibold tracking-tight">{title}</h2> | |
| 190 | 190 | <div className="mt-3">{body}</div> | |
| 191 | 191 | <form method="post" action={`${pathname}#${RESULT_ANCHOR[review.intent] ?? "top"}`} className="mt-4 flex flex-wrap items-center gap-2"> | |
| 462 | 462 | /** A radio drawn as one button of a segmented row; still a plain radio without CSS. */ | |
| 463 | 463 | function Segment({ name, value, checked, children }: { name: string; value: string; checked: boolean; children: ReactNode }) { | |
| 464 | 464 | 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"> | |
| 466 | 466 | <input type="radio" name={name} value={value} defaultChecked={checked} className="sr-only" /> | |
| 467 | 467 | {children} | |
| 468 | 468 | </label> | |
| 534 | 534 | {CREDIT_KINDS.map((option) => ( | |
| 535 | 535 | <label | |
| 536 | 536 | 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" | |
| 538 | 538 | > | |
| 539 | 539 | <input type="radio" name="kind" value={option.value} defaultChecked={kind === option.value} className="mt-0.5" required /> | |
| 540 | 540 | <span> | |
| 633 | 633 | <Badge tone="lavender">{kindLabel(grant.kind)}</Badge> | |
| 634 | 634 | <Badge tone={state.tone}>{state.label}</Badge> | |
| 635 | 635 | {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"> | |
| 637 | 637 | {grant.workspace} | |
| 638 | 638 | </Link> | |
| 639 | 639 | )} | |
| 859 | 859 | {entry.creditKind ? `Credit · ${kindLabel(entry.creditKind)}` : (ENTRY_KIND[entry.kind] ?? entry.kind)} | |
| 860 | 860 | </Badge> | |
| 861 | 861 | {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"> | |
| 863 | 863 | {entry.workspace} | |
| 864 | 864 | </Link> | |
| 865 | 865 | )} | |
| 882 | 882 | .join(" · ")} | |
| 883 | 883 | </p> | |
| 884 | 884 | </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"}`}> | |
| 886 | 886 | {usd(entry.amountMicros, { signed: true })} | |
| 887 | 887 | </td> | |
| 888 | 888 | </tr> | |
| 908 | 908 | ) : ( | |
| 909 | 909 | <ol className="space-y-3"> | |
| 910 | 910 | {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"> | |
| 912 | 912 | <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> | |
| 914 | 914 | <span className="text-xs text-faint"> | |
| 915 | 915 | <When at={entry.createdAt} time /> | |
| 916 | 916 | </span> |
| 9 | 9 | import { usd } from "~/lib/money"; | |
| 10 | 10 | ||
| 11 | 11 | /** 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)"; | |
| 13 | 13 | const COST = "#7a7a84"; | |
| 14 | 14 | ||
| 15 | 15 | function Legend() { | |
| 131 | 131 | <td className="py-1.5 pr-3 text-right">{usd(month.chargedMicros)}</td> | |
| 132 | 132 | <td className="py-1.5 pr-3 text-right">{usd(month.plansMicros ?? 0)}</td> | |
| 133 | 133 | <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> | |
| 135 | 135 | <td className={`py-1.5 pr-3 text-right ${margin.micros < 0 ? "text-danger" : "text-fg-soft"}`}> | |
| 136 | 136 | {usd(margin.micros)} | |
| 137 | 137 | {margin.percent != null && <span className="text-faint"> {margin.percent}%</span>} |
| 12 | 12 | ||
| 13 | 13 | export function chip(active: boolean) { | |
| 14 | 14 | 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" | |
| 16 | 16 | }`; | |
| 17 | 17 | } | |
| 18 | 18 | ||
| 62 | 62 | key={tab.page} | |
| 63 | 63 | to={costsHref(tab.page, range)} | |
| 64 | 64 | 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"}`} | |
| 66 | 66 | > | |
| 67 | 67 | {tab.title} | |
| 68 | 68 | </Link> |
| 8 | 8 | import { type DayFigures, marginPercent, percentLabel } from "~/lib/costs"; | |
| 9 | 9 | import { usd } from "~/lib/money"; | |
| 10 | 10 | ||
| 11 | − | const CHARGED = "var(--g1t-merged)"; | |
| 11 | + | const CHARGED = "var(--g1t-accent)"; | |
| 12 | 12 | const COST = "#7a7a84"; | |
| 13 | 13 | const LOSS = "var(--g1t-danger)"; | |
| 14 | 14 |
| 49 | 49 | ||
| 50 | 50 | /** One timer: a label, a figure, and a line under it. */ | |
| 51 | 51 | 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"; | |
| 53 | 53 | return ( | |
| 54 | 54 | <div className="min-w-0 px-4 py-3"> | |
| 55 | 55 | <p className="text-xs text-muted">{label}</p> |
| 29 | 29 | y={p.y * 10 + 1} | |
| 30 | 30 | width="8" | |
| 31 | 31 | height="8" | |
| 32 | − | fill={p.one ? "var(--g1t-merged)" : "currentColor"} | |
| 32 | + | fill={p.one ? "var(--g1t-accent)" : "currentColor"} | |
| 33 | 33 | /> | |
| 34 | 34 | )); | |
| 35 | 35 | } | |
| 55 | 55 | <Pixels of={WORD} /> | |
| 56 | 56 | </svg> | |
| 57 | 57 | </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"> | |
| 59 | 59 | sudo | |
| 60 | 60 | </span> | |
| 61 | 61 | </span> |
| 169 | 169 | return ( | |
| 170 | 170 | <ol className="mt-4 space-y-3"> | |
| 171 | 171 | {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"> | |
| 173 | 173 | <p className="text-sm break-words whitespace-pre-line text-fg-soft">{note.text}</p> | |
| 174 | 174 | <p className="mt-1 flex flex-wrap items-center gap-x-2 text-xs text-faint"> | |
| 175 | 175 | <StaffName email={note.by} me={me} /> | |
| 200 | 200 | function StripeLink({ url, children }: { url: string | null; children: ReactNode }) { | |
| 201 | 201 | if (!url || !url.startsWith("https://")) return null; | |
| 202 | 202 | 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"> | |
| 204 | 204 | {children} | |
| 205 | 205 | </a> | |
| 206 | 206 | ); |
| 92 | 92 | return ( | |
| 93 | 93 | <span | |
| 94 | 94 | 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" | |
| 96 | 96 | > | |
| 97 | 97 | {count > 99 ? "99+" : count} | |
| 98 | 98 | <span className="sr-only"> {label}</span> | |
| 127 | 127 | current ? "bg-raised font-medium text-fg" : "text-muted hover:bg-raised/60 hover:text-fg" | |
| 128 | 128 | }`} | |
| 129 | 129 | > | |
| 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"}`} /> | |
| 131 | 131 | <span className="min-w-0 grow truncate">{item.label}</span> | |
| 132 | 132 | <CountPill count={countFor([item], counts)} label={countLabel(item)} /> | |
| 133 | 133 | {item.soon && <SoonPill />} | |
| 145 | 145 | aria-current={current ? "page" : undefined} | |
| 146 | 146 | className={`relative flex h-8 items-center gap-2 rounded-md pr-2 pl-[2.375rem] text-[0.8125rem] transition-colors ${ | |
| 147 | 147 | 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" | |
| 149 | 149 | : item.soon | |
| 150 | 150 | ? "text-faint hover:bg-raised/60 hover:text-muted" | |
| 151 | 151 | : "text-muted hover:bg-raised/60 hover:text-fg" | |
| 240 | 240 | <div className="border-t border-line bg-raised/40 px-4 py-3"> | |
| 241 | 241 | {email && ( | |
| 242 | 242 | <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" /> | |
| 244 | 244 | <span className="min-w-0 truncate font-mono text-xs text-fg-soft">{email}</span> | |
| 245 | 245 | </p> | |
| 246 | 246 | )} |
| 34 | 34 | } | |
| 35 | 35 | ||
| 36 | 36 | 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"; | |
| 38 | 38 | ||
| 39 | 39 | /** Tells password managers that a field is not a login. */ | |
| 40 | 40 | const NOT_A_CREDENTIAL = { | |
| 64 | 64 | ||
| 65 | 65 | const BUTTON_VARIANTS: Record<Variant, string> = { | |
| 66 | 66 | 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", | |
| 68 | 68 | quiet: "border border-line text-fg/80 hover:border-line-strong hover:bg-surface hover:text-fg", | |
| 69 | 69 | danger: "border border-danger/40 text-danger hover:border-danger/70 hover:bg-danger/10", | |
| 70 | 70 | }; | |
| 117 | 117 | } | |
| 118 | 118 | ||
| 119 | 119 | 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" }, | |
| 121 | 121 | error: { icon: AlertTriangle, className: "border-danger/30 bg-danger/8 text-danger" }, | |
| 122 | 122 | 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" }, | |
| 124 | 124 | } as const; | |
| 125 | 125 | ||
| 126 | 126 | export function Notice({ tone, children }: { tone: keyof typeof NOTICE; children: ReactNode }) { | |
| 137 | 137 | export function Badge({ tone = "plain", children }: { tone?: "plain" | "lavender" | "mint" | "warn" | "danger" | "info"; children: ReactNode }) { | |
| 138 | 138 | const tones = { | |
| 139 | 139 | 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", | |
| 142 | 142 | warn: "border-warn/35 bg-warn/10 text-warn", | |
| 143 | 143 | danger: "border-danger/35 bg-danger/10 text-danger", | |
| 144 | 144 | info: "border-info/35 bg-info/10 text-info", | |
| 197 | 197 | } | |
| 198 | 198 | ||
| 199 | 199 | const STATE = { | |
| 200 | − | ok: { label: "OK", fill: "var(--g1t-accent)", text: "text-accent" }, | |
| 200 | + | ok: { label: "OK", fill: "var(--g1t-success)", text: "text-success" }, | |
| 201 | 201 | warning: { label: "Warning", fill: "var(--g1t-warn)", text: "text-warn" }, | |
| 202 | 202 | stopped: { label: "Stopped", fill: "var(--g1t-danger)", text: "text-danger" }, | |
| 203 | 203 | } as const; | |
| 236 | 236 | ||
| 237 | 237 | /** A label and a figure, for the strip of totals over a page. */ | |
| 238 | 238 | 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"; | |
| 240 | 240 | return ( | |
| 241 | 241 | <div className="rounded-lg border border-line bg-surface px-4 py-3"> | |
| 242 | 242 | <p className="text-xs text-muted">{label}</p> |
| 72 | 72 | <ul className="list-disc space-y-1 pl-5 text-sm text-muted"> | |
| 73 | 73 | <li> | |
| 74 | 74 | 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"> | |
| 76 | 76 | Reach out | |
| 77 | 77 | </Link> | |
| 78 | 78 | . Stripe Radar's early fraud warnings are not in sudo yet. |
| 102 | 102 | {before && ( | |
| 103 | 103 | <p className="mt-4 text-xs text-faint"> | |
| 104 | 104 | 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"> | |
| 106 | 106 | Back to the newest | |
| 107 | 107 | </Link> | |
| 108 | 108 | </p> | |
| 130 | 130 | </p> | |
| 131 | 131 | <div className="min-w-0 grow"> | |
| 132 | 132 | <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> | |
| 134 | 134 | {path && ( | |
| 135 | 135 | <Link to={path} className="inline-flex items-center gap-0.5 text-fg-soft hover:text-fg hover:underline hover:underline-offset-4"> | |
| 136 | 136 | <span className={entry.account.startsWith("ws_") ? "font-mono text-xs" : ""}>{accountName(entry.account, nameMap)}</span> |
| 211 | 211 | </Field> | |
| 212 | 212 | <div className="flex flex-col justify-end gap-2"> | |
| 213 | 213 | <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 | |
| 215 | 215 | </label> | |
| 216 | 216 | <div className="flex gap-2"> | |
| 217 | 217 | <Button type="submit">Save mapping</Button> | |
| 233 | 233 | <input type="hidden" name="intent" value="settings" /> | |
| 234 | 234 | <Field label="Apply small moves on their own"> | |
| 235 | 235 | <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 | |
| 237 | 237 | </label> | |
| 238 | 238 | </Field> | |
| 239 | 239 | <Field label="Up to, either way, %"> | |
| 259 | 259 | </Field> | |
| 260 | 260 | <Field label="Card fee on AI credit bought by card"> | |
| 261 | 261 | <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 | |
| 263 | 263 | </label> | |
| 264 | 264 | </Field> | |
| 265 | 265 | <div className="sm:col-span-2 lg:col-span-4"> | |
| 291 | 291 | </div> | |
| 292 | 292 | </div> | |
| 293 | 293 | <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"}{" "} | |
| 295 | 295 | <span className="text-faint">({percentLabel(p.changePercent, { signed: true })})</span> | |
| 296 | 296 | </p> | |
| 297 | 297 | <p className="mt-1 text-xs text-muted">{p.reason}</p> |
| 169 | 169 | ||
| 170 | 170 | function CapMeter({ label, used, cap, hint }: { label: string; used: number; cap: number; hint: ReactNode }) { | |
| 171 | 171 | 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"; | |
| 173 | 173 | return ( | |
| 174 | 174 | <div className="rounded-md border border-line px-4 py-3"> | |
| 175 | 175 | <div className="flex flex-wrap items-baseline justify-between gap-2"> |
| 293 | 293 | function StripeLink({ url }: { url: string | null }) { | |
| 294 | 294 | if (!url || !url.startsWith("https://")) return <span className="text-faint">—</span>; | |
| 295 | 295 | 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"> | |
| 297 | 297 | Stripe | |
| 298 | 298 | <ExternalLink size={12} /> | |
| 299 | 299 | </a> |
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
This change is too large to show in full.