Skip to content

Commit

g1t is described as the product it is in alpha, and its shell takes the new shape: the README, the docs home and docs/PLAN.md label every feature Live, Preview or Coming; a floating dock with the g1t mark, Today, Chat, Notifications, Agents, Code, Artifacts, your pinned apps and the Apps launcher, with People, Workspace and the account menu (now holding help) at its foot; the workspace's logo and switcher heading a flat sidebar that folds away with Ctrl B, or leading the header's breadcrumbs where there is none; the page in a rounded panel with a full-width header; Today as the front page, with first-time acceptance of agents' pull requests, what needs you and what's at stake, spend today and one item to start with, every number from a service; Notifications in place of the Inbox at /notifications; a bottom bar and More sheet on phones; and sign-in, sign-up, two-factor, reset, invites and choosing a workspace on standalone pages with no app around them.

syntaqxcommitted Parent46b48beBrowse files
92 files+1926−9890/92 viewed
+88−107
11 # g1t
22
3−One open-source workspace where a team and its agents talk, work, write
4−things down and ship. No separate chat app, wiki or forge to stitch
5−together. It runs on Cloudflare Workers and Artifacts.
3+g1t is where a company's people and agents work together. People and agents
4+are members of the same workspace: they talk in channels and direct
5+messages and sit on the same teams. You hand an agent work, it does it, and
6+it comes back with something you can act on: a pull request to merge, a
7+document, a reply to approve. Today says what happened and what is waiting
8+on you. Code and Deployments are built in, because what agents make has to
9+be reviewed, tested and shipped. Everything specific to your business will
10+come from extensions and integrations.
11+
12+g1t is MIT-licensed. Use it on [g1t.sh](https://g1t.sh), or run it
13+yourself. It runs on Cloudflare Workers, and the core runs in Docker.
614
7−- **Chat.** Channels, direct messages and threads, live. People and agents
8− are members alike: DM an agent, or mention it in a thread, and it answers
9− there. Chat is included on every plan, with no seats and no history
10− cutoff.
11−- **Agents.** A workspace's own agents, each with a role, a job, a
12− personality, limits on which models Auto may route it to (including the
13− workspace's own providers) and a budget. Start from templates: planner,
14− implementer, reviewer, triage, documenter, release manager, on-call.
15−- **Docs** (coming soon). Specs, runbooks and decisions, written together,
16− read by agents and kept current by them.
17−- **Code.** Git over HTTPS, issues, pull requests and reviews. Assign an
18− issue to g1t or connect any coding agent over MCP; hand g1t an outcome and
19− a planner splits it into issues that agents take up as they unblock.
20− Checks run by g1t in clean sandboxes, workflows from `.g1t/workflows`, a
21− merge queue that tests changes together, why-blame from any line to the
22− session that wrote it, and a preview of every pull request on `g1t.page`.
23−- **Rails.** Budgets per workspace, agent and task; agents act with the
24− asker's access and answer only with what their audience may see;
25− approvals for merges and production deploys; an audit log on every
26− workspace.
27−- **Open and fair.** MIT licensed and self-hostable (an early Docker Compose
28− version of the core forge, in `deploy/self-host`). People chat free and
29− the forge is free; agents pay the model's price plus a flat agent rate,
30− and other compute is what it costs plus 20%, never per seat.
15+**g1t is in alpha.** Accounts are by invite. Each feature below is marked:
3116
32−The plan for the workspace is [docs/WORKSPACE.md](docs/WORKSPACE.md).
17+- **Live**: works on g1t.sh for every workspace.
18+- **Preview**: works on g1t.sh, but is limited to some workspaces or
19+ unfinished.
20+- **Coming**: planned, not built.
3321
34−g1t is made by Flagon, Inc. It is also an entry in Cloudflare's **Build the
35−Next-Gen Git Platform** competition, which asks what a git platform looks
36−like when many of the people using it are agents
37−([the challenge](https://blog.cloudflare.com/next-git-platform-on-cloudflare/),
38−[rules and dates](https://www.cloudflare.com/git-competition/)).
39−[docs/PLAN.md](docs/PLAN.md) says how g1t answers the brief and what is
40−built so far.
22+The plan behind this is [docs/PLAN.md](docs/PLAN.md).
4123
4224 ## Where things are
4325
4426 - Site: <https://g1t.sh>
4527 - Docs: <https://docs.g1t.sh>
4628 - API: <https://api.g1t.sh> · MCP: <https://mcp.g1t.sh>
47−- Plan and design: [docs/PLAN.md](docs/PLAN.md)
48−- Demo walk-through: [docs/DEMO.md](docs/DEMO.md)
29+- Plan: [docs/PLAN.md](docs/PLAN.md)
30+- Limits you can hit today: [docs.g1t.sh/about/limitations](https://docs.g1t.sh/about/limitations/)
4931
50−## Status
32+## What's in it
5133
52−Working today:
34+### The workspace
5335
54−- Accounts with email verification and password reset. Applications sign
55− in through the browser with OAuth 2.1, so connecting an MCP client needs
56− no pasted token; tools without a browser use a device code.
57−- Workspaces that own repositories, with members and roles. Every account
58− creates one before anything else, and usernames and workspaces share one
59− namespace.
60−- Access tokens that belong to a workspace instead of a person, for CI and
61− integrations, so nothing needs a shared service account.
62−- Public and private repositories, and git over HTTPS, including creating a
63− repository by pushing to it.
64−- Issues with labels and comments; a description can say what done means,
65− under a Definition of done.
66−- Pull requests with a diff and a recorded agent session: in a
67− fork of their own, which is how agents work, or from a branch pushed to
68− the repository. Several can be made for one issue.
69−- Checks: the repository's workflows run on every pull request, a
70− person's or an agent's, and report a check each. The default branch
71− names the required checks a merge needs; an agent whose change fails a
72− check is sent back with the failing jobs' logs. A repository with no
73− workflows gets a starter CI workflow in one click.
74−- Review: comments on lines of a change, and approve or request-changes
75− verdicts, from people and from agents.
76−- Overlap: each pull request shows which others in progress change the
77− same files, while the work is still going on.
78−- Catch-up: when `main` has moved under a pull request, g1t merges it in,
79− and g1t resolves any conflict.
80−- Reviews written by g1t, on request: line comments, a summary and
81− a verdict.
82−- Importing a public repository from any git host by its address, and
83− public or private repositories through g1t's GitHub App, imported once,
84− mirrored, or pushed back to GitHub.
85−- Merging: lands a pull request on `main`, closes its issue naming the pull
86− request that resolved it, and closes the others for that issue as
87− superseded. When `main` has moved, the pull request is brought up to date
88− first, or refused where the repository requires that, so no commit is
89− lost.
90−- g1t agents: g1t's own agents working on an issue in sandboxes on
91− Cloudflare Containers, seeing each pull request through checks, an
92− agent's review, revisions and catch-up.
93−- Outcomes: a brief planned into issues with dependencies, which agents
94− take up as their dependencies land.
95−- The merge queue: pull requests tested together with what is ahead of
96− them before they land, with failures sent back to the agent that wrote
97− them. Required approvals and checks per repository.
98−- Checks in detail on every pull request, and conflicts worked out on
99− every push, before a merge is tried.
100−- Agents as records: every run with its live steps, cost and session, Stop
101− and Message, and memory at two levels (project and workspace) that
102− agents write and read.
103−- Projects with deployments on g1t.page: a preview for every pull request,
104− production on merge, custom domains.
105−- GitHub Actions workflows from `.g1t/workflows`, secrets and variables,
106− webhooks and integrations (Sentry, Datadog, Jira, Linear).
107−- Profiles, workspaces with display names, icons and renameable slugs.
108−- Usage billing with no seats: what it costs g1t plus a markup, a public
109− price book, usage limits and itemised invoices.
110−- A REST API, an OpenAPI document and an MCP server over the same operations.
111−- An event bus: every state change is published, logged and delivered to
112− subscribers.
36+| Feature | Status |
37+| --- | --- |
38+| The dock: Today, Chat, Notifications, Agents, Code, Artifacts and your pinned apps, with People, Workspace and your account at its foot; a bottom bar on phones | Live |
39+| Today: what agents finished, how much was accepted the first time, what's waiting on you, what was spent, and where to start | Preview |
40+| Chat: channels, direct messages and threads, live, with people and agents as members | Live |
41+| Notifications: mentions, reviews, approvals and alerts, with browser push | Live |
42+| People and teams: members, roles, nested teams, invites | Live |
43+| A directory of people and agents, profiles, org chart, teams with agent members | Coming |
44+| Artifacts: documents written together, live, by people and agents | Live |
45+| Slides, designs and dashboards as artifacts | Coming |
46+| Scratchpads: tools, reports and agents anyone can build on a copy of the data, promoted after review | Coming |
47+| Desktop app (Tauri, with a runner inside) and phone app (Expo) | Coming |
11348
114−Not built yet: what the Soon pages in each project's menu describe. Git
115−over SSH waits on inbound TCP on port 22, which on Cloudflare
116−means Workers inbound TCP, a beta g1t has applied for and is waiting on.
117−Use HTTPS until then. See the build order in the plan.
49+### Agents
11850
51+| Feature | Status |
52+| --- | --- |
53+| Agents hired into roles from templates, each with responsibilities, a voice, model limits and a budget | Live |
54+| `@g1t`, the orchestrator every workspace has, which hands work to the right agent with `hand_off` | Live |
55+| Your own model providers' keys (14 providers), and the AI Gateway | Live |
56+| g1t's hosted models (open to invited workspaces during the alpha) | Preview |
57+| Budgets per workspace, agent and task; guardrails; agents act with the asker's access | Live |
58+| Memory, sessions with live steps and cost, routines on a schedule | Live |
59+| An agent catalog of specialists, and foundational skills (documents, research, data, code, communication, files) | Coming |
60+| Agents on runners anywhere (g1t's, yours, your desktop), with sessions that persist between tasks | Coming |
61+| Agents answering in Slack and Teams | Coming |
62+
63+### Code and Deployments
64+
65+| Feature | Status |
66+| --- | --- |
67+| Public and private repositories, git over HTTPS, creating a repository by pushing to it | Live |
68+| Issues, pull requests, line comments, reviews from people and agents | Live |
69+| Required checks, rulesets, CODEOWNERS, the merge queue | Live |
70+| Workflows from `.g1t/workflows` on g1t's runners or self-hosted ones (`crates/runner`) | Live |
71+| Assign an issue to g1t: a pull request, checks, a review and revisions until it passes (invited workspaces) | Preview |
72+| Any coding agent over MCP, recording its session onto its pull requests; why-blame | Live |
73+| Packages: npm, Cargo, Composer, Maven, NuGet, RubyGems, Go modules, container images | Live |
74+| Secret push protection, history scanning, dependency upgrade pull requests | Live |
75+| A preview of every pull request on `g1t.page`, production on merge, custom domains | Live |
76+| GitHub import, mirroring and pushing back | Live |
77+| A copy of the data for every preview, instant rollback, container apps kept warm | Coming |
78+| Git over SSH (waits on inbound TCP on Workers; use HTTPS) | Coming |
79+
80+### Extend and run
81+
82+| Feature | Status |
83+| --- | --- |
84+| Integrations: the GitHub App, Sentry, Datadog, Jira, Linear, alerts | Live |
85+| REST API, OpenAPI, MCP server, webhooks, an event bus | Live |
86+| Usage billing with no seats, a public price book, spend limits, itemised invoices | Live |
87+| Marketplace of extensions, shared from public repositories; Drive, Gmail, Calendar and Slack integrations; Mail | Coming |
88+| Single sign-on (SAML, OIDC) and SCIM per workspace | Coming |
89+| Storage: databases and buckets per team, with copies of production for anything unreviewed | Coming |
90+| Self-hosting: the core forge in Docker Compose (agents, deployments and context search off) | Preview |
91+
11992 ## Try it
12093
12194 ```sh
122−# 1. Create an account and a workspace at https://g1t.sh/register.
95+# 1. Create an account and a workspace at https://g1t.sh/register (you need an invite).
12396
12497 # 2. Connect Claude Code, then run /mcp in it to sign in through your browser.
12598 claude mcp add --transport http g1t https://mcp.g1t.sh
153126 | `services/pages` | Serves every app deployed on `g1t.page`, and custom domains. | TypeScript |
154127 | `services/models` | The model proxy at `models.g1t.sh`. | TypeScript |
155128 | `services/context` | The context hub: catalog, search and scorecards. | TypeScript |
129+| `services/chat` | Channels, direct messages, threads and their live sockets. | TypeScript |
130+| `services/agents` | The workspace's agents: definitions, templates, desks, memory and runs. | TypeScript |
131+| `services/docs` | Artifacts: documents, their spaces, sharing and live editing. | TypeScript |
132+| `services/notify` | Notifications: each person's feed, live counts and browser push. | TypeScript |
133+| `services/packages` | The package registries and container images. | Rust |
156134 | `services/og` | Social cards at `og.g1t.sh`: a PNG per page, showing only what anyone may see. | TypeScript |
157135 | `apps/status` | The status page at `status.g1t.sh`. | TypeScript |
158136 | `apps/sudo` | g1t's own staff console. | TypeScript |
159137 | `crates/runner` | The program inside a sandbox: runs an agent, a workflow job or a merge queue build, and reports back. | Rust |
160138 | `crates/contracts` | Types and service interfaces for the Rust services. | Rust |
161139 | `crates/kit` | Plumbing shared by Rust services on Workers. | Rust |
140+| `crates/g1t` | The `g1t` command line. | Rust |
141+| `crates/rules` | The rules engine for rulesets on branches and tags. | Rust |
142+| `crates/blobstore` | Object storage: R2 on Cloudflare, any S3-compatible store self-hosted. | Rust |
162143 | `crates/actions` | Reads workflows and evaluates their expressions. | Rust |
163144 | `crates/scan` | Secret and lockfile scanning, shared by services. | Rust |
164145 | `crates/secrets` | Secrets at rest and signatures. | Rust |
165−| `crates/sshd` | Git over SSH, bridged to Artifacts. Not deployed yet. | Rust |
146+| `crates/sshd` | Git over SSH, bridged to Cloudflare Artifacts. Not deployed yet. | Rust |
166147 | `packages/contracts` | The same interfaces for TypeScript callers. | TypeScript |
167148 | `packages/theme` | Design tokens and the logo, shared by the site and the docs. | CSS |
168149 | `deploy` | `stack.jsonc`, every deployable part and its resources; `self-host`, the Docker Compose version. | JSON, Docker Compose |
194175
195176 ### On Cloudflare
196177
197−You need a Cloudflare account on the Workers Paid plan (Artifacts requires
198−it), Node 22.22 or newer (`engines` in `package.json`; g1t is built on
178+You need a Cloudflare account on the Workers Paid plan (Cloudflare
179+Artifacts, where repositories are stored, requires it), Node 22.22 or newer (`engines` in `package.json`; g1t is built on
199180 Node 24), Rust with the `wasm32-unknown-unknown` target, and
200181 Docker to build the sandbox image.
201182
207188 Then, once:
208189
209190 1. Create the resources each part needs: D1 databases, queues, KV
210− namespaces, R2 buckets and the Artifacts namespace (`npx wrangler d1
191+ namespaces, R2 buckets and the Cloudflare Artifacts namespace (`npx wrangler d1
211192 create <name>`, `npx wrangler queues create <name>`, and so on), and set
212193 each part's secrets. `deploy/stack.jsonc` lists them all.
213194 2. Put your own `account_id`, database ids and hostnames in each
+3−1
1212 // Pages that moved, so links already shared still arrive.
1313 redirects: {
1414 '/guides/g1t-agents/': '/guides/working-with-g1t/',
15+ '/guides/inbox/': '/guides/notifications/',
1516 },
1617 integrations: [
1718 starlight({
6768 items: [
6869 { label: 'Quickstart', slug: 'quickstart' },
6970 { label: 'How g1t works', slug: 'concepts/overview' },
70− { label: 'Your inbox', slug: 'guides/inbox' },
71+ { label: 'Today', slug: 'guides/today' },
72+ { label: 'Notifications', slug: 'guides/notifications' },
7173 { label: 'Search and Explore', slug: 'guides/search' },
7274 { label: 'Status and incidents', slug: 'guides/status' },
7375 ],
+45−0
1+---
2+// Where a feature stands in the alpha: Live (on g1t.sh for every
3+// workspace), Preview (on g1t.sh, but limited to some workspaces or
4+// unfinished) or Coming (planned, not built). Inline, in a table cell, a
5+// heading or a line.
6+interface Props {
7+ is: 'live' | 'preview' | 'coming';
8+}
9+
10+const { is } = Astro.props;
11+const LABEL = { live: 'Live', preview: 'Preview', coming: 'Coming' } as const;
12+---
13+
14+<span class:list={['status', 'not-content', is]}>{LABEL[is]}</span>
15+
16+<style>
17+ .status {
18+ display: inline-flex;
19+ align-items: center;
20+ padding: 0.05rem 0.5rem;
21+ border-radius: 999px;
22+ border: 1px solid transparent;
23+ font-family: var(--g1t-font-mono);
24+ font-size: 0.6875rem;
25+ font-weight: 500;
26+ letter-spacing: 0.04em;
27+ text-transform: uppercase;
28+ white-space: nowrap;
29+ vertical-align: middle;
30+ line-height: 1.5;
31+ }
32+ .live {
33+ color: var(--g1t-success);
34+ background: var(--g1t-success-low);
35+ }
36+ .preview {
37+ color: var(--g1t-accent);
38+ background: var(--g1t-accent-low);
39+ }
40+ .coming {
41+ color: var(--g1t-muted);
42+ background: var(--g1t-raised);
43+ border-color: var(--g1t-line-strong);
44+ }
45+</style>
+1−1
13261326
13271327 A run whose jobs wait shows **Waiting for review** at the top of its page,
13281328 with the environments, the jobs each holds, its reviewers and when its
1329−wait timer runs out. Reviewers are told in their [inbox](/guides/inbox/);
1329+wait timer runs out. Reviewers are told in their [notifications](/guides/notifications/);
13301330 on the run's page they choose **Approve and deploy** or **Reject**, with
13311331 room for a comment. One review covers every job of the run that names the
13321332 environment. A rejected job fails, and so does anything that needs it. The
+1−1
180180
181181 | | |
182182 | --- | --- |
183−| **Sees** | Chat, Artifacts, Agents and the inbox. The rail has no Code, and Home shows their channels, DMs, docs and inbox instead of projects. |
183+| **Sees** | Chat, Artifacts, Agents and Notifications. The dock has no Code, and Today shows what needs them in chat, from agents and in their notifications, without reviews. |
184184 | **Can't open** | Any repository, issue, pull request, check or deploy page. The workspace's base permission and team grants don't apply to them. |
185185 | **Still sees work reach them** | Cards about issues, pull requests and deploys appear in their channels as summaries: title, state and who is on it. Opening one asks for Code access. |
186186 | **Can ask agents** | Anything about the product. Agents explain how things work and what changed, but never show them source code. Owners can tighten this so agents answer only from the workspace's docs. |
+2−2
55
66 Every project has an **Agents** section, beside its pull requests: who is
77 working on it right now, what each agent did before, and what agents have
8−learned about the project. Across the workspace, **Agents** in the rail
8+learned about the project. Across the workspace, **Agents** in the dock
99 shows every agent, its [sessions](/guides/agent-sessions/) and what it
1010 costs, and **Memory** holds what agents know in every project.
1111
1717 | Teach every agent something about this code | **Agents → Memory** |
1818 | Teach every agent something true in every project | **Memory**, in the workspace's sidebar |
1919 | Review what agents, reviews and docs taught | **Agents → Memory**, or **Context → Memory** for the workspace |
20−| See every agent across the workspace, and its cost | **Agents** in the rail: the overview, then each agent's **Spend** |
20+| See every agent across the workspace, and its cost | **Agents** in the dock: the overview, then each agent's **Spend** |
2121
2222 ## Runs
2323
+2−2
4747
4848 | | |
4949 | --- | --- |
50−| **In every workspace** | On every surface: Chat, issues and pull requests, the inbox and MCP. It works on issues and pull requests as described in [g1t's agent](/guides/working-with-g1t/). |
50+| **In every workspace** | On every surface: Chat, issues and pull requests, Notifications and MCP. It works on issues and pull requests as described in [g1t's agent](/guides/working-with-g1t/). |
5151 | **Configurable** | Set its personality, model limits, budget and what it may do alone, like any agent. Its job is fixed, and you can add instructions to it. |
5252 | **Knows the team** <Soon /> | Every colleague's role, what they are working on and their budget, and which teams own what. |
5353 | **Delegates** | When a specialist's role fits, it [hands the work off](#hand-off) with a brief: here, if the specialist is in the conversation, or in a group message with you and them. At most two hand-offs per message. |
6464
6565 <Steps>
6666
67−1. Open **Agents** in the rail, then choose **New agent**, or go to
67+1. Open **Agents** in the dock, then choose **New agent**, or go to
6868 `g1t.sh/<workspace>/-/agents/new`.
6969 2. **Pick a role** from the templates, grouped by department, or choose
7070 **Start from nothing**. A role fills in every field below, and you can
+3−3
1212 private until you share it, and agents read only what the people they work
1313 for can read.
1414
15−Open **Artifacts** in the rail on the left, or go to
15+Open **Artifacts** in the dock on the left, or go to
1616 `g1t.sh/<workspace>/-/artifacts`. Artifacts is open to every member of a
1717 workspace, on every plan.
1818
7979
8080 1. Choose people, agents or teams in the workspace, and a role.
8181 2. Add a message if you want one, then press **Invite**. People you add get
82− an inbox item with the link.
82+ a notification with the link.
8383
8484 </Steps>
8585
117117 ### Asking for access
118118
119119 An artifact you can't open shows **You need access**. **Ask for access**
120−tells its owner and the people with full access (up to 20) in their inbox,
120+tells its owner and the people with full access (up to 20) in their notifications,
121121 with your message if you add one. You can ask once a day for each artifact.
122122
123123 ## Private
+8−8
130130 | Made from | [Settings → Invites](https://g1t.sh/settings/invites), your own | The workspace's **People** page, by its owners |
131131 | What it gives | One new account. It adds them to no workspace: the account gets a workspace of its own | An invitation to join that workspace, which they accept or decline |
132132 | Someone without an account | Makes their account with it | Makes their account with it too, while g1t is invite-only, then answers the invitation |
133−| Someone already on g1t | Nothing: they have an account | The invitation, in their inbox and by email |
133+| Someone already on g1t | Nothing: they have an account | The invitation, in their notifications and by email |
134134 | Exists | Only while g1t is invite-only | Always |
135135 | What its page and email say | "@syntaqx invited you to g1t" | "@syntaqx invited you to join Flagon, Inc. on g1t" |
136136
306306 ### Workspace invitations
307307
308308 A workspace invitation asks one account to join one workspace, with the
309−role chosen when it was sent. You hear of it in your inbox and by email,
309+role chosen when it was sent. You hear of it in your notifications and by email,
310310 and answer it at [g1t.sh/invitations](https://g1t.sh/invitations):
311311
312312 - **Accept** joins the workspace with that role, and takes you there.
313−- **Decline** joins nothing; whoever invited you is told in their inbox.
313+- **Decline** joins nothing; whoever invited you is told in their notifications.
314314
315315 An invitation works for 30 days, the same as an invite. Until it is
316316 answered, the workspace's owners see it under **Pending invitations** on
625625 (public repositories, read-only, and the workspace's own settings its
626626 permissions allow).
627627 - **No workspace**: your account and public repositories only, such as
628− a token that reads your inbox.
628+ a token that reads your notifications.
629629 5. Under **Permissions**, set each resource the token needs to a level.
630630 **Read only**, **Agent** and **CI** fill in a [preset](#presets);
631631 **Clear** sets everything back to no access.
638638 [requires approval](#a-workspaces-rules-for-tokens), and you are not one of
639639 its owners, the token is made **Pending approval**: it works at once, but
640640 reads public repositories only until an owner approves it. The owners hear
641−of it in their [inbox](/guides/inbox/), and you hear of their answer in
641+of it in their [notifications](/guides/notifications/), and you hear of their answer in
642642 yours. An owner's own token never waits.
643643
644644 ### Change or delete a token
882882 | `memory:write` | Save memory for the next agent |
883883 | `account:read` | Read your email addresses, invites, invitations, pinned projects and stars |
884884 | `account:write` | Change your email addresses, make invites, answer invitations, pin projects and star repositories |
885−| `notifications:read` | See your [inbox](/guides/inbox/), its threads, and what you subscribe to and watch |
885+| `notifications:read` | See your [notifications](/guides/notifications/), its threads, and what you subscribe to and watch |
886886 | `notifications:write` | Mark notifications read, done, saved or snoozed, subscribe to threads and watch repositories |
887887 | `workspace:read` | Read workspace settings, invites, integrations, model routes and [teams](/guides/teams/) |
888888 | `workspace:admin` | Create and delete workspaces, invite members, manage teams, connect integrations |
10491049
10501050 - **Waiting for approval.** Each pending token with its owner,
10511051 permissions, repositories and expiry. Add an optional note, then select
1052− **Approve** or **Deny**. Its owner hears of it in their inbox, with the
1052+ **Approve** or **Deny**. Its owner hears of it in their notifications, with the
10531053 note.
10541054 - **Tokens that can reach the workspace.** Every token made for it, and
10551055 every token of its members and outside collaborators made for all of
12761276
12771277 | | |
12781278 | --- | --- |
1279−| Your addresses, keys and profile | Removed: your email addresses, two-factor secret and recovery codes, GitHub link, picture, profile and security log, and your inbox and its settings. |
1279+| Your addresses, keys and profile | Removed: your email addresses, two-factor secret and recovery codes, GitHub link, picture, profile and security log, and your notifications and their settings. |
12801280 | What you wrote | Issues, pull requests, comments and reviews keep their place and their words, and show as written by `ghost`. You are taken off issues and pull requests you were assigned to or asked to review. Commits keep the name and address git recorded in them; those made with your [noreply address](#keeping-your-address-private) show as `ghost`. |
12811281 | Workspaces you made | Name `ghost` as their creator. |
12821282 | Statements, invoices and audit logs | Kept with your username, for the workspaces they belong to. |
+1−1
6060 ## Approvals settle everywhere
6161
6262 An approval is a button in the chat app message, a card in g1t and an item
63−in your inbox. Approving in any one of them settles all three.
63+in your notifications. Approving in any one of them settles all three.
6464
6565 ## Account linking
6666
+9−9
2222 no history cutoff. Agents' replies are charged to the agent that wrote
2323 them; see [what an agent costs](/guides/agents/#what-an-agent-costs).
2424
25−Open **Chat** in the rail on the left, or go to
25+Open **Chat** in the dock on the left, or go to
2626 `g1t.sh/<workspace>/-/chat`.
2727
2828 ## A conversation
374374
375375 When a card waits on you (a session at its cap, a draft issue you asked
376376 for), its pop-up has the card's buttons, and so does **Waiting on you in
377−chat** at the top of the inbox panel (the bell), for a day or until you
377+chat** at the top of the notifications panel (the bell), for a day or until you
378378 act. **Approve more** asks for the amount right there; **Stop** asks
379379 first. A browser notification has the buttons that need nothing typed,
380380 such as **Stop** and **Open**.
551551
552552 ### Set a status
553553
554−Open the menu on your avatar at the bottom of the rail (on a phone, the
555−workspace avatar at the top left) and choose **Set a status**. Give it an
554+Open the menu on your avatar at the foot of the dock (on a phone, **More**,
555+then **You and help**) and choose **Set a status**. Give it an
556556 emoji and a few words, or pick one:
557557
558558 | | Clears after |
600600 | Notify me about | You hear about |
601601 | --- | --- |
602602 | **DMs and mentions** (the default) | Direct messages, mentions, replies in your threads, agents waiting on you, and approvals. |
603−| **Everything** | Every message in conversations you're in, and everything in your inbox. |
603+| **Everything** | Every message in conversations you're in, and everything in your notifications. |
604604 | **Nothing** | No pop-ups or browser notifications. Counts still update. |
605605
606606 Under **For a workspace**, you can give one workspace a different level,
612612 From the menu on your avatar, **Pause notifications** for 30 minutes, an
613613 hour, or until tomorrow morning (9:00 your time). Until then nothing pops
614614 up and no browser notification is sent, whatever your settings; counts and
615−your inbox still update, and everyone sees your amber dot. **Resume
615+your notifications still update, and everyone sees your amber dot. **Resume
616616 notifications** ends it early.
617617
618−[Your inbox](/guides/inbox/) is separate: it keeps every item until you
618+[Notifications](/guides/notifications/) are separate: they keep every item until you
619619 deal with it, whatever these settings say.
620620
621621 ## Coming soon
627627 Pull requests, checks, deploys and approvals as cards in the channels
628628 linked to a project, with their buttons working in place. <Soon />
629629 </Card>
630− <Card title="Mentions in your inbox" icon="inbox">
631− Mentions and approvals as items in [your inbox](/guides/inbox/), so
630+ <Card title="Mentions in Notifications" icon="bell">
631+ Mentions and approvals as items in [Notifications](/guides/notifications/), so
632632 acting in either place settles both. <Soon />
633633 </Card>
634634 <Card title="Files" icon="paperclip">
+1−1
219219 - owners of optional sections too;
220220 - a draft once it is marked ready.
221221
222−The inbox tells them **acme/api#42 changes files you own**, or **acme/api#42
222+Notifications tell them **acme/api#42 changes files you own**, or **acme/api#42
223223 changes files @acme/backend owns**, with the reason `review_requested`.
224224 Webhooks get `pull.review_requested` with `data.code_owners` set to `true`.
225225
+0−279
1−---
2−title: Your inbox
3−description: What needs you, and what you follow, as it happens. One thread per issue, pull request, workflow or deployment, with why you were told. Choose what you hear of with subscriptions, watching and email settings.
4−---
5−
6−Your **inbox** tells you when something needs you, or when something
7−happens to work you answer for or follow: an agent is waiting on you,
8−someone asked you to review a pull request, checks failed on your pull
9−request, a deployment failed, someone mentioned you. You are never told
10−about what you did yourself.
11−
12−Open it from the bell in the top bar. The number on the bell is what is
13−unread. It is amber while something is waiting on you, red while a failure
14−is unread, and green otherwise.
15−
16−## Threads
17−
18−Your inbox holds one **thread** for each thing you were told about:
19−
20−- an issue
21−- a pull request
22−- a workflow on one branch
23−- a deployment: a project's production, or one pull request's preview
24−- a workflow run waiting for your review to deploy to an environment
25−
26−When something new happens on a thread, it comes back to the top of your
27−inbox, unread, even if you had marked it done. It is not added a second
28−time. A thread that is snoozed stays snoozed until its time.
29−
30−Each card shows the latest activity's title, why you were told (see
31−[reasons](#reasons)), and, when more than one thing has happened, how many,
32−such as **3 updates**. g1t keeps the last 10 activities of each thread.
33−
34−While a thread is unread it keeps the most urgent of what happened since
35−you last read it. A failure followed by a comment still shows as a failure
36−until you read it.
37−
38−## Reasons
39−
40−Every thread says why you were told of its latest activity. When you are
41−told of one thing for more than one reason, the first that applies in this
42−table is shown.
43−
44−| Reason | Shown as | Why you were told |
45−| --- | --- | --- |
46−| `agent` | agent waiting | An agent is waiting on you: it asked a question, or it stopped until a person steps in. |
47−| `review_requested` | review requested | Someone asked you, or a team you are in, to review a pull request; it changes files you own; or you are one of its reviewers. Also a workflow run waiting for you, as one of an [environment's reviewers](/guides/actions/#environments), to approve its deployment, and, for a workspace's owners, a member's token made for the workspace waiting for [approval](/guides/authentication/#a-workspaces-rules-for-tokens) (in the inbox only, never emailed). |
48−| `assign` | assigned | You were assigned, or you are an assignee. |
49−| `mention` | mentioned | Someone mentioned you with `@username`, or you were mentioned on it before. |
50−| `team_mention` | team mentioned | Someone mentioned a [team](/guides/teams/#mentions) you are in with `@workspace/team`, or a team you are in was mentioned on it before. |
51−| `ci_activity` | CI activity | A check, workflow or deployment on your work finished badly, or recovered. |
52−| `security_alert` | security alert | A new secret, code scanning or vulnerability alert on a repository you look after, a push of yours that push protection blocked, or a [bypass request](/guides/security/secret-protection/#delegated-bypass) to review or its answer. The workspace's owners hear of new alerts; watchers who chose **Security alerts** do too, if they can see findings. |
53−| `state_change` | state changed | It was closed, reopened or merged. |
54−| `author` | your work | You opened it, or you asked g1t for it. Also an owner's answer to your [token made for a workspace](/guides/authentication/#create-a-token) waiting for approval, or its revocation. |
55−| `comment` | commented | You commented on it. |
56−| `manual` | subscribed | You subscribed to it yourself. |
57−| `subscribed` | watching | You watch its repository. |
58−
59−## What lands there
60−
61−Each item comes from something that happened on g1t. Who is told depends on
62−what it was:
63−
64−| What happened | Who is told | Reason | Shown as |
65−| --- | --- | --- | --- |
66−| An agent asked a question of the agent on a pull request, or handed it work | The person the pull request belongs to, and its issue's author and assignees | `agent` | Needs you |
67−| g1t stopped on a pull request until a person steps in | The same people | `agent` | Needs you |
68−| Someone asked for reviews on a pull request, or opened one with reviewers | The reviewers asked | `review_requested` | Needs you |
69−| Someone asked a team to review a pull request | Everyone in the team and its child teams, or, with [review assignment](/guides/teams/#review-assignment), the people picked | `review_requested` | Needs you |
70−| A workflow run's jobs wait for an [environment's reviewers](/guides/actions/#environments) | Each reviewer, and everyone in a reviewing team ("Deploy is waiting for your review to deploy to production in acme/api"), but not whoever started the run when it may not approve it | `review_requested` | Needs you |
71−| A pull request changes files a [CODEOWNERS file](/guides/codeowners/) gives you or your team | The owners asked: "acme/api#42 changes files you own" | `review_requested` | Needs you |
72−| Someone assigned people to an issue or pull request, or opened one with assignees | The people newly assigned | `assign` | Info |
73−| Checks failed, or could not run, on a pull request | The person the pull request belongs to | `ci_activity` | Error |
74−| A workflow failed on a pull request | The person the pull request belongs to | `ci_activity` | Error |
75−| A workflow failed on a branch | Whoever pushed the commit it ran on | `ci_activity` | Error |
76−| A preview of a pull request failed to deploy | The person the pull request belongs to, and people watching deployments | `ci_activity`, or `subscribed` for watchers | Error |
77−| Production failed to deploy | Whoever pushed or started it, and people watching deployments | `ci_activity`, or `subscribed` for watchers | Error |
78−| A deployment went live | People watching deployments; after a failure, also whoever was told of the failure | `ci_activity`, or `subscribed` for watchers | Success |
79−| g1t finished a change and marked it ready for review | The person who asked g1t for it | `author` | Success |
80−| g1t reviewed a pull request | The person the pull request belongs to | `author` | Success when approved, Info when it asks for changes |
81−| A pull request was merged | Everyone subscribed to it, and people watching pull requests | `state_change`, or `subscribed` for watchers | Success |
82−| An issue or pull request was closed, or an issue was reopened | Everyone subscribed to it, and people watching its kind | `state_change`, or `subscribed` for watchers | Info |
83−| Someone mentioned you with `@username` in a comment | You | `mention` | Info |
84−| Someone mentioned a team with `@workspace/team` in a comment, or in an issue or pull request they opened | Everyone in the team and its child teams, when the team's notifications are on and the writer can see the team | `team_mention` | Info |
85−| Someone commented on an issue or pull request | Everyone subscribed to it, and people watching its kind | Why each is subscribed, or `subscribed` for watchers | Info, or Success for an approval |
86−| One of your workspace's agents commented on or reviewed an issue or pull request, as itself | The same people, and the person the pull request belongs to for a review; shown as from "Margo (agent)", with "(advisory)" on a review. See [agent reviews](/guides/pull-requests/#agent-reviews) | Why each is subscribed, or `author` for a review | Info, or Success for an approval |
87−| An issue or pull request was opened | People watching its kind | `subscribed` | Info |
88−
89−"The person a pull request belongs to" is its author, or, for a change g1t
90−made, the person who asked for it. An approval or a request for changes
91−always reaches that person. g1t itself is never told. A mention in code or
92−in a quoted line does not count.
93−
94−Some threads close themselves once they no longer need you:
95−
96−- When an agent was waiting on you, the thread moves to Done as soon as the
97− agent picks back up: it resumes, its pull request changes, a merge is
98− asked for, or the pull request is merged or closed.
99−- When a review request to you is removed, that thread moves to Done.
100−
101−New activity brings either back, as with any thread.
102−
103−You only see items about repositories you can read. If you lose access to a
104−repository, its items leave your inbox the next time you open it.
105−
106−## Subscriptions
107−
108−You are **subscribed** to an issue or pull request, and hear of what
109−happens on it, without doing anything when you:
110−
111−- opened it, or asked g1t for it
112−- are assigned to it
113−- are one of its reviewers
114−- commented on it
115−- were mentioned in it, by name or through a team
116−
117−You can also subscribe to any issue or pull request yourself, or
118−unsubscribe from one.
119−
120−| You are | You hear of |
121−| --- | --- |
122−| Subscribed | Everything in [What lands there](#what-lands-there) that goes to everyone subscribed: comments, closes, reopens and merges. |
123−| Unsubscribed | Only what is asked of you or is about your own work: an agent waiting on you, a review request, an assignment, a mention of you or your team, and failed checks, workflows and deployments. Commenting on it, or being mentioned in it, subscribes you again. |
124−| Ignoring it | Nothing on it at all, not even a mention. Only you can undo this. |
125−
126−To subscribe to an issue or pull request, or unsubscribe:
127−
128−1. Open the issue or pull request.
129−2. In the sidebar, under **Notifications**, select **Subscribe** or
130− **Unsubscribe**.
131−
132−The line under the button says where you stand, such as "You're subscribed
133−because you were assigned.", "You're not subscribed. You'll still hear if
134−you're mentioned or asked to review." or "You ignore this thread."
135−
136−To ignore an issue or pull request, use the API:
137−`PUT /repos/{owner}/{name}/issues/{number}/subscription` with
138−`"ignored": true`, or the `notifications` tool's `subscribe` action with
139−`ignored`. See [from the API and agents](#from-the-api-and-agents). While
140−you ignore one, its button reads **Stop ignoring**, which puts you back to
141−the default: subscribed only while you take part.
142−
143−## Watching a repository
144−
145−How you **watch** a repository decides what you hear of on it beyond what
146−you take part in.
147−
148−| Level | You hear of |
149−| --- | --- |
150−| **Participating and @mentions** | Only what you take part in or are mentioned in. The default. |
151−| **All activity** | Also every issue and pull request opened, commented on, closed, reopened or merged, and every deployment. |
152−| **Ignore** | Nothing on the repository at all, not even a mention or a review request. |
153−| **Custom** | What you take part in, and the kinds you choose: **Issues**, **Pull requests**, **Deployments** and **Security alerts**. Security alerts reach only those with Write on the repository, who can see its findings. |
154−
155−To change how you watch a repository:
156−
157−1. Open the repository.
158−2. In the header, open the **Watch** menu.
159−3. Select a level. For **Custom**, tick the kinds you want. Unticking
160− every kind puts you back on **Participating and @mentions**.
161−
162−A repository you create is watched the way you choose in
163−[your settings](#settings): **All activity** unless you change it.
164−
165−## Email
166−
167−You can also be emailed when you are told of something. By default, g1t
168−emails you for three reasons: **agent waiting**, **review requested** and
169−**mentioned**. Each time you are told of something for a reason you chose,
170−g1t sends one email with what happened and a link to it.
171−
172−An email is sent only when:
173−
174−- your account's email address is confirmed, and
175−- you can still read the repository it is about.
176−
177−The foot of each email says why you got it, and links to
178−[g1t.sh/settings/notifications](https://g1t.sh/settings/notifications).
179−
180−## Settings
181−
182−**Settings → Notifications**, at
183−[g1t.sh/settings/notifications](https://g1t.sh/settings/notifications),
184−holds your choices:
185−
186−| Setting | What it does | Default |
187−| --- | --- | --- |
188−| **Email** | One checkbox per reason: you are also emailed when you are told of something for it. | agent waiting, review requested, mentioned |
189−| **Repositories you create** | How you watch a new repository you create: **Participating and @mentions** or **All activity**. | All activity |
190−| **Watched repositories** | Every repository you watch other than the default way, with how. | |
191−
192−## Tabs
193−
194−| Tab | Shows |
195−| --- | --- |
196−| **All** | Everything, with what is waiting on you first |
197−| **Needs you** | What is waiting on you: an agent, or a review asked of you |
198−| **Errors** | Failed checks, workflows and deployments |
199−| **Success** | Merges, approvals, finished agent work, and deployments that went live |
200−| **Info** | Mentions, comments, assignments, and what you watch |
201−
202−The count beside each tab is what is unread under it.
203−
204−## Work through it
205−
206−1. Select the bell in the top bar. The inbox opens beside the page.
207−2. Select an item to open what it is about. It is marked read, and the inbox
208− closes.
209−3. Point at an item (on a phone, the buttons are always there) to act on it
210− without opening it:
211−
212−| Action | What it does |
213−| --- | --- |
214−| **Done** (✓) | Moves the thread out of the inbox and into Done, until something new happens on it |
215−| **Mark as read** / **Mark as unread** | Changes whether it counts as unread |
216−| **Save** | Keeps it under Saved, even after it is done |
217−| **Snooze until** | Hides it for 3 hours, until tomorrow, or for a week, then brings it back |
218−
219−**Mark all read** marks everything under the tab you are on as read.
220−
221−## The full inbox
222−
223−**Open inbox**, at the foot of the panel, goes to
224−[g1t.sh/inbox](https://g1t.sh/inbox). It has the same tabs, every thread a
225−page at a time, and two more views:
226−
227−| View | Shows |
228−| --- | --- |
229−| **Saved** | Threads you saved, done or not |
230−| **Done** | Threads you marked done. Select ↶ on one to move it back |
231−
232−Beside the tabs, the **Reason** filter shows only threads told for one
233−reason: select **Any reason** or one of the [reasons](#reasons). It is kept
234−in the address as `?reason=`, such as
235−[g1t.sh/inbox?reason=review_requested](https://g1t.sh/inbox?reason=review_requested),
236−so you can bookmark it.
237−
238−Mission control shows a **Needs you** card with the newest unread threads
239−waiting on you, then failures. It is hidden when there are none.
240−
241−## From the API and agents
242−
243−Everything here is also in the REST API and the MCP server, for a personal
244−access token or an OAuth sign-in. A workspace's token cannot use it, and
245−neither can g1t's own agents: they act as `g1t`, which has no inbox.
246−Reading needs the `notifications:read` scope, and changing anything
247−`notifications:write`; the Agent [preset](/guides/authentication/#presets)
248−has both.
249−
250−| Route | MCP action | What it does |
251−| --- | --- | --- |
252−| [`GET /notifications`](/reference/api/notifications/list-notifications/) | `list` | Your unread threads, latest first. Filter by `reason`, `severity`, `participating`, `since` and `before`; `all` adds read ones; `view` lists `saved` or `done`. |
253−| [`PUT /notifications`](/reference/api/notifications/mark-notifications-read/) | `mark_all_read` | Mark everything read up to `last_read_at`. |
254−| [`GET /notifications/threads/{id}`](/reference/api/notifications/get-notification-thread/) | `get` | One thread, its last 10 activities, and your subscription. |
255−| [`PATCH /notifications/threads/{id}`](/reference/api/notifications/mark-thread-read/) | `mark_read` | Mark a thread read, or unread. |
256−| [`DELETE /notifications/threads/{id}`](/reference/api/notifications/mark-thread-done/) | `done` | Mark a thread done. |
257−| [`PUT /notifications/threads/{id}/saved`](/reference/api/notifications/save-thread/) | `save` | Save a thread; `DELETE` unsaves it. |
258−| [`PUT /notifications/threads/{id}/snooze`](/reference/api/notifications/snooze-thread/) | `snooze` | Snooze a thread until `until`; `DELETE` brings it back. |
259−| [`PUT /repos/{owner}/{name}/issues/{number}/subscription`](/reference/api/notifications/set-issue-subscription/) | `subscribe` | Subscribe to an issue or pull request, unsubscribe, or ignore it. `GET` reads it and `DELETE` unsubscribes. The same works at `/notifications/threads/{id}/subscription`. |
260−| [`PUT /repos/{owner}/{name}/subscription`](/reference/api/notifications/set-repo-subscription/) | `watch` | Watch a repository at a `level`. `GET` reads it and `DELETE` goes back to the default. |
261−| [`GET /user/subscriptions`](/reference/api/notifications/list-watched-repos/) | `watched` | The repositories you watch other than the default way. |
262−
263−`GET /repos/{owner}/{name}/notifications` and
264−`PUT /repos/{owner}/{name}/notifications` list and mark one repository's
265−threads. Every action of the `notifications` tool is in
266−[MCP tools](/reference/mcp/#notifications).
267−
268−For example, to list the reviews waiting on you:
269−
270−```sh
271−curl "https://api.g1t.sh/notifications?reason=review_requested" \
272− -H "Authorization: Bearer $G1T_TOKEN"
273−```
274−
275−## How long items are kept
276−
277−Threads you mark done are removed 30 days after you mark them. Any other
278−thread is removed once nothing has happened on it for 180 days. Saved
279−threads are kept until you unsave them.
+1−1
2121 | **License** | The license its `LICENSE` file holds, such as **MIT license**, linked to the file. **View license** when the text is not one g1t recognizes. `LICENCE`, `COPYING` and `UNLICENSE` are read too, with or without an extension, and an `SPDX-License-Identifier` line says it outright. |
2222 | **Security policy** | A link to `SECURITY.md` at the root, or in `.g1t`, `.github` or `docs`. |
2323 | **Activity** | The repository's [activity](#activity): pushes, merges, new branches and tags. |
24−| **Stars**, **watching** | How many people [starred](#stars) it, and how many watch all or some of its activity from the [Watch menu](/guides/inbox/). |
24+| **Stars**, **watching** | How many people [starred](#stars) it, and how many watch all or some of its activity from the [Watch menu](/guides/notifications/). |
2525 | **Releases** | How many [releases](/guides/releases/) it has and the latest, or **Create a new release** for people who can push. |
2626 | **Packages** | [Packages](/guides/packages/) linked to it, or how to publish the first. |
2727 | **Contributors** | How many people and agents made it, and the most active. See [contributors](#contributors). |
+1−1
8383
8484 | Setting | Default | Options |
8585 | --- | --- | --- |
86−| When the remote stops answering | Show it on the repository | Also tell the workspace's owners in their [inbox](/guides/inbox/) |
86+| When the remote stops answering | Show it on the repository | Also tell the workspace's owners in their [notifications](/guides/notifications/) |
8787 | Take over automatically | Off | After 5 to 1440 minutes unreachable |
8888 | Hand back on its own | When every branch goes back cleanly | Only when someone hands it back |
8989 | Keep CI warm | Off | Run `.g1t/workflows` on each push copied in |
+285−0
1+---
2+title: Notifications
3+description: What needs you, and what you follow, as it happens. One thread per issue, pull request, workflow or deployment, with why you were told. Choose what you hear of with subscriptions, watching and email settings.
4+---
5+
6+**Notifications** tell you when something needs you, or when something
7+happens to work you answer for or follow: an agent is waiting on you,
8+someone asked you to review a pull request, checks failed on your pull
9+request, a deployment failed, someone mentioned you. You are never told
10+about what you did yourself.
11+
12+Open them from **Notifications** in the dock, or from the bottom bar on a
13+phone. The number on it is what is unread. The bell at the right of the
14+page's header opens the same notifications in a panel beside the page; its
15+number is amber while something is waiting on you, red while a failure is
16+unread, and lavender otherwise.
17+
18+## Threads
19+
20+Notifications hold one **thread** for each thing you were told about:
21+
22+- an issue
23+- a pull request
24+- a workflow on one branch
25+- a deployment: a project's production, or one pull request's preview
26+- a workflow run waiting for your review to deploy to an environment
27+
28+When something new happens on a thread, it comes back to the top of your
29+notifications, unread, even if you had marked it done. It is not added a second
30+time. A thread that is snoozed stays snoozed until its time.
31+
32+Each card shows the latest activity's title, why you were told (see
33+[reasons](#reasons)), and, when more than one thing has happened, how many,
34+such as **3 updates**. g1t keeps the last 10 activities of each thread.
35+
36+While a thread is unread it keeps the most urgent of what happened since
37+you last read it. A failure followed by a comment still shows as a failure
38+until you read it.
39+
40+## Reasons
41+
42+Every thread says why you were told of its latest activity. When you are
43+told of one thing for more than one reason, the first that applies in this
44+table is shown.
45+
46+| Reason | Shown as | Why you were told |
47+| --- | --- | --- |
48+| `agent` | agent waiting | An agent is waiting on you: it asked a question, or it stopped until a person steps in. |
49+| `review_requested` | review requested | Someone asked you, or a team you are in, to review a pull request; it changes files you own; or you are one of its reviewers. Also a workflow run waiting for you, as one of an [environment's reviewers](/guides/actions/#environments), to approve its deployment, and, for a workspace's owners, a member's token made for the workspace waiting for [approval](/guides/authentication/#a-workspaces-rules-for-tokens) (in Notifications only, never emailed). |
50+| `assign` | assigned | You were assigned, or you are an assignee. |
51+| `mention` | mentioned | Someone mentioned you with `@username`, or you were mentioned on it before. |
52+| `team_mention` | team mentioned | Someone mentioned a [team](/guides/teams/#mentions) you are in with `@workspace/team`, or a team you are in was mentioned on it before. |
53+| `ci_activity` | CI activity | A check, workflow or deployment on your work finished badly, or recovered. |
54+| `security_alert` | security alert | A new secret, code scanning or vulnerability alert on a repository you look after, a push of yours that push protection blocked, or a [bypass request](/guides/security/secret-protection/#delegated-bypass) to review or its answer. The workspace's owners hear of new alerts; watchers who chose **Security alerts** do too, if they can see findings. |
55+| `state_change` | state changed | It was closed, reopened or merged. |
56+| `author` | your work | You opened it, or you asked g1t for it. Also an owner's answer to your [token made for a workspace](/guides/authentication/#create-a-token) waiting for approval, or its revocation. |
57+| `comment` | commented | You commented on it. |
58+| `manual` | subscribed | You subscribed to it yourself. |
59+| `subscribed` | watching | You watch its repository. |
60+
61+## What lands there
62+
63+Each item comes from something that happened on g1t. Who is told depends on
64+what it was:
65+
66+| What happened | Who is told | Reason | Shown as |
67+| --- | --- | --- | --- |
68+| An agent asked a question of the agent on a pull request, or handed it work | The person the pull request belongs to, and its issue's author and assignees | `agent` | Needs you |
69+| g1t stopped on a pull request until a person steps in | The same people | `agent` | Needs you |
70+| Someone asked for reviews on a pull request, or opened one with reviewers | The reviewers asked | `review_requested` | Needs you |
71+| Someone asked a team to review a pull request | Everyone in the team and its child teams, or, with [review assignment](/guides/teams/#review-assignment), the people picked | `review_requested` | Needs you |
72+| A workflow run's jobs wait for an [environment's reviewers](/guides/actions/#environments) | Each reviewer, and everyone in a reviewing team ("Deploy is waiting for your review to deploy to production in acme/api"), but not whoever started the run when it may not approve it | `review_requested` | Needs you |
73+| A pull request changes files a [CODEOWNERS file](/guides/codeowners/) gives you or your team | The owners asked: "acme/api#42 changes files you own" | `review_requested` | Needs you |
74+| Someone assigned people to an issue or pull request, or opened one with assignees | The people newly assigned | `assign` | Info |
75+| Checks failed, or could not run, on a pull request | The person the pull request belongs to | `ci_activity` | Error |
76+| A workflow failed on a pull request | The person the pull request belongs to | `ci_activity` | Error |
77+| A workflow failed on a branch | Whoever pushed the commit it ran on | `ci_activity` | Error |
78+| A preview of a pull request failed to deploy | The person the pull request belongs to, and people watching deployments | `ci_activity`, or `subscribed` for watchers | Error |
79+| Production failed to deploy | Whoever pushed or started it, and people watching deployments | `ci_activity`, or `subscribed` for watchers | Error |
80+| A deployment went live | People watching deployments; after a failure, also whoever was told of the failure | `ci_activity`, or `subscribed` for watchers | Success |
81+| g1t finished a change and marked it ready for review | The person who asked g1t for it | `author` | Success |
82+| g1t reviewed a pull request | The person the pull request belongs to | `author` | Success when approved, Info when it asks for changes |
83+| A pull request was merged | Everyone subscribed to it, and people watching pull requests | `state_change`, or `subscribed` for watchers | Success |
84+| An issue or pull request was closed, or an issue was reopened | Everyone subscribed to it, and people watching its kind | `state_change`, or `subscribed` for watchers | Info |
85+| Someone mentioned you with `@username` in a comment | You | `mention` | Info |
86+| Someone mentioned a team with `@workspace/team` in a comment, or in an issue or pull request they opened | Everyone in the team and its child teams, when the team's notifications are on and the writer can see the team | `team_mention` | Info |
87+| Someone commented on an issue or pull request | Everyone subscribed to it, and people watching its kind | Why each is subscribed, or `subscribed` for watchers | Info, or Success for an approval |
88+| One of your workspace's agents commented on or reviewed an issue or pull request, as itself | The same people, and the person the pull request belongs to for a review; shown as from "Margo (agent)", with "(advisory)" on a review. See [agent reviews](/guides/pull-requests/#agent-reviews) | Why each is subscribed, or `author` for a review | Info, or Success for an approval |
89+| An issue or pull request was opened | People watching its kind | `subscribed` | Info |
90+
91+"The person a pull request belongs to" is its author, or, for a change g1t
92+made, the person who asked for it. An approval or a request for changes
93+always reaches that person. g1t itself is never told. A mention in code or
94+in a quoted line does not count.
95+
96+Some threads close themselves once they no longer need you:
97+
98+- When an agent was waiting on you, the thread moves to Done as soon as the
99+ agent picks back up: it resumes, its pull request changes, a merge is
100+ asked for, or the pull request is merged or closed.
101+- When a review request to you is removed, that thread moves to Done.
102+
103+New activity brings either back, as with any thread.
104+
105+You only see items about repositories you can read. If you lose access to a
106+repository, its items leave your notifications the next time you open them.
107+
108+## Subscriptions
109+
110+You are **subscribed** to an issue or pull request, and hear of what
111+happens on it, without doing anything when you:
112+
113+- opened it, or asked g1t for it
114+- are assigned to it
115+- are one of its reviewers
116+- commented on it
117+- were mentioned in it, by name or through a team
118+
119+You can also subscribe to any issue or pull request yourself, or
120+unsubscribe from one.
121+
122+| You are | You hear of |
123+| --- | --- |
124+| Subscribed | Everything in [What lands there](#what-lands-there) that goes to everyone subscribed: comments, closes, reopens and merges. |
125+| Unsubscribed | Only what is asked of you or is about your own work: an agent waiting on you, a review request, an assignment, a mention of you or your team, and failed checks, workflows and deployments. Commenting on it, or being mentioned in it, subscribes you again. |
126+| Ignoring it | Nothing on it at all, not even a mention. Only you can undo this. |
127+
128+To subscribe to an issue or pull request, or unsubscribe:
129+
130+1. Open the issue or pull request.
131+2. In the sidebar, under **Notifications**, select **Subscribe** or
132+ **Unsubscribe**.
133+
134+The line under the button says where you stand, such as "You're subscribed
135+because you were assigned.", "You're not subscribed. You'll still hear if
136+you're mentioned or asked to review." or "You ignore this thread."
137+
138+To ignore an issue or pull request, use the API:
139+`PUT /repos/{owner}/{name}/issues/{number}/subscription` with
140+`"ignored": true`, or the `notifications` tool's `subscribe` action with
141+`ignored`. See [from the API and agents](#from-the-api-and-agents). While
142+you ignore one, its button reads **Stop ignoring**, which puts you back to
143+the default: subscribed only while you take part.
144+
145+## Watching a repository
146+
147+How you **watch** a repository decides what you hear of on it beyond what
148+you take part in.
149+
150+| Level | You hear of |
151+| --- | --- |
152+| **Participating and @mentions** | Only what you take part in or are mentioned in. The default. |
153+| **All activity** | Also every issue and pull request opened, commented on, closed, reopened or merged, and every deployment. |
154+| **Ignore** | Nothing on the repository at all, not even a mention or a review request. |
155+| **Custom** | What you take part in, and the kinds you choose: **Issues**, **Pull requests**, **Deployments** and **Security alerts**. Security alerts reach only those with Write on the repository, who can see its findings. |
156+
157+To change how you watch a repository:
158+
159+1. Open the repository.
160+2. In the header, open the **Watch** menu.
161+3. Select a level. For **Custom**, tick the kinds you want. Unticking
162+ every kind puts you back on **Participating and @mentions**.
163+
164+A repository you create is watched the way you choose in
165+[your settings](#settings): **All activity** unless you change it.
166+
167+## Email
168+
169+You can also be emailed when you are told of something. By default, g1t
170+emails you for three reasons: **agent waiting**, **review requested** and
171+**mentioned**. Each time you are told of something for a reason you chose,
172+g1t sends one email with what happened and a link to it.
173+
174+An email is sent only when:
175+
176+- your account's email address is confirmed, and
177+- you can still read the repository it is about.
178+
179+The foot of each email says why you got it, and links to
180+[g1t.sh/settings/notifications](https://g1t.sh/settings/notifications).
181+
182+## Settings
183+
184+**Settings → Notifications**, at
185+[g1t.sh/settings/notifications](https://g1t.sh/settings/notifications),
186+holds your choices:
187+
188+| Setting | What it does | Default |
189+| --- | --- | --- |
190+| **Email** | One checkbox per reason: you are also emailed when you are told of something for it. | agent waiting, review requested, mentioned |
191+| **Repositories you create** | How you watch a new repository you create: **Participating and @mentions** or **All activity**. | All activity |
192+| **Watched repositories** | Every repository you watch other than the default way, with how. | |
193+
194+## Tabs
195+
196+| Tab | Shows |
197+| --- | --- |
198+| **All** | Everything, with what is waiting on you first |
199+| **Needs you** | What is waiting on you: an agent, or a review asked of you |
200+| **Errors** | Failed checks, workflows and deployments |
201+| **Success** | Merges, approvals, finished agent work, and deployments that went live |
202+| **Info** | Mentions, comments, assignments, and what you watch |
203+
204+The count beside each tab is what is unread under it.
205+
206+## The Notifications page
207+
208+**Notifications** in the dock opens
209+[g1t.sh/notifications](https://g1t.sh/notifications): every thread, a page
210+at a time, under the same tabs. Its sidebar chooses what you look at:
211+
212+| In the sidebar | Shows |
213+| --- | --- |
214+| **Everything** | Threads you have not marked done, with how many are unread |
215+| **Saved** | Threads you saved, done or not |
216+| **Done** | Threads you marked done. Select ↶ on one to move it back |
217+| **Why you were told** | Only threads told for one [reason](#reasons), or **Any reason** |
218+
219+The reason is kept in the address as `?reason=`, such as
220+[g1t.sh/notifications?reason=review_requested](https://g1t.sh/notifications?reason=review_requested),
221+so you can bookmark it. On a phone, and on a narrow window, where the
222+sidebar is a drawer, the page shows the views as tabs and the reason as a
223+menu above the list.
224+
225+## Work through it
226+
227+1. Open the Notifications page, or select the bell in the page's header to
228+ work through them in a panel beside the page.
229+2. Select an item to open what it is about. It is marked read, and the
230+ panel closes.
231+3. Point at an item (on a phone, the buttons are always there) to act on it
232+ without opening it:
233+
234+| Action | What it does |
235+| --- | --- |
236+| **Done** (✓) | Moves the thread out of Everything and into Done, until something new happens on it |
237+| **Mark as read** / **Mark as unread** | Changes whether it counts as unread |
238+| **Save** | Keeps it under Saved, even after it is done |
239+| **Snooze until** | Hides it for 3 hours, until tomorrow, or for a week, then brings it back |
240+
241+**Mark all read** marks everything under the tab you are on as read.
242+**Open Notifications**, at the foot of the panel, goes to the page.
243+
244+[Today](/guides/today/) shows the newest unread threads waiting on you, with
245+what else needs you across the workspace.
246+
247+## From the API and agents
248+
249+Everything here is also in the REST API and the MCP server, for a personal
250+access token or an OAuth sign-in. A workspace's token cannot use it, and
251+neither can g1t's own agents: they act as `g1t`, which has no notifications.
252+Reading needs the `notifications:read` scope, and changing anything
253+`notifications:write`; the Agent [preset](/guides/authentication/#presets)
254+has both.
255+
256+| Route | MCP action | What it does |
257+| --- | --- | --- |
258+| [`GET /notifications`](/reference/api/notifications/list-notifications/) | `list` | Your unread threads, latest first. Filter by `reason`, `severity`, `participating`, `since` and `before`; `all` adds read ones; `view` lists `saved` or `done`. |
259+| [`PUT /notifications`](/reference/api/notifications/mark-notifications-read/) | `mark_all_read` | Mark everything read up to `last_read_at`. |
260+| [`GET /notifications/threads/{id}`](/reference/api/notifications/get-notification-thread/) | `get` | One thread, its last 10 activities, and your subscription. |
261+| [`PATCH /notifications/threads/{id}`](/reference/api/notifications/mark-thread-read/) | `mark_read` | Mark a thread read, or unread. |
262+| [`DELETE /notifications/threads/{id}`](/reference/api/notifications/mark-thread-done/) | `done` | Mark a thread done. |
263+| [`PUT /notifications/threads/{id}/saved`](/reference/api/notifications/save-thread/) | `save` | Save a thread; `DELETE` unsaves it. |
264+| [`PUT /notifications/threads/{id}/snooze`](/reference/api/notifications/snooze-thread/) | `snooze` | Snooze a thread until `until`; `DELETE` brings it back. |
265+| [`PUT /repos/{owner}/{name}/issues/{number}/subscription`](/reference/api/notifications/set-issue-subscription/) | `subscribe` | Subscribe to an issue or pull request, unsubscribe, or ignore it. `GET` reads it and `DELETE` unsubscribes. The same works at `/notifications/threads/{id}/subscription`. |
266+| [`PUT /repos/{owner}/{name}/subscription`](/reference/api/notifications/set-repo-subscription/) | `watch` | Watch a repository at a `level`. `GET` reads it and `DELETE` goes back to the default. |
267+| [`GET /user/subscriptions`](/reference/api/notifications/list-watched-repos/) | `watched` | The repositories you watch other than the default way. |
268+
269+`GET /repos/{owner}/{name}/notifications` and
270+`PUT /repos/{owner}/{name}/notifications` list and mark one repository's
271+threads. Every action of the `notifications` tool is in
272+[MCP tools](/reference/mcp/#notifications).
273+
274+For example, to list the reviews waiting on you:
275+
276+```sh
277+curl "https://api.g1t.sh/notifications?reason=review_requested" \
278+ -H "Authorization: Bearer $G1T_TOKEN"
279+```
280+
281+## How long items are kept
282+
283+Threads you mark done are removed 30 days after you mark them. Any other
284+thread is removed once nothing has happened on it for 180 days. Saved
285+threads are kept until you unsave them.
+1−1
214214 person's does.
215215
216216 People mentioned in an agent's comment, and everyone subscribed to the
217−pull request, hear of it in their [inbox](/guides/inbox/) as from
217+pull request, hear of it in their [notifications](/guides/notifications/) as from
218218 **Margo (agent)**, with "(advisory)" on a review. It publishes
219219 `comment.created` as a person's comment does, with `agent` (its `id`,
220220 `handle`, `display_name` and `avatar_seed`), `acting_for`, and for a
+1−1
118118 With **Delegated bypass** on, in the workspace's Security settings
119119 (`g1t.sh/<owner>/-/security/settings`), someone who pushed a blocked
120120 secret asks instead: **Ask to bypass** records a request, and the
121−workspace's owners are told in their [inbox](/guides/inbox/). The
121+workspace's owners are told in their [notifications](/guides/notifications/). The
122122 workspace's owners and the repository's admins review requests at
123123 `g1t.sh/<owner>/-/security/bypass-requests`:
124124
+2−2
190190 Write `@<workspace>/<team>`, such as `@acme/backend`, in a comment, or in
191191 the description of an issue or pull request when you open it, and the
192192 people of that team and its child teams are told in their
193−[inbox](/guides/inbox/), with the reason `team_mention`:
193+[notifications](/guides/notifications/), with the reason `team_mention`:
194194
195195 ```text
196196 ana mentioned @acme/backend on acme/api#42
289289
290290 The timeline records who asked whom: **ana requested a review from
291291 @acme/backend**, and, when people are picked, **requested a review from
292−cy**. The inbox says **ana asked @acme/backend to review acme/api#42** to
292+cy**. Notifications say **ana asked @acme/backend to review acme/api#42** to
293293 the team's people, and **ana asked you to review acme/api#42** to each
294294 person picked. A request made by a CODEOWNERS file reads **acme/api#42
295295 changes files @acme/backend owns**.
+174−0
1+---
2+title: Today
3+description: Your workspace's front page. How the agents' work went today, what is waiting on you with the action beside it, what the day cost, and the one item to start with. How every number is worked out.
4+---
5+
6+**Today** is the first page of your workspace, at `/<workspace>/-/today`.
7+It sums up the day instead of opening a conversation: one sentence about
8+the day, how the agents' work went, what is waiting on you, what the day
9+cost, and the one thing g1t suggests you start with. Chat is one click
10+away, and **Ask g1t** at the top sends a message to g1t from here.
11+
12+Every number on the page comes from what g1t has recorded. Nothing is
13+estimated. When a part of g1t does not answer, the section it feeds says
14+so, and the rest of the page still shows.
15+
16+## What "today" means
17+
18+Today is your calendar day in your own time zone, which your browser
19+tells g1t. Until it has, g1t uses UTC.
20+
21+**Spent today** is the one exception: the statement keeps days from
22+midnight to midnight UTC, so the spend shown is for the current UTC day.
23+See [Spent today](#spent-today).
24+
25+## The sentence at the top
26+
27+Under the heading, one sentence counts the day's tasks and what waits on
28+you, for example:
29+
30+> Oct 9 · Agents finished 35 tasks. 4 needed a fix after review, and 6 are
31+> still open. 8 things are waiting on you.
32+
33+- **Finished** counts tasks accepted first time, fixed after review, and
34+ sessions that finished.
35+- **Needed a fix after review**, **didn't finish** and **still open** are
36+ the counts defined under [Tasks](#tasks).
37+- **Things waiting on you** is the number of rows in
38+ [Needs attention](#needs-attention).
39+
40+The **Review** button beside it shows the same number and opens your
41+notifications.
42+
43+## Tasks
44+
45+A **task** is one piece of agent work. Two kinds count:
46+
47+| Kind | What it is | Where it comes from |
48+| --- | --- | --- |
49+| Pull request | A pull request an agent made in one of the workspace's projects | Code |
50+| Session | An agent session at the root of its tree: one a person started in a conversation, or one a routine started | Agents |
51+
52+A session that another session started, such as a subagent or a colleague
53+brought in, is part of the task that started it and is not counted again.
54+A conversation whose session filed an issue that an agent then worked on
55+counts twice: once for the session, once for the pull request.
56+
57+A task is one of **today's** when it settled today, or when it is still
58+open and something happened on it today. Each one has one outcome:
59+
60+| Outcome | Pull request | Session |
61+| --- | --- | --- |
62+| Accepted first time | Merged today, and the agent was never sent back to revise it | Never: sessions are not reviewed |
63+| Fixed after review | Merged today after the agent was sent back to revise it at least once | Never |
64+| Session finished | — | Finished today |
65+| Still open | Draft or open, and updated today | Queued, working, waiting or waiting for spend approval, and updated today |
66+| Didn't finish | Closed today without merging | Failed or stopped today |
67+
68+"Sent back to revise" means g1t started a revise run on the pull request:
69+it does this when checks fail or a review asks for changes. Fixes a person
70+pushes themselves are not counted as revisions.
71+
72+## Accepted first time
73+
74+The large percentage is:
75+
76+> pull requests accepted first time ÷ agents' pull requests that merged or
77+> closed today
78+
79+A pull request closed without merging counts against it. Open pull
80+requests and sessions are not part of it. With no agent pull request
81+settled today, the percentage shows as a dash.
82+
83+Under it, the change from the **last 7 days** is the same share over the
84+seven calendar days before today, in whole percentage points. It is left
85+out when nothing settled in those days, or when g1t could not read back far
86+enough to count them all.
87+
88+## The strip
89+
90+Beside the percentage are four counts (tasks today, accepted first time,
91+fixed after review, still open) and a strip with one mark for each of
92+today's tasks, coloured by its outcome. Settled tasks come first, in the
93+order they settled, then open ones. Point at a mark to see what it is, and
94+select it to open it. A session in a conversation you are not in shows as
95+"A session in a conversation you're not in".
96+
97+## Needs attention
98+
99+Each row is one thing waiting on you, with what is at stake and the
100+action beside it. Rows come from:
101+
102+| Source | Rows |
103+| --- | --- |
104+| Code | A failed production build; an agent's pull request that stopped and needs a decision; a pull request ready for you to merge; a review asked of you by name; an invitation to a repository; an agent run that stopped reporting; a job waiting for a self-hosted runner |
105+| Agents | A session stopped at its spend cap that you may approve more for; an agent out of its monthly budget, shown only to those who may raise it; an agent waiting |
106+| Notifications | An unread notification in this workspace that is a warning (someone or something is waiting) or a failure |
107+| Chat | A conversation that mentions you, or a direct message you haven't read. Muted conversations are left out |
108+
109+A notification about something already on the list, such as a pull request
110+whose row is there, is left out.
111+
112+Rows are ordered most pressing first, by tier, then by how long each has
113+waited, longest first:
114+
115+1. A failed production build, or the workspace at its usage limit.
116+2. Agent work that has stopped and can't go on without you: a session at
117+ its spend cap, an agent out of budget, a pull request g1t stopped on.
118+3. Finished work that only waits for you: a pull request to merge, a
119+ review, an invitation, an agent waiting.
120+4. Something that may be stuck: a quiet agent run, a job without a runner.
121+5. Unread warnings and failures in your notifications.
122+6. Mentions and direct messages.
123+
124+The card shows the first six rows. **Where today's work came from** under
125+it counts today's tasks by source: Chat (sessions started in a
126+conversation), Code (pull requests), Schedules (sessions a routine
127+started) and Another agent.
128+
129+## Start here
130+
131+**Start here** is the first row of Needs attention: the most pressing
132+tier, and within it the row that has waited longest. g1t picks it with that
133+rule, not with a model, so the same rows always give the same pick. The
134+card shows who is on it, the next action, how long it has waited, what is
135+at stake, and why it was picked.
136+
137+## Spent today
138+
139+**Spent today** is the current UTC day of your workspace's
140+[statement](/guides/usage-and-billing/), with each line at its price:
141+what was charged plus what your plan's included usage, a trial, a pool or a
142+discount paid of it. The lines are the statement's kinds of charge, such as
143+**Agent runs**, **Sandbox time**, **Self-hosted runner time** and
144+**Deployments**, most first. Payments, credits, refunds, tax and card fees
145+are money in, not spend, and are left out. Usage that g1t measures through
146+the month, such as hosting requests and storage, shows on the day it is
147+added to the statement. Under the total is the month so far, at price.
148+
149+## Without Code access
150+
151+A member whose membership does not include Code sees Today without
152+anything from Code:
153+
154+- Tasks are sessions only. The large number is **Finished today**, and the
155+ counts are sessions finished, didn't finish and still open. Accepted
156+ first time is not shown, since it is measured on pull requests.
157+- Needs attention has no rows from Code, and no notifications about a
158+ repository.
159+- Spent today is the same for every member.
160+
161+## When something doesn't answer
162+
163+Each section reads its own part of g1t and shows as soon as it has it.
164+When one does not answer:
165+
166+- The day's work counts what could be read and says which part is missing.
167+ If neither answered, it says so instead of showing counts.
168+- Needs attention names the parts that didn't answer, so an empty list
169+ never reads as nothing waiting.
170+- Spent today says billing didn't answer.
171+
172+When a list g1t reads is longer than it reads at once (200 sessions, 200
173+revise runs, 100 pull requests per project and state, 50 projects), the
174+page says that some of today's tasks may be missing.
+108−53
358358
359359 - **By username**: they get a
360360 [workspace invitation](/guides/authentication/#workspace-invitations) in
361− their inbox and by email, and join with that role when they accept at
361+ their notifications and by email, and join with that role when they accept at
362362 [g1t.sh/invitations](https://g1t.sh/invitations). If they decline, you
363− are told in your inbox. It costs nothing.
363+ are told in your notifications. It costs nothing.
364364 - **By email address**: g1t emails an invite that only that address can
365365 use. With a g1t account, it is a workspace invitation like the one above
366366 and costs nothing. Without one, the invitation also lets them make the
523523 Everything you choose is in the address, so a filtered list can be
524524 bookmarked or shared.
525525
526−## The sidebar
526+## The dock, the sidebar and the page
527527
528−The sidebar is always about one workspace: the one the switcher at its top
529−names. On a workspace's pages, and on a project in one of your workspaces,
530−that is the workspace the page belongs to; on a project somewhere you are
531−not a member, it stays the one you chose last. Choose the workspace's name
532−to open its page, or the arrows beside it to switch, or for **Workspace
533−overview** and **All projects**.
534−[Explore](https://g1t.sh/explore), public projects from all of g1t, is in
535−the top bar, beside **Docs**.
528+Signed in, g1t is always about one workspace: the one you are in. On a
529+workspace's pages, and on a project in one of your workspaces, that is the
530+workspace the page belongs to; anywhere else, even on public pages such as
531+[Explore](https://g1t.sh/explore), someone's profile or another
532+workspace's public project, it stays the one you chose last.
536533
537−It has two parts, a rule apart. Above the rule is what is yours in every
538−workspace: **Mission control**, your **Inbox** with how many items are
539−unread, and the repositories **Shared with you** in workspaces you do not
540−belong to. Below it, under the workspace's name, is the workspace:
534+Each page has three parts:
541535
542−1. **Overview**, the [workspace's page](#the-workspaces-page).
543−2. Its [projects](#pinned-and-recent-projects), ending with **All projects**.
544−3. The places work happens across them: **Agents**, **Context**,
545− **Memory**, **Security** and [**Packages**](/guides/packages/), with
546− **Insights**, **Boards** and **Roadmap** soon.
547−4. **People**, [**Teams**](/guides/teams/), **Usage**, what g1t's runs have
548− cost (see [usage and billing](/guides/usage-and-billing/)), **Support**
549− and **Settings**.
536+| Part | Where | What it holds |
537+| --- | --- | --- |
538+| **The dock** | A rounded bar down the left | g1t's mark, which goes to [Today](/guides/today/); the built-in apps; the apps you pinned; **Apps**; and at its foot **People**, **Workspace** and your account. |
539+| **The sidebar** | Beside the dock | The workspace and its switcher at the top, then the lists of the app you are in. Today and Apps have none. |
540+| **The page** | A rounded panel, the rest of the window | Its header: the button that shows or hides the sidebar, where you are, **Search or jump to** (<kbd>Ctrl</kbd> <kbd>K</kbd>, or <kbd>⌘</kbd> <kbd>K</kbd> on a Mac), **Ask g1t**, the [notifications](/guides/notifications/) bell and **Create new**. Then the page itself. |
550541
551−One row is lit wherever you are: **Teams** on a team's pages, **Packages**
552−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:
553−**Settings** slides over to how the workspace is set up and connected, and
554−the row at the top, **‹ Settings**, slides back:
542+### The dock
543+
544+The built-in apps are always in the dock, each with its name and what is
545+unread:
546+
547+| App | Opens |
548+| --- | --- |
549+| **Today** | What needs you across the workspace. See [Today](/guides/today/). |
550+| **Chat** | Channels and messages. See [Chat](/guides/chat/). |
551+| **Notifications** | Reviews, mentions, failures and what agents wait on. See [notifications](/guides/notifications/). |
552+| **Agents** | The workspace's agents and their sessions. See [agents](/guides/agents/). |
553+| **Code** | Projects, pull requests and checks. Not shown to a member without [Code access](/guides/agent-access/). |
554+| **Artifacts** | Documents, decks and pages. See [artifacts](/guides/artifacts/). |
555+| **People** | Everyone in the workspace, and its [teams](/guides/teams/). |
556+| **Workspace** | Usage, billing, integrations, policies and settings. |
557+
558+A bar on the dock's edge marks the app you are in. Your account is the
559+avatar at the foot: your status, your profile and settings, the
560+documentation, support, status, the keyboard's shortcuts, and signing out.
561+
562+### Apps
563+
564+Every other app in the workspace is a page you can pin to your dock:
565+**Projects**, **Packages**, **Security**, **Context** and **Memory** (for
566+members with Code access), **Teams**, **Usage**, **AI Gateway**,
567+**Integrations** and **Audit log**. A pinned app shows as its icon under
568+the built-in ones, in the order you pinned them.
569+
570+To pin an app:
571+
572+1. Select **Apps** near the foot of the dock, or open the Apps page at
573+ `g1t.sh/<workspace>/-/apps`.
574+2. Find the app, by name if you like.
575+3. Select the pin on its tile. Select it again to unpin it.
576+
577+Your pins are yours alone, kept per workspace in this browser, so another
578+browser or device starts with none. There is no marketplace yet: it shows
579+as coming.
580+
581+### The sidebar
555582
583+The sidebar's top row names the workspace you are in. Select it to switch
584+to another of your workspaces, make a new one, or open your profile or
585+settings. The button beside it hides the sidebar, and the same button at
586+the left of the page's header brings it back; <kbd>Ctrl</kbd> <kbd>B</kbd>
587+(<kbd>⌘</kbd> <kbd>B</kbd> on a Mac) does both, except while you type. g1t
588+remembers which you chose. When the sidebar is hidden, or the app has none,
589+the page's header starts with the same switcher.
590+
591+Below it are the lists of the app you are in. Code's lists the workspace's
592+[projects](#pinned-and-recent-projects), and drills into a project's own
593+list when you open one, with **‹ All projects** at the top to go back. Its
594+**Settings** opens one level further: **General**, **Deployments**,
595+**Domains**, **Agents**, **Guardrails**, **Repository**, **Access**,
596+**Branches and merging**, **Secrets and variables** and **Webhooks**, each
597+for the roles that can use it. A link straight to any of these pages opens
598+the sidebar already there.
599+
600+Workspace's sidebar lists the workspace's **Overview**, **Usage**,
601+**Billing and plans**, **Integrations**, its security policies, the
602+**Audit log** and **Settings**, which slides over to how the workspace is
603+set up:
604+
556605 | Settings | Who | |
557606 | --- | --- | --- |
558607 | **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). |
608+| **Chat** | Members | What members may do in chat. Owners change it. |
559609 | **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. |
560610 | **Access tokens** | Members | The workspace's own tokens. Owners create and delete them. |
561−| **Guardrails** | Members | What agents may do and spend across the workspace. Owners change them. |
611+| **Personal access tokens** | Owners | The workspace's rules for members' tokens, and approving them. |
612+| [**Webhooks**](/guides/webhooks/) | Members | Where the workspace's events are sent. Owners add and change them. |
613+| **Emoji** | Members | The workspace's own emoji. |
562614 | [**Secrets and variables**](/guides/secrets-and-variables/) | Members | What runs and deployments are given. Owners change them. |
615+| **Actions** | Members | How workflows run in the workspace. |
563616 | **Runners** | Owners | The workspace's self-hosted machines, their groups and registration tokens. |
564−| [**Integrations**](/guides/integrations/) | Members | Every tool the workspace connects to, by category: what is connected, what is available, and what is coming. Owners connect and remove them. |
565−| [**Webhooks**](/guides/webhooks/) | Members | Where the workspace's events are sent. Owners add and change them. |
566−| **Billing and plans** | Members | [The g1t plan](/guides/usage-and-billing/#the-g1t-plan), [limits](/guides/usage-and-billing/#limits) and the statement. Owners start the plan, check a card, prepay and set limits. |
567−| **Audit log** | Members | [Every action agents, people and tokens took](/guides/audit-log/). |
568617
569−**People** is in the main list, for every member to see; owners add and
570−remove people there, set the
571−[base permission](/guides/access-and-roles/#the-base-permission), and see
572−the **Outside collaborators** tab. Each member's row also shows the
618+**People** is for every member to see; owners add and remove people there,
619+set the [base permission](/guides/access-and-roles/#the-base-permission),
620+and see the **Outside collaborators** tab. Each member's row also shows the
573621 [teams](/guides/teams/) they are in that you can see.
574622
575−Opening a [project](/guides/projects/) slides the sidebar over to the
576−project's own list, with **‹ All projects** at the top to go back. Its
577−**Settings** opens one level further: **General**, **Deployments**,
578−**Domains**, **Agents**, **Guardrails**, **Repository**, **Access**,
579−**Branches and merging**, **Secrets and variables** and **Webhooks**, each
580−for the roles that can use it. A link straight to any of these pages opens
581−the sidebar already there.
623+Below 1024px wide, the sidebar opens over the page from the left, from the
624+header's button, and closes when you open a page.
582625
583626 ### Pinned and recent projects
584627
585−However many projects a workspace has, its sidebar lists a few:
628+However many projects a workspace has, Code's sidebar lists a few:
586629
587630 - **Pinned**: the projects you pinned, in your order, up to eight a
588631 workspace. Pin one with **Pin** on its page, or the pin on its row of the
608651
609652 | | What it does |
610653 | --- | --- |
611−| **The tabs along the bottom** | **Home**, **Code** (or **Artifacts**, if you don't use Code in this workspace), **Chat**, **Agents** and **Inbox**, each with what is unread. They step aside while the keyboard is up and inside a conversation. |
612−| **The menu button** (☰), beside the workspace's icon at the top left | Opens the sidebar of the mode you are in from the left: the same lists and links as on a computer. Inside a project, that is the project's own list; on a workspace or settings page, the Workspace or account sidebar. Tap outside it, or open a page, and it closes. |
613−| **The tab you are already on** | Tap it again to open that mode's sidebar too. |
614−| **The workspace's icon** at the top left | Everything else, from the bottom: **Artifacts** first, then the workspace's **Overview**, **People**, **Teams**, **Usage and billing**, **Integrations** and **Settings**; switching workspaces; help; and your status, profile, settings and signing out. |
654+| **The bar along the bottom** | **Today**, **Chat**, **Notifications**, **Agents** and **Code** (for members with Code access), each with what is unread, and **More**. It steps aside while the keyboard is up and inside a conversation. |
655+| **More** | A panel above the bar with **All apps**, the Marketplace (coming), your pinned apps, **Artifacts**, **People**, **Workspace**, and **You and help**: your status, profile and settings, the documentation, support, status, the keyboard's shortcuts and signing out. |
656+| **The sidebar button**, at the left of the page's header | Opens the sidebar of the app you are in from the left: the same lists and links as on a computer. Tap outside it, or open a page, and it closes. |
657+| **The tab you are already on** | Tap it again to open that app's sidebar too. |
658+| **The workspace's name** in the page's header | Switches workspace, or opens your profile or settings. |
615659
616660 Inside a [project](/guides/projects/), its pages (**Overview**, **Code**,
617661 **Issues**, **Pull requests**, **Agents**, **Workflows**, **Deployments**,
625669 of the keyboard while you type, and nothing scrolls the page sideways: a
626670 wide file, diff or table scrolls within its own box.
627671
672+### Signed out, and signing in
673+
674+Signing in, signing up, an invite link, the two-factor step, choosing a new
675+password and making your first workspace each stand on their own: the g1t
676+logo, the form, and links to the terms, privacy policy, docs, status and
677+support at the foot. Someone who is not signed in reads public pages, such
678+as Explore, a profile or a public project, under a plain header with
679+**Explore**, **Docs**, **Sign in** and **Sign up**; a project's pages run in
680+the row under its name.
681+
628682 ## Mission control
629683
630−Mission control, `g1t.sh` when you are signed in, is your home page. It
631−shows where you are needed in the workspace you have chosen in the
632−sidebar, what its agents are doing, and what landed without you.
684+Mission control is Code's **Overview**, at `g1t.sh/<workspace>/-/overview`.
685+It shows where you are needed in the workspace's code, what its agents are
686+doing, and what landed without you. Signed in, `g1t.sh` itself opens
687+[Today](/guides/today/) in the workspace you are in.
633688
634689 Under the greeting, one line sums up the week, such as *Agents landed 37
635690 of their 39 changes this week without you, and people landed 8 changes of
788843 public repositories. The link preview for a profile uses only public work.
789844
790845 **How it is laid out.** Signed out, a profile, Explore and Search are
791−shown with g1t's public top bar (search, Explore, signing in) and the page
792−at full width, with no workspace sidebar. Signed in, the rail stays, but
846+shown with g1t's public header (search, Explore, Docs, signing in) and the page
847+at full width, with no workspace sidebar. Signed in, the dock stays, but
793848 no mode is lit and no mode's sidebar opens beside these pages: they are
794849 nobody's workspace, and the profile's own left column says whose it is.
795850
+98−83
11 ---
22 title: What is g1t?
3−description: g1t is one workspace where a team and its agents talk, work, write things down and ship. Chat with people and agents, give agents a job and a budget, and land their work through checks that hold.
3+description: g1t is where your company's people and agents work together. Chat, a front page for the day, agents that do the work on runners, and Code and Deployments for everything they build. In alpha, with every feature marked Live, Preview or Coming.
44 tableOfContents: false
55 prev: false
66 ---
77
88 import { CardGrid } from '@astrojs/starlight/components';
9−import Soon from '../../components/Soon.astro';
9+import Aside from '../../components/Aside.astro';
1010 import Card from '../../components/Card.astro';
1111 import LinkCard from '../../components/LinkCard.astro';
12+import Status from '../../components/Status.astro';
1213
13−**g1t is one workspace where your team and its agents work together.** You
14−talk in channels and direct messages, and agents are members there like
15−anyone else. Every workspace comes with `@g1t`, the orchestrator who knows
16−them all. The rest you hire into roles from templates, like colleagues: for
17−example, Margo from **QA Engineer** or Sam from **Support Specialist**,
18−each with a title, responsibilities, a voice and a budget. You DM them or
19−mention them in a thread. Assign an issue to g1t, and the code it
20−changes lands through pull requests, checks and review. No separate chat
21−app, wiki or forge to stitch together.
14+**g1t is where your company's people and agents work together.** People and
15+agents are members of the same workspace: they talk in channels and direct
16+messages, and they sit on the same teams. You hand an agent work, it does it
17+on a runner, and it comes back with something you can act on: a pull request
18+to merge, a document, a reply to approve. **Today** tells you what happened
19+and what is waiting on you. **Code** and **Deployments** are built in,
20+because what agents make has to be reviewed, tested and shipped somewhere.
21+
22+g1t is MIT-licensed. You can use it on g1t.sh, or run it yourself.
23+
24+<Aside type="note" title="g1t is in alpha">
25+Accounts are by invite while the alpha runs. Every feature on these pages
26+is marked with where it stands:
27+
28+- <Status is="live" /> works on g1t.sh for every workspace.
29+- <Status is="preview" /> works on g1t.sh, but is limited to some workspaces
30+ or not finished. Its page says how.
31+- <Status is="coming" /> is planned and not built yet. Nothing on a page
32+ marked Coming works today.
2233
23−| Mode | What it is |
24−| --- | --- |
25−| **[Chat](/guides/chat/)** | Channels, direct messages and threads, live. People and agents are members alike. |
26−| **[Agents](/guides/agents/)** | Your workspace's colleagues: who they are, what they answer for, what they cost. `@g1t` orchestrates. |
27−| **[Artifacts](/guides/artifacts/)** | Docs now, and slides, designs and dashboards soon, made together live by people and agents. Private until you share them, or kept in spaces. |
28−| **[Code](/concepts/overview/)** | Repositories, issues, pull requests, checks, the merge queue and deployments. |
34+[What g1t can't do yet](/about/limitations/) lists the limits you can hit.
35+</Aside>
2936
3037 <CardGrid>
3138 <LinkCard
3542 />
3643 <LinkCard
3744 title="Hire an agent"
38− description="Pick a role by department, shuffle for a name, give it a team and a voice, and set its budget."
45+ description="Pick a role, give it a name, a team and a voice, and set its budget."
3946 href="/guides/agents/#hire-an-agent"
4047 />
4148 <LinkCard
42− title="Bring the whole company"
43− description="Agents act with the asker's access and answer only with what the audience may see."
44− href="/guides/agent-access/"
49+ title="Bring a repository"
50+ description="Push any git repository, or import one from another host, and your workflows run here."
51+ href="/guides/github/"
4552 />
4653 </CardGrid>
4754
48−## Talk and work
55+## The workspace
4956
50−<CardGrid>
51− <Card title="Chat with your team" icon="messages-square">
52− [Channels, DMs and threads](/guides/chat/), live, with typing and read
53− state. Chat is included on every plan, with no seats and no history
54− cutoff.
55− </Card>
56− <Card title="Agents as colleagues" icon="bot">
57− Each [agent](/guides/agents/) is hired into a role: a title, a team,
58− responsibilities and a voice. DM it or mention it, and it answers in the
59− thread, charged to its own budget.
60− </Card>
61− <Card title="Models, chosen for you" icon="route">
62− Auto routes each step to the cheapest model that can do it. You set a
63− [floor, a ceiling and the providers](/guides/agents/#model-routing) an
64− agent may use, including your own.
65− </Card>
66− <Card title="Rails you can see" icon="shield-check">
67− Budgets, [what an agent may do alone](/guides/agents/#what-it-may-do-alone),
68− [who it acts for](/guides/agent-access/) and the
69− [audit log](/guides/audit-log/), decided by policy, not by the agent.
70− </Card>
71−</CardGrid>
57+Everything happens in a workspace: your company, or one team in it. The
58+dock down the left holds Today, Chat, Notifications, Agents, Code and
59+Artifacts, the apps you pin, and People, Workspace and your account at its
60+foot. On a phone it is a bar along the bottom.
7261
73−## Ship code
62+| Feature | What it does | Status |
63+| --- | --- | --- |
64+| [Today](/guides/today/) | The front page: what agents finished today, how much was accepted the first time, what is waiting on you, what was spent, and where to start. | <Status is="preview" /> |
65+| [Chat](/guides/chat/) | Channels, direct messages and threads, live. People and agents are members alike. No seats and no history limit. | <Status is="live" /> |
66+| [Notifications](/guides/notifications/) | Everything waiting on you: mentions, reviews, approvals and alerts, with browser push. | <Status is="live" /> |
67+| [People and teams](/guides/teams/) | Members, roles, nested teams and invites. | <Status is="live" /> |
68+| Directory and profiles | Everyone, people and agents, in one search, with profiles, reporting lines and teams that include agents. | <Status is="coming" /> |
69+| [Artifacts](/guides/artifacts/) | Documents written together, live, by people and agents. Private until you share them. | <Status is="live" /> |
70+| More kinds of artifact | Slides, designs and dashboards that keep the query behind every number. | <Status is="coming" /> |
71+| Scratchpads | Tools, reports and agents anyone can build on a copy of the data, used in production only after review. | <Status is="coming" /> |
72+| Desktop and phone apps | g1t on your desktop, with a runner inside, and on iOS and Android, with approvals and live task progress. | <Status is="coming" /> |
7473
75−<CardGrid>
76− <Card title="Issues still work" icon="circle-dot">
77− Open an issue and [assign it to g1t](/guides/working-with-g1t/). It opens
78− a pull request, runs your workflows, gets a review and revises until it
79− passes. You step in only when asked, and [your inbox](/guides/inbox/)
80− puts that first.
81− </Card>
82− <Card title="Hand off an outcome" icon="rocket">
83− Describe a result. A planner turns it into issues in the order they land, and
84− [agents work them in parallel](/guides/outcomes/).
85− </Card>
86− <Card title="Main stays green" icon="circle-check">
87− Each change must pass its
88− [required checks](/guides/pull-requests/#required-status-checks) to merge,
89− through the [merge queue](/guides/merge-queue/) if you turn it on.
90− </Card>
91− <Card title="Bring your own agent" icon="plug">
92− Any coding agent or MCP client works on g1t as a teammate, and
93− [records its session](/guides/bring-your-own-agent/) onto its pull requests.
94− </Card>
95− <Card title="Workflows from .g1t" icon="workflow">
96− Workflows [run from `.g1t/workflows`](/guides/actions/), most without
97− changes, and failing ones send the agent back to fix the cause.
98− </Card>
99− <Card title="A preview for every change" icon="globe">
100− On the g1t plan, every pull request gets a [live preview on g1t.page](/guides/deployments/), and
101− the default branch goes to production. Apps cost nothing while no one visits.
102− </Card>
103− <Card title="Secrets kept out, fixes opened" icon="lock">
104− Pushes that add a key or a token are refused, and each vulnerable dependency
105− gets a [pull request that upgrades it](/guides/security/).
106− </Card>
107− <Card title="Everything has an API" icon="book-open">
108− Repositories, issues, pull requests, agents and workflows are all a [REST endpoint](/reference/api/) and an [MCP tool action](/reference/mcp/) away, with
109− [webhooks](/guides/webhooks/) for every event. Tests that drive a browser
110− [use the website with a token](/guides/authentication/#use-a-token-on-the-website).
111− </Card>
112−</CardGrid>
74+## Agents
75+
76+| Feature | What it does | Status |
77+| --- | --- | --- |
78+| [Agents](/guides/agents/) | Colleagues hired into a role, each with responsibilities, a voice, the models it may use and a budget. DM one, or mention it in a thread. | <Status is="live" /> |
79+| [g1t, the orchestrator](/guides/agents/) | Every workspace's own agent. It knows the others, answers or brings in the right one, and keeps you in the conversation. | <Status is="live" /> |
80+| [Model providers](/guides/models/) | Agents use your own provider's key, or g1t's hosted models through the [AI Gateway](/guides/ai-gateway/). | <Status is="live" /> |
81+| g1t's hosted models | Models g1t runs for you, billed on your workspace. Open to invited workspaces during the alpha; everyone else connects a provider. | <Status is="preview" /> |
82+| [Budgets](/guides/agent-budgets/) and [guardrails](/guides/guardrails/) | Spend limits per workspace, agent and task, checked before work starts, and what an agent may do alone. | <Status is="live" /> |
83+| [Acting for the asker](/guides/agent-access/) | Agents act with the access of the person who asked, and answer with only what their audience may see. | <Status is="live" /> |
84+| [Memory](/guides/agent-memory/), [sessions](/guides/agent-sessions/) and [routines](/guides/agent-routines/) | What an agent keeps, every run with its steps and cost, and work on a schedule. | <Status is="live" /> |
85+| Agent catalog | Specialists to hire from a catalog, each with a role, skills, tools and the runner it prefers. | <Status is="coming" /> |
86+| Foundational skills | Documents, research on the web, data and charts, code, communication, and files, in every agent. | <Status is="coming" /> |
87+| Agents on runners | Every agent's machine is a runner: g1t's, one of yours, or your desktop, with sessions that last between tasks. | <Status is="coming" /> |
88+| [Agents in your chat app](/guides/chat-app/) | The same agents, answering in the chat app your team already uses. | <Status is="coming" /> |
89+
90+## Code and Deployments
91+
92+| Feature | What it does | Status |
93+| --- | --- | --- |
94+| [Repositories](/guides/managing-repositories/) and [git](/guides/git/) | Public and private repositories over HTTPS, with creating one by pushing to it. | <Status is="live" /> |
95+| [Issues and pull requests](/guides/pull-requests/) | Issues, pull requests, line comments and reviews from people and agents. | <Status is="live" /> |
96+| [Checks](/guides/checks/), [rules](/guides/rules/) and the [merge queue](/guides/merge-queue/) | Required checks, protected branches, code owners, and changes tested together before they land. | <Status is="live" /> |
97+| [Workflows](/guides/actions/) | Workflows from `.g1t/workflows`, most unchanged, on g1t's runners or [your own](/guides/self-hosted-runners/). | <Status is="live" /> |
98+| [g1t's agent on an issue](/guides/working-with-g1t/) | Assign an issue to g1t: it opens a pull request, runs your checks, gets a review and revises until they pass. Open to invited workspaces during the alpha. | <Status is="preview" /> |
99+| [Bring your own agent](/guides/bring-your-own-agent/) | Any coding agent or MCP client works on g1t and records its session onto its pull requests. | <Status is="live" /> |
100+| [Packages](/guides/packages/) | npm, Cargo, Composer, Maven, NuGet, RubyGems, Go modules and container images. | <Status is="live" /> |
101+| [Security](/guides/security/) | Secret push protection, history scanning and pull requests that upgrade vulnerable dependencies. | <Status is="live" /> |
102+| [Deployments](/guides/deployments/) | A live preview of every pull request on g1t.page, production on merge, and custom domains. | <Status is="live" /> |
103+| Preview data | Each preview gets its own copy of the database and bucket, deleted when the pull request closes. | <Status is="coming" /> |
104+| Rollback and always on | One click back to any deploy, request logs, and container apps kept warm. | <Status is="coming" /> |
105+| [GitHub](/guides/github/) and [mirroring](/guides/mirroring/) | Import repositories, keep them mirrored, or push changes back. | <Status is="live" /> |
106+| Git over SSH | `git@g1t.sh` remotes. Use HTTPS until then. | <Status is="coming" /> |
107+
108+## Extend and connect
109+
110+| Feature | What it does | Status |
111+| --- | --- | --- |
112+| [Integrations](/guides/integrations/) | The GitHub App, Sentry, Datadog, Jira, Linear and alerts. | <Status is="live" /> |
113+| [API](/reference/api/), [MCP](/reference/mcp/) and [webhooks](/guides/webhooks/) | Everything as a REST endpoint and an MCP tool, and a webhook for every event. | <Status is="live" /> |
114+| Marketplace | Extensions that add pages, cards, tools and agent roles, installed by an owner after reading their permissions. | <Status is="coming" /> |
115+| More integrations | Drive, Gmail, Calendar, Slack and Microsoft 365, each listing what it lets agents do. | <Status is="coming" /> |
116+| Mail | Email on your own domain, with shared inboxes that agents work in. | <Status is="coming" /> |
117+
118+## Your workspace
119+
120+| Feature | What it does | Status |
121+| --- | --- | --- |
122+| [Accounts and sign-in](/guides/authentication/) | Invites, two-factor authentication, access tokens and OAuth apps. | <Status is="live" /> |
123+| Single sign-on | SAML or OIDC and SCIM, set up by each workspace for itself. | <Status is="coming" /> |
124+| [Usage and billing](/guides/usage-and-billing/) | No seats. Usage at what it costs, a spend limit, and itemised invoices. | <Status is="live" /> |
125+| Storage | Databases and buckets your teams can create, at a level each team is given, with copies of production data for anything not yet reviewed. | <Status is="coming" /> |
126+| [Audit log](/guides/audit-log/) | Every change to the workspace, with export. | <Status is="live" /> |
127+| [Run g1t yourself](/guides/self-hosting/) | The core forge in Docker. Agents, deployments and context search are off in this version. | <Status is="preview" /> |
113128
114129 ## Bring a repository
115130
+2−2
3232
3333 2. **Open Chat**
3434
35− Choose **Chat** in the rail, or go to `g1t.sh/<workspace>/-/chat`. Every
35+ Choose **Chat** in the dock, or go to `g1t.sh/<workspace>/-/chat`. Every
3636 workspace starts with `#general`. Say hello; people chatting is free on
3737 every plan. See [Chat](/guides/chat/).
3838
4747
4848 4. **Create an agent**
4949
50− Open **Agents** in the rail and choose **New agent**. The templates are
50+ Open **Agents** in the dock and choose **New agent**. The templates are
5151 grouped by department. Pick one, such as **QA Engineer**, and keep its
5252 defaults: you hire Margo, with a title, responsibilities, a personality
5353 and sensible model limits. Choose
+1−1
224224 | --- | --- |
225225 | [Accounts](/reference/api/accounts/whoami/) | Signing in from a tool, and who a token acts as. Accounts are made and [deleted](/guides/authentication/#deleting-your-account) only in a browser: there is no route for either. |
226226 | [Workspaces](/reference/api/workspaces/create-workspace/) | Creating a workspace. |
227−| [Notifications](/reference/api/notifications/list-notifications/) | Your inbox: its threads, why you were told of each, marking them read, done, saved or snoozed, and what you subscribe to and watch. See [your inbox](/guides/inbox/). |
227+| [Notifications](/reference/api/notifications/list-notifications/) | Your notifications: their threads, why you were told of each, marking them read, done, saved or snoozed, and what you subscribe to and watch. See [notifications](/guides/notifications/). |
228228 | [Billing](/reference/api/billing/get-usage/) | A workspace's usage by product, project and day, its budget, its AI credit, its invoices and its [AI Gateway](/guides/ai-gateway/) requests. See [usage and billing](/guides/usage-and-billing/). |
229229 | [Invites](/reference/api/invites/list-invites/) | Your invites while g1t is invite-only, and inviting people into a workspace by email. |
230230 | [Repositories](/reference/api/repositories/list-repos/) | A repository, how it handles pull requests, and its timeline. |
+2−2
715715
716716 ## `notifications`
717717
718−Your [inbox](/guides/inbox/): one thread per issue, pull request, workflow
718+Your [notifications](/guides/notifications/): one thread per issue, pull request, workflow
719719 on a branch or deployment, with why you were told (`reason`), and what you
720720 subscribe to and watch. `list` is the default action. It is your own: a
721721 personal access token or an OAuth sign-in can use it, a workspace's token
816816 | Catch up | The reading actions only. |
817817
818818 No agent's token can use the `workspace`, `access`, `team`, `secret`, `webhook` or
819−`notifications` tools (g1t acts as `g1t`, which has no inbox), the controls of `workflow`, or `pull_request` `merge`, `agent`
819+`notifications` tools (g1t acts as `g1t`, which has no notifications), the controls of `workflow`, or `pull_request` `merge`, `agent`
820820 `assign` and `delegate`, `plan` `create` and `apply`, `issue` `import`, or
821821 any `repository` action that creates, changes, renames, archives,
822822 transfers, deletes, restores or purges a repository, or dismisses or
+46−0
522522 text-transform: uppercase;
523523 letter-spacing: 0.04em;
524524 }
525+
526+/* Feature tables with a Live, Preview or Coming badge (components/Status.astro):
527+ on a phone each row stacks, the name and its badge on one line and what it
528+ does under them, rather than three narrow columns. */
529+@media (max-width: 50rem) {
530+ .sl-markdown-content table:has(.status),
531+ .sl-markdown-content table:has(.status) tbody {
532+ display: block;
533+ width: 100%;
534+ }
535+ .sl-markdown-content table:has(.status) thead {
536+ display: none;
537+ }
538+ .sl-markdown-content table:has(.status) tr {
539+ display: grid;
540+ grid-template-columns: minmax(0, 1fr) max-content;
541+ width: 100%;
542+ column-gap: 0.75rem;
543+ padding-block: 0.5rem;
544+ border-bottom: 1px solid var(--sl-color-hairline);
545+ }
546+ .sl-markdown-content table:has(.status) td {
547+ border: 0;
548+ padding: 0.15rem 0;
549+ width: auto;
550+ }
551+ .sl-markdown-content table:has(.status) td:first-child {
552+ font-weight: 600;
553+ }
554+ .sl-markdown-content table:has(.status) td:nth-child(2) {
555+ grid-column: 1 / -1;
556+ grid-row: 2;
557+ color: var(--sl-color-gray-2);
558+ }
559+ .sl-markdown-content table:has(.status) td:nth-child(3) {
560+ grid-column: 2;
561+ grid-row: 1;
562+ justify-self: end;
563+ align-self: center;
564+ width: max-content;
565+ min-width: 0;
566+ }
567+ .sl-markdown-content table:has(.status) td + td {
568+ margin-top: 0;
569+ }
570+}
+32−11
1212 --tracking-tight: -0.012em;
1313
1414 --color-bg: var(--g1t-bg);
15+ /* Behind the page panel, where the sidebar sits flat (components/shell.tsx). */
16+ --color-shell: #0b0b0d;
17+ /* The dock's own surface: a floating bar a step above the shell. */
18+ --color-dock: #15151a;
1519 --color-surface: var(--g1t-surface);
1620 --color-raised: var(--g1t-raised);
1721 --color-line: var(--g1t-line);
157161 }
158162
159163 /*
160− * Phones: a field under 16px makes iOS zoom the page when it is focused, so
161− * every field is at least that; and nothing scrolls the page sideways.
162− */
163−/*
164− * The phone's tab bar (components/mobile.tsx): how much of the bottom it
165− * takes, for the shell to keep every page's end clear of it.
164+ * The app's frame (components/shell.tsx), set on <body data-frame="app">:
165+ * the page is a rounded panel inset from the window, with the dock beside
166+ * it and, on a phone, the bottom bar under it. What sticks to the top or
167+ * the bottom of a page reads these:
168+ * --frame-top the gap above the panel
169+ * --topbar-h the panel's top edge plus its header: where sticky things sit
170+ * --tabbar-h what the bottom takes: the gap under the panel, and the phone's bar
171+ * --page-h the height left for a page that fills the panel (Chat)
172+ * The public frame keeps a plain header 3.5rem tall and nothing at the bottom.
166173 */
167174 :root {
175+ --frame-top: 0px;
168176 --tabbar-h: 0px;
169− /* The top bar's height (components/shell.tsx), for what sticks just under it. */
170177 --topbar-h: 3.5rem;
178+ --page-h: calc(100dvh - var(--topbar-h) - var(--tabbar-h));
179+}
180+body[data-frame="app"] {
181+ --frame-top: 0.5rem;
182+ --topbar-h: calc(var(--frame-top) + 3.5rem);
183+ --tabbar-h: 0.5rem;
184+ --page-h: calc(100dvh - var(--topbar-h) - var(--tabbar-h));
171185 }
172186 @media (max-width: 767px) {
173− :root {
174− --tabbar-h: calc(3.5rem + env(safe-area-inset-bottom));
175− /* On a phone it grows by the notch. */
176− --topbar-h: calc(3.5rem + env(safe-area-inset-top));
187+ body[data-frame="app"] {
188+ --frame-top: max(0.375rem, env(safe-area-inset-top));
189+ /* The bottom bar: 64px tall, 8px off the edge, 6px under the panel. */
190+ --tabbar-h: calc(4.875rem + env(safe-area-inset-bottom));
177191 }
178192 }
179193
194+/*
195+ * Phones: a field under 16px makes iOS zoom the page when it is focused, so
196+ * every field is at least that; and nothing scrolls the page sideways.
197+ */
180198 @media (max-width: 767px) {
181199 input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
182200 textarea,
191209 body {
192210 @apply bg-bg font-sans text-fg antialiased;
193211 }
212+body[data-frame="app"] {
213+ @apply bg-shell;
214+}
194215
195216 ::selection {
196217 background: color-mix(in srgb, var(--color-accent) 35%, transparent);
+3−3
9898 <span className="relative shrink-0">
9999 <AgentFace agent={{ ...agent, builtin: isOrchestrator(agent) }} size={26} />
100100 {!isOrchestrator(agent) && (
101− <StatusDot status={agent.status} className="absolute -right-0.5 -bottom-0.5 ring-2 ring-[color-mix(in_srgb,var(--color-surface)_70%,var(--color-bg))]" />
101+ <StatusDot status={agent.status} className="absolute -right-0.5 -bottom-0.5 ring-2 ring-shell" />
102102 )}
103103 </span>
104104 <span className="min-w-0 grow leading-tight">
121121 );
122122 return (
123123 <div className="flex h-full flex-col">
124− <div className={`flex h-14 shrink-0 items-center justify-between border-b border-line pr-2.5 pl-4 ${phone ? "hidden" : ""}`}>
125− <h2 className="text-[0.9375rem] font-semibold">Agents</h2>
124+ <div className={`flex h-9 shrink-0 items-center justify-between pr-1 pl-3 ${phone ? "hidden" : ""}`}>
125+ <h2 className="text-xs font-medium text-faint">Agents</h2>
126126 <NavLink
127127 to={`/${slug}/-/agents/new`}
128128 aria-label="New agent"
+216−0
1+import {
2+ BarChart3,
3+ Bell,
4+ Boxes,
5+ Brain,
6+ Building2,
7+ Code2,
8+ History,
9+ LayoutGrid,
10+ MessagesSquare,
11+ Network,
12+ Package,
13+ Pin,
14+ Plug,
15+ Search,
16+ Shapes,
17+ ShieldCheck,
18+ Sparkles,
19+ Store,
20+ Sun,
21+ Users,
22+ UsersRound,
23+ Waypoints,
24+} from "lucide-react";
25+import { type ReactNode, useMemo, useState } from "react";
26+import { Link, useFetcher, useFetchers } from "react-router";
27+
28+import { type AppInfo, type AppKey, type PinnableApp, appPinFromForm, appsFor, withPin } from "../lib/apps";
29+import { cn } from "../lib/cn";
30+import { Hint } from "./ui/hint";
31+
32+/** Each app's icon, at the size asked for. */
33+export function appIcon(key: AppKey, size = 18): ReactNode {
34+ const icons: Record<AppKey, ReactNode> = {
35+ today: <Sun size={size} />,
36+ chat: <MessagesSquare size={size} />,
37+ notifications: <Bell size={size} />,
38+ agents: <Sparkles size={size} />,
39+ code: <Code2 size={size} />,
40+ artifacts: <Shapes size={size} />,
41+ people: <Users size={size} />,
42+ workspace: <Building2 size={size} />,
43+ projects: <Boxes size={size} />,
44+ packages: <Package size={size} />,
45+ security: <ShieldCheck size={size} />,
46+ context: <Network size={size} />,
47+ memory: <Brain size={size} />,
48+ teams: <UsersRound size={size} />,
49+ usage: <BarChart3 size={size} />,
50+ gateway: <Waypoints size={size} />,
51+ integrations: <Plug size={size} />,
52+ audit: <History size={size} />,
53+ };
54+ return icons[key];
55+}
56+
57+/** Every pin form shares this key, so the dock, the launcher and the Apps page see one change. */
58+const PIN_FETCHER = "app-pin";
59+
60+/**
61+ * Someone's pins in the workspace `slug`, as saved (`saved`, from the
62+ * root's data), with any pin or unpin on its way already applied, so the
63+ * dock changes the moment a pin is pressed. `toggle` pins or unpins.
64+ */
65+export function useAppPins(slug: string, saved: PinnableApp[]) {
66+ const fetcher = useFetcher({ key: PIN_FETCHER });
67+ const fetchers = useFetchers();
68+ const pins = useMemo(() => {
69+ let pins = saved;
70+ for (const pending of fetchers) {
71+ if (pending.key !== PIN_FETCHER || !pending.formData) continue;
72+ const change = appPinFromForm(pending.formData);
73+ if (change) pins = withPin(pins, change.app, change.pinned);
74+ }
75+ return pins;
76+ }, [saved, fetchers]);
77+ const toggle = (app: PinnableApp) => {
78+ const form = new FormData();
79+ form.set("intent", pins.includes(app) ? "unpin" : "pin");
80+ form.set("app", app);
81+ fetcher.submit(form, { method: "post", action: `/${slug}/-/apps` });
82+ };
83+ return { pins, toggle };
84+}
85+
86+/**
87+ * One app: its icon and name, opening it; and, for an app people pin, a
88+ * pin that shows on hover or focus and stays lit while it is pinned.
89+ */
90+export function AppTile({
91+ app,
92+ slug,
93+ pinned,
94+ onToggle,
95+ onOpen,
96+ card = false,
97+}: {
98+ app: AppInfo;
99+ slug: string;
100+ pinned: boolean;
101+ onToggle: (app: PinnableApp) => void;
102+ onOpen?: () => void;
103+ /** On the Apps page: a card with what the app is. */
104+ card?: boolean;
105+}) {
106+ return (
107+ <div
108+ className={cn(
109+ "group/tile relative rounded-xl transition-colors",
110+ card ? "border border-line bg-surface hover:border-line-strong" : "hover:bg-raised focus-within:bg-raised",
111+ )}
112+ >
113+ <Link
114+ to={app.path(slug)}
115+ prefetch="intent"
116+ onClick={onOpen}
117+ className={cn(
118+ "flex w-full rounded-xl text-center outline-none focus-visible:ring-2 focus-visible:ring-accent",
119+ card ? "items-center gap-3 p-3.5 pr-11 text-left" : "flex-col items-center gap-1.5 px-1 pt-3 pb-2.5",
120+ )}
121+ >
122+ <span className="flex size-10 shrink-0 items-center justify-center rounded-xl bg-raised text-fg ring-1 ring-line group-hover/tile:ring-line-strong">
123+ {appIcon(app.key, 19)}
124+ </span>
125+ {card ? (
126+ <span className="min-w-0">
127+ <span className="block truncate text-sm font-medium text-fg">{app.name}</span>
128+ <span className="block truncate text-xs text-muted">{app.about}</span>
129+ </span>
130+ ) : (
131+ <span className="w-full truncate text-xs font-medium text-fg-soft">{app.name}</span>
132+ )}
133+ </Link>
134+ {!app.builtin && (
135+ <Hint label={pinned ? "Unpin from your dock" : "Pin to your dock"}>
136+ <button
137+ type="button"
138+ aria-label={pinned ? `Unpin ${app.name} from your dock` : `Pin ${app.name} to your dock`}
139+ aria-pressed={pinned}
140+ onClick={() => onToggle(app.key as PinnableApp)}
141+ className={cn(
142+ "absolute flex size-6 items-center justify-center rounded-md outline-none transition-opacity hover:bg-line focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-accent [@media(hover:none)]:opacity-100",
143+ card ? "top-1/2 right-2.5 -translate-y-1/2" : "top-1 right-1",
144+ pinned ? "text-accent opacity-100" : "text-faint opacity-0 group-hover/tile:opacity-100 hover:text-fg",
145+ )}
146+ >
147+ <Pin size={13} className={pinned ? "fill-current" : undefined} />
148+ </button>
149+ </Hint>
150+ )}
151+ </div>
152+ );
153+}
154+
155+/**
156+ * The Apps launcher's body: a search, every app in the workspace the
157+ * person can use, and the way to all of them and to the Marketplace.
158+ */
159+export function AppsLauncher({
160+ slug,
161+ code,
162+ pins,
163+ onToggle,
164+ onClose,
165+}: {
166+ slug: string;
167+ code: boolean;
168+ pins: PinnableApp[];
169+ onToggle: (app: PinnableApp) => void;
170+ onClose: () => void;
171+}) {
172+ const [query, setQuery] = useState("");
173+ const wanted = query.trim().toLowerCase();
174+ const apps = appsFor(code).filter((app) => !wanted || app.name.toLowerCase().includes(wanted) || app.about.toLowerCase().includes(wanted));
175+ return (
176+ <div className="flex flex-col gap-2">
177+ <label className="flex h-9 items-center gap-2 rounded-lg border border-line bg-bg px-2.5 text-sm focus-within:border-line-strong">
178+ <Search size={15} className="shrink-0 text-faint" />
179+ <input
180+ value={query}
181+ onChange={(event) => setQuery(event.target.value)}
182+ placeholder="Find an app"
183+ aria-label="Find an app"
184+ autoComplete="off"
185+ className="min-w-0 grow bg-transparent outline-none placeholder:text-faint"
186+ />
187+ </label>
188+ {apps.length === 0 ? (
189+ <p className="px-2 py-6 text-center text-sm text-muted">No app matches.</p>
190+ ) : (
191+ <div className="grid max-h-[min(23.5rem,60dvh)] grid-cols-4 gap-1 overflow-y-auto max-sm:grid-cols-3">
192+ {apps.map((app) => (
193+ <AppTile key={app.key} app={app} slug={slug} pinned={pins.includes(app.key as PinnableApp)} onToggle={onToggle} onOpen={onClose} />
194+ ))}
195+ </div>
196+ )}
197+ <div className="flex items-center justify-between border-t border-line pt-2">
198+ <Link
199+ to={`/${slug}/-/apps`}
200+ onClick={onClose}
201+ className="flex h-8 items-center gap-1.5 rounded-md px-2 text-[0.8125rem] text-muted transition-colors hover:bg-line hover:text-fg"
202+ >
203+ <LayoutGrid size={14} />
204+ All apps
205+ </Link>
206+ <Hint label="There is no marketplace yet." disabled>
207+ <button type="button" disabled className="flex h-8 items-center gap-1.5 rounded-md px-2 text-[0.8125rem] text-faint">
208+ <Store size={14} />
209+ Marketplace
210+ <span className="rounded-full bg-line px-1.5 text-[0.625rem] font-medium text-muted">Coming</span>
211+ </button>
212+ </Hint>
213+ </div>
214+ </div>
215+ );
216+}
+9−8
11 import type { ReactNode } from "react";
22
3−import { Mark } from "./logo";
4−
5−/** The centred panel shared by the sign-in and sign-up pages. */
3+/**
4+ * The column shared by the sign-in and sign-up pages, under the logo the
5+ * standalone frame draws (components/standalone.tsx): a short heading, a
6+ * line under it, the form, and what else there is to do.
7+ */
68 export function AuthCard({
79 title,
810 subtitle,
1517 children: ReactNode;
1618 }) {
1719 return (
18− <main className="mx-auto flex max-w-sm flex-col px-4 pt-20 pb-10">
19− <Mark className="size-9" />
20− <h1 className="mt-6 text-2xl font-semibold tracking-tight">{title}</h1>
21− <p className="text-2xl tracking-tight text-faint">{subtitle}</p>
22− <div className="mt-8">{children}</div>
20+ <main className="mx-auto flex w-full max-w-85 flex-col pt-8">
21+ <h1 className="text-center text-xl font-semibold tracking-tight">{title}</h1>
22+ <p className="mt-1 text-center text-sm text-muted">{subtitle}</p>
23+ <div className="mt-7">{children}</div>
2324 <p className="mt-6 text-center text-sm text-muted">{footer}</p>
2425 </main>
2526 );
+4−4
1313 GitMerge,
1414 GitPullRequest,
1515 Hash,
16− Home,
17− Inbox,
16+ Sun,
17+ Bell,
1818 MessagesSquare,
1919 Paperclip,
2020 SendHorizontal,
114114 }
115115
116116 const RAIL: { icon: ReactNode; label: string; on?: boolean }[] = [
117− { icon: <Home size={16} />, label: "Home" },
117+ { icon: <Sun size={16} />, label: "Today" },
118118 { icon: <Code2 size={16} />, label: "Code" },
119119 { icon: <MessagesSquare size={16} />, label: "Chat", on: true },
120120 { icon: <BookOpen size={16} />, label: "Docs" },
121121 { icon: <Bot size={16} />, label: "Agents" },
122− { icon: <Inbox size={16} />, label: "Inbox" },
122+ { icon: <Bell size={16} />, label: "Notifications" },
123123 ];
124124
125125 const CHANNELS: { name: string; on?: boolean; unread?: number }[] = [
+1−1
606606 <CardContext.Provider value={cardContext}>
607607 <div
608608 {...swipe}
609− className="flex h-[calc(100dvh-3.5rem)] min-h-0 lg:h-dvh max-md:fixed max-md:inset-x-0 max-md:top-(--vv-top,0px) max-md:z-30 max-md:h-(--vv-height,100dvh) max-md:bg-bg"
609+ className="flex h-(--page-h) min-h-0 max-md:fixed max-md:inset-x-0 max-md:top-(--vv-top,0px) max-md:z-30 max-md:h-(--vv-height,100dvh) max-md:bg-bg"
610610 >
611611 {/* What pressing a card's action did (components/chat/card.tsx). */}
612612 <CardToasts />
+1−1
77 /** A calm full-height panel for a conversation that cannot be shown. */
88 function Panel({ icon, title, children }: { icon: ReactNode; title: string; children: ReactNode }) {
99 return (
10− <div className="flex h-[calc(100dvh-3.5rem)] items-center justify-center px-6 lg:h-dvh">
10+ <div className="flex h-(--page-h) items-center justify-center px-6">
1111 <div className="max-w-sm text-center">
1212 <div className="mx-auto flex size-12 items-center justify-center rounded-xl border border-line bg-surface text-muted">{icon}</div>
1313 <h1 className="mt-4 text-base font-semibold">{title}</h1>
+3−3
139139
140140 return (
141141 // The colour behind it, for the dots cut out of people's avatars.
142− <div className="flex h-full flex-col [--chat-sidebar-bg:color-mix(in_srgb,var(--color-surface)_70%,var(--color-bg))]">
142+ <div className="flex h-full flex-col [--chat-sidebar-bg:var(--color-shell)]">
143143 {heading && (
144− <div className="flex h-14 shrink-0 items-center justify-between border-b border-line pr-2.5 pl-4">
145− <h2 className="text-[0.9375rem] font-semibold">Chat</h2>
144+ <div className="flex h-9 shrink-0 items-center justify-between pr-1 pl-3">
145+ <h2 className="text-xs font-medium text-faint">Chat</h2>
146146 <NewMessageButton slug={slug} />
147147 </div>
148148 )}
+2−2
433433 >
434434 <header
435435 // Sticks under the change's toolbar (its height is --diff-toolbar), not behind it.
436− className={`sticky top-[calc(3.5rem+var(--diff-toolbar,0px))] z-20 flex items-center gap-2 border-line bg-surface/95 px-2 py-2 backdrop-blur sm:gap-2.5 sm:px-3 ${
436+ className={`sticky top-[calc(var(--topbar-h)+var(--diff-toolbar,0px))] z-20 flex items-center gap-2 border-line bg-surface/95 px-2 py-2 backdrop-blur sm:gap-2.5 sm:px-3 ${
437437 collapsed ? "rounded-xl" : "rounded-t-xl border-b"
438438 }`}
439439 >
869869 )}
870870 <div className="flex gap-4">
871871 {files.length > 1 && (
872− <aside className="sticky top-28 hidden max-h-[calc(100vh-8rem)] w-60 shrink-0 flex-col self-start xl:flex">
872+ <aside className="sticky top-[calc(var(--topbar-h)+3.5rem)] hidden max-h-[calc(100vh-var(--topbar-h)-4.5rem)] w-60 shrink-0 flex-col self-start xl:flex">
873873 <div className="relative">
874874 <Search size={13} className="pointer-events-none absolute top-1/2 left-2.5 -translate-y-1/2 text-faint" />
875875 <input
+328−0
1+import { Check, ChevronDown, CircleUserRound, LayoutGrid, Plus, Settings } from "lucide-react";
2+import { type ReactNode, useState } from "react";
3+import { Link, useLocation, useNavigation } from "react-router";
4+
5+import { type Membership, type User, hasCodeAccess, shownUsername } from "@g1t/contracts";
6+
7+import { AppsLauncher, appIcon, useAppPins } from "./apps";
8+import { Mark } from "./logo";
9+import { Avatar } from "./ui";
10+import { Hint } from "./ui/hint";
11+import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "./ui/dialog";
12+import {
13+ DropdownMenu,
14+ DropdownMenuContent,
15+ DropdownMenuItem,
16+ DropdownMenuLabel,
17+ DropdownMenuSeparator,
18+ DropdownMenuTrigger,
19+} from "./ui/dropdown-menu";
20+import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
21+import { type AppKey, type PinnableApp, appOf } from "../lib/apps";
22+import { cn } from "../lib/cn";
23+import { paletteKeyLabel } from "../lib/shortcut";
24+import { type ModeKey, modeHome, modeOf, todayPath } from "../lib/workspace-nav";
25+
26+/** The dock's column: 8px from the window's edge, 80px wide. */
27+export const DOCK_COLUMN = "5.5rem";
28+
29+type Item = { key: ModeKey; label: string; badge?: { count: number; loud: boolean } | null };
30+
31+/** A count on an icon: loud (the accent) for what waits on you, quiet for the rest. */
32+export function CountBadge({ count, loud, className }: { count: number; loud: boolean; className?: string }) {
33+ if (count <= 0) return null;
34+ return (
35+ <span
36+ className={cn(
37+ "absolute -top-1 -right-1.5 min-w-[1.125rem] rounded-full px-1 text-center text-[0.625rem] leading-[1.125rem] font-bold tabular-nums ring-2 ring-dock",
38+ loud ? "bg-accent text-bg" : "bg-line-strong text-fg",
39+ className,
40+ )}
41+ >
42+ {count > 99 ? "99+" : count}
43+ </span>
44+ );
45+}
46+
47+/** The square behind an icon in the dock: raised for the current one. */
48+function IconSquare({ current, children }: { current: boolean; children: ReactNode }) {
49+ return (
50+ <span
51+ className={cn(
52+ "relative flex size-9 items-center justify-center rounded-[11px] transition-colors",
53+ current
54+ ? "bg-raised text-fg shadow-[inset_0_0_0_1px_var(--color-line-strong)]"
55+ : "text-muted group-hover:bg-raised group-hover:text-fg group-data-[state=open]:bg-raised group-data-[state=open]:text-fg",
56+ )}
57+ >
58+ {children}
59+ </span>
60+ );
61+}
62+
63+/** The bar on the dock's left edge beside the current item. */
64+function CurrentBar() {
65+ return <span aria-hidden="true" className="absolute top-[9px] -left-1.5 h-5 w-[3px] rounded-r-[3px] bg-fg" />;
66+}
67+
68+// Each item fills the dock's width and centres its icon and name on it.
69+const ITEM = "group relative flex w-full flex-col items-center justify-center gap-0.5 rounded-lg py-0.5 text-center outline-none focus-visible:ring-2 focus-visible:ring-accent";
70+
71+function DockLink({ to, label, icon, current, badge, compact = false }: { to: string; label: string; icon: ReactNode; current: boolean; badge?: Item["badge"]; compact?: boolean }) {
72+ const link = (
73+ <Link
74+ to={to}
75+ prefetch="intent"
76+ aria-current={current ? "page" : undefined}
77+ aria-label={badge && badge.count > 0 ? `${label}, ${badge.count} unread` : label}
78+ className={ITEM}
79+ >
80+ {current && <CurrentBar />}
81+ <IconSquare current={current}>
82+ {icon}
83+ {badge && <CountBadge count={badge.count} loud={badge.loud} />}
84+ </IconSquare>
85+ {!compact && <DockLabel current={current}>{label}</DockLabel>}
86+ </Link>
87+ );
88+ // An icon alone says its name on hover.
89+ return compact ? (
90+ <Hint label={label} side="right">
91+ {link}
92+ </Hint>
93+ ) : (
94+ link
95+ );
96+}
97+
98+/** A name under an icon: centred on the dock, whatever its length. */
99+function DockLabel({ current, children }: { current: boolean; children: ReactNode }) {
100+ return (
101+ <span className={cn("block w-full truncate text-[0.625rem] leading-tight font-medium transition-colors", current ? "text-fg" : "text-faint group-hover:text-muted")}>
102+ {children}
103+ </span>
104+ );
105+}
106+
107+function displayName(membership: Membership): string {
108+ return membership.name?.trim() || membership.slug;
109+}
110+
111+/**
112+ * The workspace you are in and the way to another: its avatar, its name
113+ * and a chevron, opening your workspaces, a new one, and your own account.
114+ * `row` heads the sidebar; `compact` starts the page header's trail when
115+ * no sidebar is open.
116+ */
117+export function WorkspaceSwitcher({ user, workspace, compact = false }: { user: User; workspace: Membership; compact?: boolean }) {
118+ return (
119+ <DropdownMenu>
120+ <DropdownMenuTrigger
121+ aria-label={`${displayName(workspace)}: switch workspace`}
122+ className={cn(
123+ "flex min-w-0 items-center rounded-lg outline-none transition-colors hover:bg-raised focus-visible:ring-2 focus-visible:ring-accent data-[state=open]:bg-raised",
124+ compact ? "h-8 shrink-0 gap-1.5 px-1 max-sm:max-w-40" : "h-9 grow gap-2 px-1.5",
125+ )}
126+ >
127+ <Avatar name={workspace.slug} image={workspace.avatar} size={compact ? 22 : 26} square />
128+ <span className={cn("min-w-0 truncate font-semibold", compact ? "text-sm" : "text-[0.875rem]")}>{displayName(workspace)}</span>
129+ <ChevronDown size={14} className="shrink-0 text-faint" />
130+ </DropdownMenuTrigger>
131+ <DropdownMenuContent align="start" className="w-72 p-1.5">
132+ <DropdownMenuItem asChild className="gap-3 px-2 py-2">
133+ <Link to={`/${workspace.slug}/-/workspace`}>
134+ <Avatar name={workspace.slug} image={workspace.avatar} size={36} square />
135+ <span className="flex min-w-0 grow flex-col leading-tight">
136+ <span className="truncate text-sm font-medium text-fg">{displayName(workspace)}</span>
137+ <span className="truncate font-mono text-xs text-muted">g1t.sh/{workspace.slug}</span>
138+ <span className="mt-0.5 text-xs text-faint capitalize">{workspace.role}</span>
139+ </span>
140+ </Link>
141+ </DropdownMenuItem>
142+ <DropdownMenuSeparator className="my-1.5" />
143+ <DropdownMenuLabel>Switch workspace</DropdownMenuLabel>
144+ {(user.workspaces ?? []).map((membership) => (
145+ <DropdownMenuItem key={membership.slug} asChild>
146+ <Link to={todayPath(membership.slug)}>
147+ <Avatar name={membership.slug} image={membership.avatar} size={24} square />
148+ <span className="flex min-w-0 grow flex-col leading-tight">
149+ <span className="truncate">{displayName(membership)}</span>
150+ <span className="truncate font-mono text-[0.6875rem] text-faint">{membership.slug}</span>
151+ </span>
152+ {membership.slug === workspace.slug ? <Check className="shrink-0 text-accent" /> : <span className="size-4 shrink-0" aria-hidden="true" />}
153+ </Link>
154+ </DropdownMenuItem>
155+ ))}
156+ <DropdownMenuItem asChild>
157+ <Link to="/workspaces/new">
158+ <Plus />
159+ New workspace
160+ </Link>
161+ </DropdownMenuItem>
162+ <DropdownMenuSeparator className="my-1.5" />
163+ <DropdownMenuLabel>Personal account</DropdownMenuLabel>
164+ <DropdownMenuItem asChild>
165+ <Link to={`/u/${user.username}`}>
166+ <CircleUserRound />
167+ <span className="grow">Your profile</span>
168+ <span className="font-mono text-xs text-faint">@{shownUsername(user)}</span>
169+ </Link>
170+ </DropdownMenuItem>
171+ <DropdownMenuItem asChild>
172+ <Link to="/settings">
173+ <Settings />
174+ Your settings
175+ </Link>
176+ </DropdownMenuItem>
177+ </DropdownMenuContent>
178+ </DropdownMenu>
179+ );
180+}
181+
182+/** Keyboard shortcuts, as the account menu lists them. */
183+function shortcuts(palette: string, sidebar: string): [string, string][] {
184+ return [
185+ [palette, "Search, jump anywhere, or run a command"],
186+ [sidebar, "Show or hide the sidebar"],
187+ ["Esc", "Close a menu, dialog or panel"],
188+ ["Enter", "Send a message in Chat"],
189+ ["Shift Enter", "A new line in a message"],
190+ ["@", "Mention a person or an agent"],
191+ ["↑ ↓ then Tab", "Choose a suggestion"],
192+ ["Alt ↑ ↓", "Move a pinned project in Code's sidebar"],
193+ ];
194+}
195+
196+/** The sidebar's shortcut, as this computer writes it. */
197+export function sidebarKeyLabel(platform: string | null): string {
198+ return paletteKeyLabel(platform).replace(/K$/, "B");
199+}
200+
201+/** The keyboard's shortcuts, opened from the account menu. */
202+export function ShortcutsDialog({ open, onOpenChange }: { open: boolean; onOpenChange: (open: boolean) => void }) {
203+ const platform = typeof navigator === "undefined" ? null : navigator.platform;
204+ return (
205+ <Dialog open={open} onOpenChange={onOpenChange}>
206+ <DialogContent className="max-w-md">
207+ <DialogHeader>
208+ <DialogTitle>Keyboard shortcuts</DialogTitle>
209+ <DialogDescription>Everywhere in g1t, and in Chat.</DialogDescription>
210+ </DialogHeader>
211+ <dl className="divide-y divide-line rounded-lg border border-line">
212+ {shortcuts(paletteKeyLabel(platform), sidebarKeyLabel(platform)).map(([key, what]) => (
213+ <div key={key} className="flex items-center justify-between gap-4 px-3.5 py-2.5 text-sm">
214+ <dt className="text-fg-soft">{what}</dt>
215+ <dd>
216+ <kbd className="rounded-md border border-line-strong bg-raised px-1.5 py-0.5 font-sans text-xs whitespace-nowrap text-fg">{key}</kbd>
217+ </dd>
218+ </div>
219+ ))}
220+ </dl>
221+ </DialogContent>
222+ </Dialog>
223+ );
224+}
225+
226+/** The Apps button at the foot of the dock's apps, and the launcher it opens. */
227+function AppsButton({ slug, code, pins, onToggle, current }: { slug: string; code: boolean; pins: PinnableApp[]; onToggle: (app: PinnableApp) => void; current: boolean }) {
228+ const [open, setOpen] = useState(false);
229+ return (
230+ <Popover open={open} onOpenChange={setOpen}>
231+ <PopoverTrigger aria-label="Apps" className={ITEM}>
232+ {current && <CurrentBar />}
233+ <IconSquare current={current || open}>
234+ <LayoutGrid size={19} />
235+ </IconSquare>
236+ <DockLabel current={current || open}>Apps</DockLabel>
237+ </PopoverTrigger>
238+ <PopoverContent side="right" align="end" sideOffset={14} aria-label="Apps" className="w-[23rem] bg-dock p-2.5">
239+ <AppsLauncher slug={slug} code={code} pins={pins} onToggle={onToggle} onClose={() => setOpen(false)} />
240+ </PopoverContent>
241+ </Popover>
242+ );
243+}
244+
245+/**
246+ * The dock down the left (docs/WORKSPACE.md, "Shell"): g1t's mark, which
247+ * leads to Today; the built-in apps (Today, Chat, Notifications, Agents,
248+ * Code and Artifacts) with their names; the apps you pinned, as icons;
249+ * the Apps launcher; and at its foot People, Workspace and your account.
250+ * What is lit follows the address. A member without Code access has no
251+ * Code. From 768px; a phone has the bottom bar (components/mobile.tsx).
252+ */
253+export function Dock({
254+ workspace,
255+ pins: saved,
256+ unread,
257+ account,
258+}: {
259+ workspace: Membership;
260+ /** The apps pinned to this person's dock in this workspace, as saved. */
261+ pins: PinnableApp[];
262+ unread: { notifications: number; chat: number; mentions: number };
263+ account: ReactNode;
264+}) {
265+ const { pathname } = useLocation();
266+ const going = useNavigation().location?.pathname;
267+ const path = going ?? pathname;
268+ const here = modeOf(path, workspace.slug);
269+ const slug = workspace.slug;
270+ const code = hasCodeAccess(workspace);
271+ const { pins, toggle } = useAppPins(slug, saved);
272+ const builtins: Item[] = [
273+ { key: "today", label: "Today" },
274+ {
275+ key: "chat",
276+ label: "Chat",
277+ badge: { count: unread.mentions > 0 ? unread.mentions : unread.chat, loud: unread.mentions > 0 },
278+ },
279+ { key: "notifications", label: "Notifications", badge: { count: unread.notifications, loud: true } },
280+ { key: "agents", label: "Agents" },
281+ ...(code ? [{ key: "code" as const, label: "Code" }] : []),
282+ { key: "artifacts", label: "Artifacts" },
283+ ];
284+ const shown = pins.filter((key) => code || !appOf(key).code);
285+ // A pinned app is current on its own page and the pages under it.
286+ const at = (to: string) => path === to || path.startsWith(`${to}/`);
287+ return (
288+ <nav
289+ aria-label="Apps"
290+ className="flex h-full flex-col overflow-hidden rounded-2xl border border-line bg-dock shadow-[0_1px_0_rgba(255,255,255,0.03)_inset,0_8px_24px_rgba(0,0,0,0.18)]"
291+ >
292+ <div className="flex h-14 shrink-0 items-center justify-center">
293+ <Hint label="Today" side="right">
294+ <Link to={todayPath(slug)} aria-label="g1t: Today" className="flex size-9 items-center justify-center rounded-[11px] transition-colors hover:bg-raised">
295+ <Mark tight className="h-[1.125rem] w-auto" />
296+ </Link>
297+ </Hint>
298+ </div>
299+ <div className="flex w-full shrink-0 flex-col items-center gap-1 px-1.5 pb-1.5">
300+ {builtins.map((item) => (
301+ <DockLink
302+ key={item.key}
303+ to={modeHome(item.key, slug)}
304+ label={item.label}
305+ icon={appIcon(item.key as AppKey, 19)}
306+ current={here === item.key && !shown.some((key) => at(appOf(key).path(slug)))}
307+ badge={item.badge}
308+ />
309+ ))}
310+ </div>
311+ {/* Your pinned apps: as many as you like, scrolling under a fade at either end. */}
312+ <div className="flex min-h-0 w-full grow flex-col items-center gap-1 overflow-y-auto border-t border-line px-1.5 py-2 [mask-image:linear-gradient(to_bottom,transparent,#000_10px,#000_calc(100%-14px),transparent)] [scrollbar-width:none]">
313+ {shown.map((key) => {
314+ const app = appOf(key);
315+ return <DockLink key={key} to={app.path(slug)} label={app.name} icon={appIcon(key, 18)} current={at(app.path(slug))} compact />;
316+ })}
317+ </div>
318+ <div className="flex w-full shrink-0 flex-col items-center px-1.5 pb-1.5">
319+ <AppsButton slug={slug} code={code} pins={pins} onToggle={toggle} current={here === "apps"} />
320+ </div>
321+ <div className="flex w-full shrink-0 flex-col items-center gap-1 border-t border-line px-1.5 pt-2 pb-2.5">
322+ <DockLink to={modeHome("people", slug)} label="People" icon={appIcon("people", 19)} current={here === "people"} />
323+ <DockLink to={modeHome("workspace", slug)} label="Workspace" icon={appIcon("workspace", 19)} current={here === "workspace"} />
324+ <div className="mt-1.5">{account}</div>
325+ </div>
326+ </nav>
327+ );
328+}
+1−1
316316 </div>
317317 {showComments && (
318318 <aside ref={comments} aria-label="Comments" className="scroll-mt-20 max-xl:border-t max-xl:border-line max-xl:pt-6">
319− <div className="xl:sticky xl:top-20 xl:max-h-[calc(100dvh-6rem)] xl:overflow-y-auto xl:[scrollbar-width:thin]">
319+ <div className="xl:sticky xl:top-[calc(var(--topbar-h)+1.5rem)] xl:max-h-[calc(100dvh-var(--topbar-h)-2.5rem)] xl:overflow-y-auto xl:[scrollbar-width:thin]">
320320 <ThreadsSidebar filter="all" sort="position" />
321321 </div>
322322 </aside>
+1−1
9595 <DialogContent className="max-w-xl">
9696 <DialogHeader>
9797 <DialogTitle className="truncate pr-6">Share “{folio.title || "Untitled"}”</DialogTitle>
98− <DialogDescription>{canShare ? "People you add get an inbox item with a link." : `Only people with full access can change who can open it. Ask ${access?.owner.display_name ?? "its owner"} to share it.`}</DialogDescription>
98+ <DialogDescription>{canShare ? "People you add get a notification with a link." : `Only people with full access can change who can open it. Ask ${access?.owner.display_name ?? "its owner"} to share it.`}</DialogDescription>
9999 </DialogHeader>
100100
101101 {canShare && (
+2−2
363363 };
364364 return (
365365 <div className="flex h-full flex-col">
366− <div className="flex h-14 shrink-0 items-center gap-1 border-b border-line pr-2.5 pl-4">
367− <h2 className="min-w-0 grow truncate text-[0.9375rem] font-semibold">Artifacts</h2>
366+ <div className="flex h-9 shrink-0 items-center gap-1 pr-1 pl-3">
367+ <h2 className="min-w-0 grow truncate text-xs font-medium text-faint">Artifacts</h2>
368368 <NewMenu onMake={make}>
369369 <button type="button" aria-label="New" className="flex h-8 items-center gap-0.5 rounded-md px-1.5 text-faint transition-colors hover:bg-raised hover:text-fg">
370370 <Plus size={16} />
+0−157
1−import { Code2, MessagesSquare, Search, Sparkles } from "lucide-react";
2−import type { ReactNode } from "react";
3−import { NavLink } from "react-router";
4−
5−import type { ChatSidebarEntry } from "@g1t/contracts";
6−
7−import { PaletteKey } from "./command-palette";
8−import { StatusDot } from "./chat/marks";
9−import { isOrchestrator } from "./orchestrator";
10−import type { ShellAgent, ShellData } from "./shell";
11−import { channelPath } from "../lib/chat";
12−
13−/** Most of each kind under Recent. */
14−const RECENT_CHATS = 5;
15−const RECENT_PROJECTS = 4;
16−const RECENT_AGENTS = 3;
17−
18−type Item = { key: string; to: string; label: string; mode: "chat" | "code" | "agents"; trailing?: ReactNode };
19−
20−const MODE_ICON: Record<Item["mode"], ReactNode> = {
21− chat: <MessagesSquare size={15} />,
22− code: <Code2 size={15} />,
23− agents: <Sparkles size={15} />,
24−};
25−const MODE_WORD: Record<Item["mode"], string> = { chat: "Chat", code: "Code", agents: "Agents" };
26−
27−function Row({ item }: { item: Item }) {
28− return (
29− <li>
30− <NavLink
31− to={item.to}
32− prefetch="intent"
33− className={({ isActive }) =>
34− `group flex h-8 items-center gap-2.5 rounded-md px-2 text-[0.8125rem] transition-colors ${
35− isActive ? "bg-raised font-medium text-fg" : "text-muted hover:bg-raised/60 hover:text-fg"
36− }`
37− }
38− >
39− <span className="shrink-0 text-faint group-hover:text-muted" aria-hidden="true">
40− {MODE_ICON[item.mode]}
41− </span>
42− <span className="sr-only">{MODE_WORD[item.mode]}: </span>
43− <span className="min-w-0 grow truncate">{item.label}</span>
44− {item.trailing}
45− </NavLink>
46− </li>
47− );
48−}
49−
50−function Group({ title, items, empty }: { title: string; items: Item[]; empty: string }) {
51− return (
52− <section className="mt-4">
53− <h3 className="mb-1 px-2 text-xs font-medium text-faint">{title}</h3>
54− {items.length === 0 ? <p className="px-2 py-1 text-xs text-faint">{empty}</p> : <ul className="space-y-px">{items.map((item) => <Row key={item.key} item={item} />)}</ul>}
55− </section>
56− );
57−}
58−
59−function chatItem(slug: string, entry: ChatSidebarEntry): Item {
60− const count = entry.mentions || entry.unread;
61− return {
62− key: `chat:${entry.channel.id}`,
63− to: channelPath(slug, entry.channel),
64− label: entry.channel.kind === "channel" ? `#${entry.channel.name}` : entry.title,
65− mode: "chat",
66− trailing:
67− count > 0 && !entry.muted ? (
68− <span
69− className={`rounded-full px-1.5 text-[0.6875rem] leading-[1.125rem] font-semibold tabular-nums ${entry.mentions ? "bg-accent text-bg" : "bg-line-strong text-fg"}`}
70− >
71− {count}
72− </span>
73− ) : null,
74− };
75−}
76−
77−function agentItem(slug: string, agent: ShellAgent): Item {
78− return {
79− key: `agent:${agent.id}`,
80− to: `/${slug}/-/agents/${agent.handle}`,
81− label: agent.display_name,
82− mode: "agents",
83− trailing: isOrchestrator(agent) ? null : <StatusDot status={agent.status} />,
84− };
85−}
86−
87−/**
88− * Home's sidebar: never Code's. A way to search or jump, what you pinned
89− * across modes (starred channels and conversations, pinned projects, and
90− * docs once they exist), and what you were in last, each marked with its
91− * mode.
92− */
93−export function HomeSidebar({
94− slug,
95− shell,
96− code,
97− onFind,
98− header,
99−}: {
100− slug: string;
101− shell: ShellData;
102− /** Whether the viewer uses Code here: without it, no projects. */
103− code: boolean;
104− onFind: () => void;
105− header: ReactNode;
106−}) {
107− const chat = shell.chat;
108− const starredChats = chat?.starred ?? [];
109− const starred: Item[] = [
110− ...starredChats.map((entry) => chatItem(slug, entry)),
111− ...(code ? (shell.pinned ?? []) : []).map((project) => ({
112− key: `project:${project.namespace}/${project.name}`,
113− to: `/${project.namespace}/${project.name}`,
114− label: project.title ?? project.name,
115− mode: "code" as const,
116− })),
117− ];
118− const starredIds = new Set(starredChats.map((entry) => entry.channel.id));
119− const pinnedProjects = new Set((shell.pinned ?? []).map((project) => `${project.namespace}/${project.name}`));
120− const agents = shell.agents ?? [];
121− const recent: Item[] = [
122− ...(chat?.recent ?? []).filter((entry) => !starredIds.has(entry.channel.id)).slice(0, RECENT_CHATS).map((entry) => chatItem(slug, entry)),
123− ...(code ? (shell.recent ?? []) : [])
124− .filter((project) => !pinnedProjects.has(`${project.namespace}/${project.name}`))
125− .slice(0, RECENT_PROJECTS)
126− .map((project) => ({
127− key: `project:${project.namespace}/${project.name}`,
128− to: `/${project.namespace}/${project.name}`,
129− label: project.title ?? project.name,
130− mode: "code" as const,
131− })),
132− ...[...agents]
133− .sort((a, b) => Number(isOrchestrator(b)) - Number(isOrchestrator(a)) || Number(b.status !== "idle") - Number(a.status !== "idle"))
134− .slice(0, RECENT_AGENTS)
135− .map((agent) => agentItem(slug, agent)),
136− ];
137− return (
138− <div className="flex h-full flex-col">
139− {header}
140− <div className="px-2.5 pt-3">
141− <button
142− type="button"
143− onClick={onFind}
144− className="flex h-9 w-full items-center gap-2 rounded-md bg-surface px-2.5 text-[0.8125rem] text-faint ring-1 ring-line transition-colors hover:text-muted hover:ring-line-strong"
145− >
146− <Search size={14} />
147− <span className="grow text-left">Search or jump to…</span>
148− <PaletteKey className="rounded bg-raised px-1.5 font-mono text-[0.625rem] text-muted ring-1 ring-line" />
149− </button>
150− </div>
151− <nav aria-label="Home" className="min-h-0 grow overflow-y-auto px-2.5 pb-4 [scrollbar-width:thin]">
152− <Group title="Starred" items={starred} empty="Star a channel or pin a project to keep it here." />
153− <Group title="Recent" items={recent} empty={chat == null ? "Recent conversations show here once chat answers." : "Nothing yet."} />
154− </nav>
155− </div>
156− );
157−}
+22−58
1−import { AlarmClock, Bell, Bookmark, Check, CheckCheck, CircleCheck, CircleX, Ellipsis, Hand, Inbox, Info, Mail, MailOpen, Sparkles, Undo2 } from "lucide-react";
1+import { AlarmClock, Bell, Bookmark, Check, CheckCheck, CircleCheck, CircleX, Ellipsis, Hand, Info, Mail, MailOpen, Undo2 } from "lucide-react";
22 import { type ReactNode, useEffect, useState } from "react";
33 import { Link, useFetcher } from "react-router";
44
1212 import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "./ui/sheet";
1313 import { Skeleton } from "./ui/skeleton";
1414 import { Tabs, TabsList, TabsTrigger } from "./ui/tabs";
15−import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
1615 import { cn } from "../lib/cn";
1716 import { isPending } from "../lib/pending";
1817 import { INBOX_TABS, type InboxTab, REASON_LABEL, SEVERITY_LABEL, SNOOZES, bellCount, emptyFor, isUnread, tabCount, updatesLabel, whenShort } from "../lib/inbox";
1918 import type { InboxPanelData } from "../routes/inbox-json";
2019
21−/** Where every inbox form posts (routes/inbox.tsx). */
22−const ACTION = "/inbox";
20+/** Where every notifications form posts (routes/notifications.tsx). */
21+const ACTION = "/notifications";
2322 /** How often the bell asks for its count while the page is in view. */
2423 const COUNT_EVERY_MS = 60_000;
2524
9392 <Link
9493 to={item.url}
9594 onClick={open}
96− className="block truncate pr-14 text-sm font-semibold text-fg outline-none after:absolute after:inset-0 after:rounded-lg focus-visible:after:ring-2 focus-visible:after:ring-accent"
95+ className="block truncate pr-14 text-sm font-semibold text-fg outline-none [@media(hover:none)]:pr-24 after:absolute after:inset-0 after:rounded-lg focus-visible:after:ring-2 focus-visible:after:ring-accent"
9796 >
9897 {item.title}
9998 </Link>
10099 {item.body && <p className="mt-0.5 truncate text-xs text-muted">{item.body}</p>}
101− <div className="mt-2 flex min-w-0 items-center gap-2 text-xs text-faint">
100+ <div className="mt-2 flex min-w-0 items-center gap-2 text-xs text-faint [@media(hover:none)]:pr-24">
102101 <time dateTime={item.updatedAt} suppressHydrationWarning className="shrink-0 whitespace-nowrap">
103102 {whenShort(item.updatedAt, now)}
104103 </time>
126125 {item.doneAt ? (
127126 <fetcher.Form method="post" action={ACTION}>
128127 <Fields intent="undone" id={item.id} />
129− <Hint label="Move back to the inbox">
130− <SubmitButton fetcher={fetcher} icon aria-label="Move back to the inbox" className={ICON_BUTTON}>
128+ <Hint label="Move back to Notifications">
129+ <SubmitButton fetcher={fetcher} icon aria-label="Move back to Notifications" className={ICON_BUTTON}>
131130 <Undo2 size={14} />
132131 </SubmitButton>
133132 </Hint>
182181 const { title, detail } = emptyFor(tab, view);
183182 return (
184183 <div className="flex flex-col items-center gap-2 rounded-lg border border-dashed border-line px-6 py-12 text-center">
185− <Inbox size={20} className="text-faint" aria-hidden="true" />
184+ <Bell size={20} className="text-faint" aria-hidden="true" />
186185 <p className="text-sm font-medium">{title}</p>
187186 <p className="max-w-64 text-xs text-muted">{detail}</p>
188187 </div>
239238
240239 /**
241240 * The bell in the top bar, and the panel it opens from the right: the
242− * inbox's tabs and newest items, to work through without leaving the page.
241+ * notifications's tabs and newest items, to work through without leaving the page.
243242 * Its count comes with the page (root.tsx), and is asked for again every
244243 * minute while the page is in view, and after anything is marked.
245244 */
246−export function InboxBell({ counts: loaded }: { counts: InboxCounts | null }) {
245+export function NotificationsBell({ counts: loaded }: { counts: InboxCounts | null }) {
247246 const [open, setOpen] = useState(false);
248247 const [tab, setTab] = useState<InboxTab>("all");
249248 const list = useFetcher<InboxPanelData>({ key: "inbox-panel" });
274273 return (
275274 <Sheet open={open} onOpenChange={setOpen}>
276275 <SheetTrigger
277− aria-label={unread ? `Inbox, ${unread} unread` : "Inbox"}
276+ aria-label={unread ? `Notifications, ${unread} unread` : "Notifications"}
278277 className="relative flex size-9 items-center justify-center rounded-md text-muted transition-colors hover:bg-raised hover:text-fg"
279278 >
280279 <Bell size={17} />
298297 }}
299298 >
300299 <SheetHeader className="flex-row items-center justify-between gap-3">
301− <SheetTitle>Inbox</SheetTitle>
300+ <SheetTitle>Notifications</SheetTitle>
302301 <MarkAllRead tab={tab} disabled={tabCount(counts, tab) === 0} />
303302 </SheetHeader>
304303 <div className="border-b border-line px-5 py-3">
309308 <WaitingCards onNavigate={() => setOpen(false)} />
310309 {items == null ? (
311310 list.state === "idle" && list.data?.tab === tab ? (
312− <p className="text-sm text-muted">The inbox could not be loaded. Try again in a moment.</p>
311+ <p className="text-sm text-muted">Notifications could not be loaded. Try again in a moment.</p>
313312 ) : (
314313 <ul aria-busy="true" className="space-y-2">
315314 {Array.from({ length: 4 }, (_, index) => (
334333 )}
335334 </div>
336335 <SheetFooter className="justify-between">
337− <SheetDescription className="text-xs">Done, saved and snoozed items are in the full inbox.</SheetDescription>
336+ <SheetDescription className="text-xs">Done, saved and snoozed items are on the Notifications page.</SheetDescription>
338337 <SheetClose asChild>
339− <Link to={tab === "all" ? "/inbox" : `/inbox?tab=${tab}`} className="shrink-0 text-sm font-medium text-accent hover:underline">
340− Open inbox
338+ <Link to={tab === "all" ? "/notifications" : `/notifications?tab=${tab}`} className="shrink-0 text-sm font-medium text-accent hover:underline">
339+ Open Notifications
341340 </Link>
342341 </SheetClose>
343342 </SheetFooter>
347346 }
348347
349348 /**
350− * Agent: g1t's agent to talk to, here to say it is coming, and not yet usable. A disabled button
351− * gets no pointer events, so the tooltip hangs on a span around it: shown on hover and focus,
352− * and on a tap, since a touch screen has no hover.
353− */
354−export function AgentButton() {
355− const [open, setOpen] = useState(false);
356− return (
357− <Tooltip open={open} onOpenChange={setOpen}>
358− <TooltipTrigger asChild>
359− <span
360− tabIndex={0}
361− // A tap opens it; without preventDefault the trigger's own click would close it again.
362− onClick={(event) => {
363− event.preventDefault();
364− setOpen(true);
365− }}
366− className="inline-flex rounded-md outline-none focus-visible:ring-2 focus-visible:ring-accent"
367− >
368− <button
369− type="button"
370− disabled
371− aria-label="Agent"
372− className="pointer-events-none flex h-9 items-center gap-1.5 rounded-md border border-line px-2.5 text-sm text-muted opacity-60 sm:px-3"
373− >
374− <Sparkles size={15} className="text-accent" />
375− <span className="hidden sm:inline">Agent</span>
376− </button>
377− </span>
378− </TooltipTrigger>
379− <TooltipContent>Agent is coming soon</TooltipContent>
380− </Tooltip>
381− );
382−}
383−
384−/**
385− * Mission control's card of what is unread in the person's inbox and
349+ * Mission control's card of what is unread in the person's notifications and
386350 * worth a look: items an agent is waiting on, then failures. Titled for
387− * the inbox, not "Needs you", which on mission control is the work list's
351+ * notifications, not "Needs you", which on mission control is the work list's
388352 * own count. Not shown when there are none.
389353 */
390354 export function InboxNeedsCard({ items, total }: { items: InboxItem[]; total: number }) {
393357 <section className="rounded-xl border border-line bg-surface p-5">
394358 <div className="flex items-center justify-between gap-3">
395359 <h2 className="flex items-center gap-2 text-sm font-semibold">
396− <Inbox size={14} className="text-warn" aria-hidden="true" />
397− From your inbox
360+ <Bell size={14} className="text-warn" aria-hidden="true" />
361+ From your notifications
398362 <span className="rounded-full bg-warn/15 px-1.5 text-[0.6875rem] font-medium tabular-nums text-warn">{total}</span>
399363 </h2>
400− <Link to="/inbox" className="text-xs font-medium text-accent hover:underline">
401− Open inbox
364+ <Link to="/notifications" className="text-xs font-medium text-accent hover:underline">
365+ Open Notifications
402366 </Link>
403367 </div>
404368 <ul className="mt-3 space-y-2">
+3−3
88 Eye,
99 FileLock2,
1010 Handshake,
11− Inbox,
11+ Bell,
1212 KeyRound,
1313 Lock,
1414 MessagesSquare,
561561 "Live delivery, typing and read state",
562562 { text: "Agents that look up code, issues and checks as they answer", soon: true },
563563 { text: "Pull requests, checks and deploys as cards in the channel", soon: true },
564− { text: "Mentions and approvals in your inbox", soon: true },
564+ { text: "Mentions and approvals in Notifications", soon: true },
565565 { text: "Desktop and mobile apps", soon: true },
566566 ]}
567567 />
954954 <div className="flex flex-col justify-between gap-6">
955955 <ul className="space-y-3 text-sm">
956956 {[
957− { icon: <Inbox size={15} />, text: "One inbox for mentions, reviews and approvals" },
957+ { icon: <Bell size={15} />, text: "One place for mentions, reviews and approvals" },
958958 { icon: <ScrollText size={15} />, text: "One audit log for people and agents" },
959959 { icon: <Wallet size={15} />, text: "One bill, at cost plus 20%" },
960960 ].map((item) => (
+1−1
514514 >
515515 <RadioGroup name="notify" defaultValue={settings.notify} className="gap-3">
516516 <RadioOption value="banner" label="Show it on the repository" description={`A line across ${full}'s pages, and an amber dot beside its name.`} />
517− <RadioOption value="inbox" label="Also tell workspace owners in their inbox" description="Once when it stops answering, and once when it answers again." />
517+ <RadioOption value="inbox" label="Also tell workspace owners in their notifications" description="Once when it stops answering, and once when it answers again." />
518518 </RadioGroup>
519519 <div className="rounded-xl border border-line bg-surface p-4">
520520 <div className="flex flex-wrap items-center gap-x-2.5 gap-y-2">
+209−161
11 import {
22 Activity,
3− BarChart3,
43 BellOff,
54 BellRing,
65 BookOpen,
7− Building2,
8− Check,
9− ChevronRight,
106 CircleUserRound,
11− Code2,
12− House,
13− Inbox,
7+ Ellipsis,
8+ Keyboard,
9+ LayoutGrid,
1410 LifeBuoy,
1511 LogOut,
16− MessagesSquare,
1712 Moon,
18− Plug,
19− Plus,
2013 Settings,
21− Shapes,
2214 Smile,
23− Sparkles,
15+ Store,
2416 Sun,
25− Users,
26− UsersRound,
2717 } from "lucide-react";
2818 import { Dialog as Primitive } from "radix-ui";
2919 import { type ReactNode, useEffect, useRef, useState } from "react";
3121
3222 import { type Abilities, type Membership, type User, hasCodeAccess, shownUsername } from "@g1t/contracts";
3323
24+import { appIcon } from "./apps";
25+import { CountBadge, ShortcutsDialog } from "./dock";
26+import { Mark } from "./logo";
3427 import { Avatar } from "./ui";
3528 import { TabStrip } from "./ui/tab-strip";
3629 import { StatusDialog } from "./presence";
30+import { type AppKey, type PinnableApp, appOf } from "../lib/apps";
3731 import { setPresence, useOwnPresence } from "../lib/notify-client";
3832 import { dndOn, liveStatus, pauseUntil, untilLabel } from "../lib/presence";
3933 import { STATUS_URL } from "../lib/status";
40−import { modeOf } from "../lib/workspace-nav";
34+import { type ModeKey, modeHome, modeOf } from "../lib/workspace-nav";
4135 import { PROJECT_PAGE_LINKS, type ProjectPage, projectPageAt, projectPages } from "../lib/chrome";
4236 import { ROADMAP, type RoadmapItem } from "../lib/roadmap";
4337
4438 /**
45− * The phone's layout (below 768px): a tab bar along the bottom, the
46− * workspace's avatar opening a sheet for everything else, and each tab a
47− * list that pushes its detail full screen. Desktop keeps the rail.
39+ * The phone's layout (below 768px): a bottom bar of apps with More for
40+ * the rest, the mode's sidebar as a drawer from the left, and each tab a
41+ * list that pushes its detail full screen. A computer has the dock.
4842 */
4943
5044 /** Whether a path is one conversation, which takes the whole screen on a phone. */
121115 };
122116 }
123117
124−type Tab = { key: string; label: string; to: string; icon: ReactNode; badge?: { count: number; loud: boolean } };
118+type Tab = { key: ModeKey; label: string; badge?: { count: number; loud: boolean } };
125119
126120 /**
127− * The tab bar along the bottom of a phone: Home, Code (Docs, for a member
128− * without Code), Chat, Agents and the Inbox, each with what is unread. It
129− * steps aside while the keyboard is up and inside a conversation. The tab
130− * you are in, tapped again, opens its mode's menu.
121+ * The bottom bar of a phone, floating 8px from the edges: Today, Chat,
122+ * Notifications, Agents and Code (for a member with Code access), each
123+ * with what is unread, and More. It steps aside while the keyboard is up
124+ * and inside a conversation. The tab you are in, tapped again, opens its
125+ * mode's menu (its sidebar).
131126 */
132−export function MobileTabBar({
127+export function BottomBar({
128+ user,
133129 workspace,
130+ pins,
134131 unread,
135132 onReselect,
136133 }: {
134+ user: User;
137135 workspace: Membership;
138− unread: { inbox: number; chat: number; mentions: number };
136+ /** The apps pinned to this person's dock here, for the More sheet. */
137+ pins: PinnableApp[];
138+ unread: { notifications: number; chat: number; mentions: number };
139139 /** Tapping the tab you are already in: the shell opens that mode's menu (its sidebar). */
140140 onReselect?: () => void;
141141 }) {
142142 const { pathname } = useLocation();
143+ const [more, setMore] = useState(false);
144+ useEffect(() => setMore(false), [pathname]);
143145 const slug = workspace.slug;
144146 const code = hasCodeAccess(workspace);
145147 const mode = modeOf(pathname, slug);
146148 const tabs: Tab[] = [
147− { key: "home", label: "Home", to: `/${slug}/-/home`, icon: <House size={21} /> },
148− code
149− ? { key: "code", label: "Code", to: `/${slug}/-/projects`, icon: <Code2 size={21} /> }
150− : { key: "artifacts", label: "Artifacts", to: `/${slug}/-/artifacts`, icon: <Shapes size={21} /> },
151− {
152− key: "chat",
153− label: "Chat",
154− to: `/${slug}/-/chat`,
155− icon: <MessagesSquare size={21} />,
156− badge: { count: unread.mentions > 0 ? unread.mentions : unread.chat, loud: unread.mentions > 0 },
157− },
158− { key: "agents", label: "Agents", to: `/${slug}/-/agents`, icon: <Sparkles size={21} /> },
159− { key: "inbox", label: "Inbox", to: "/inbox", icon: <Inbox size={21} />, badge: { count: unread.inbox, loud: true } },
149+ { key: "today", label: "Today" },
150+ { key: "chat", label: "Chat", badge: { count: unread.mentions > 0 ? unread.mentions : unread.chat, loud: unread.mentions > 0 } },
151+ { key: "notifications", label: "Notifications", badge: { count: unread.notifications, loud: true } },
152+ { key: "agents", label: "Agents" },
153+ ...(code ? [{ key: "code" as const, label: "Code" }] : []),
160154 ];
155+ const inTabs = tabs.some((tab) => tab.key === mode);
161156 if (isConversation(pathname)) return null;
162157 return (
163− <nav
164− aria-label="Tabs"
165− className="fixed inset-x-0 bottom-0 z-40 border-t border-line bg-[#0b0b0d]/95 pb-[env(safe-area-inset-bottom)] backdrop-blur md:hidden in-data-[keyboard=open]:hidden"
166− >
167− <ul className="grid h-14 grid-cols-5">
168− {tabs.map((tab) => {
169− const current = mode === tab.key;
170− const count = tab.badge?.count ?? 0;
171− return (
172− <li key={tab.key}>
173− <Link
174− to={tab.to}
175− prefetch="intent"
176− aria-current={current ? "page" : undefined}
177− aria-label={count > 0 ? `${tab.label}, ${count} unread` : tab.label}
178− onClick={(event) => {
179− // The tab you are in, tapped again: its menu, as a phone's own apps do.
180− // Chat's first page on a phone is its sidebar already.
181− if (!current || !onReselect || (tab.key === "chat" && pathname.replace(/\/$/, "") === tab.to)) return;
182− event.preventDefault();
183− onReselect();
158+ <>
159+ <nav
160+ aria-label="Apps"
161+ className="fixed right-2 bottom-[calc(0.5rem+env(safe-area-inset-bottom))] left-2 z-40 h-16 rounded-2xl border border-line bg-dock px-1 shadow-[0_1px_0_rgba(255,255,255,0.03)_inset,0_8px_24px_rgba(0,0,0,0.35)] md:hidden in-data-[keyboard=open]:hidden"
162+ >
163+ <ul className="grid h-full" style={{ gridTemplateColumns: `repeat(${tabs.length + 1}, minmax(0, 1fr))` }}>
164+ {tabs.map((tab) => {
165+ const current = mode === tab.key && !more;
166+ const count = tab.badge?.count ?? 0;
167+ const to = modeHome(tab.key, slug);
168+ return (
169+ <li key={tab.key} className="min-w-0">
170+ <Link
171+ to={to}
172+ prefetch="intent"
173+ aria-current={current ? "page" : undefined}
174+ aria-label={count > 0 ? `${tab.label}, ${count} unread` : tab.label}
175+ onClick={(event) => {
176+ // The tab you are in, tapped again: its menu, as a phone's own apps do.
177+ // Chat's first page on a phone is its sidebar already.
178+ if (!current || !onReselect || (tab.key === "chat" && pathname.replace(/\/$/, "") === to)) return;
179+ event.preventDefault();
180+ onReselect();
181+ }}
182+ className={BAR_ITEM}
183+ >
184+ {current && <TopBar />}
185+ <BarIcon current={current}>
186+ {appIcon(tab.key as AppKey, 20)}
187+ {tab.badge && <CountBadge count={count} loud={tab.badge.loud} />}
188+ </BarIcon>
189+ <span className={`w-full truncate text-[0.625rem] leading-tight font-medium max-[380px]:text-[0.5625rem] max-[380px]:tracking-[-0.02em] ${current ? "text-fg" : "text-faint"}`}>{tab.label}</span>
190+ </Link>
191+ </li>
192+ );
193+ })}
194+ <li className="min-w-0">
195+ <button type="button" aria-expanded={more} aria-haspopup="dialog" onClick={() => setMore(true)} className={BAR_ITEM}>
196+ {(more || !inTabs) && <TopBar />}
197+ <BarIcon current={more || !inTabs}>
198+ <Ellipsis size={20} />
199+ </BarIcon>
200+ <span className={`w-full truncate text-[0.625rem] leading-tight font-medium ${more || !inTabs ? "text-fg" : "text-faint"}`}>More</span>
201+ </button>
202+ </li>
203+ </ul>
204+ </nav>
205+ <MoreSheet open={more} onOpenChange={setMore} user={user} workspace={workspace} pins={pins} />
206+ </>
207+ );
208+}
209+
210+const BAR_ITEM = "relative flex h-full w-full flex-col items-center justify-center gap-0.5 rounded-xl outline-none transition-colors focus-visible:ring-2 focus-visible:ring-accent active:bg-raised/60";
211+
212+/** The bar on the bottom bar's top edge, over the current tab. */
213+function TopBar() {
214+ return <span aria-hidden="true" className="absolute -top-px left-1/2 h-[3px] w-[22px] -translate-x-1/2 rounded-b-[3px] bg-fg" />;
215+}
216+
217+function BarIcon({ current, children }: { current: boolean; children: ReactNode }) {
218+ return (
219+ <span className={`relative flex size-8 items-center justify-center rounded-[10px] transition-colors ${current ? "bg-raised text-fg shadow-[inset_0_0_0_1px_var(--color-line-strong)]" : "text-muted"}`}>
220+ {children}
221+ </span>
222+ );
223+}
224+
225+/** A big tile in the More sheet. */
226+function MoreTile({ to, icon, label, onClick, disabled, note }: { to?: string; icon: ReactNode; label: string; onClick?: () => void; disabled?: boolean; note?: string }) {
227+ const inner = (
228+ <>
229+ <span className={`flex size-11 items-center justify-center rounded-xl ${disabled ? "bg-raised/60 text-faint" : "bg-raised text-fg"}`}>{icon}</span>
230+ <span className={`line-clamp-2 w-full text-xs leading-tight max-[380px]:text-[0.6875rem] ${disabled ? "text-faint" : "text-fg-soft"}`}>{label}</span>
231+ {note && <span className="-mt-0.5 rounded-full bg-line px-1.5 text-[0.625rem] font-medium text-muted">{note}</span>}
232+ </>
233+ );
234+ const className = "flex min-w-0 flex-col items-center gap-1.5 rounded-xl px-1 py-2.5 text-center outline-none focus-visible:ring-2 focus-visible:ring-accent active:bg-raised/60";
235+ if (to) {
236+ return (
237+ <Link to={to} className={className}>
238+ {inner}
239+ </Link>
240+ );
241+ }
242+ return (
243+ <button type="button" onClick={onClick} disabled={disabled} aria-disabled={disabled} className={className}>
244+ {inner}
245+ </button>
246+ );
247+}
248+
249+/**
250+ * More, from the bottom bar: a rounded panel just above it, with g1t's
251+ * mark at its top and a grid of big tiles: all apps, the Marketplace
252+ * (coming), your pinned apps, Artifacts, People, Workspace, and "You and
253+ * help", which opens your account's sheet.
254+ */
255+function MoreSheet({ open, onOpenChange, user, workspace, pins }: { open: boolean; onOpenChange: (open: boolean) => void; user: User; workspace: Membership; pins: PinnableApp[] }) {
256+ const slug = workspace.slug;
257+ const code = hasCodeAccess(workspace);
258+ const [account, setAccount] = useState(false);
259+ const shown = pins.filter((key) => code || !appOf(key).code);
260+ return (
261+ <>
262+ <Primitive.Root open={open} onOpenChange={onOpenChange}>
263+ <Primitive.Portal>
264+ <Primitive.Overlay className="fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-fade-in data-[state=closed]:animate-fade-out md:hidden" />
265+ <Primitive.Content
266+ aria-describedby={undefined}
267+ onOpenAutoFocus={(event) => event.preventDefault()}
268+ className="fixed right-2 bottom-[calc(4.875rem+env(safe-area-inset-bottom))] left-2 z-50 max-h-[calc(100dvh-7rem)] overflow-y-auto rounded-[18px] border border-line-strong bg-dock px-3 pt-2 pb-3.5 text-fg shadow-2xl shadow-black/60 outline-none data-[state=open]:animate-pop-in data-[state=closed]:animate-pop-out md:hidden"
269+ >
270+ <div aria-hidden="true" className="mx-auto mt-0.5 mb-2 h-1 w-9 rounded-full bg-line-strong" />
271+ <div className="mb-2 flex items-center gap-2 px-1">
272+ <Mark tight className="h-4 w-auto" />
273+ <Primitive.Title className="text-sm font-semibold">More</Primitive.Title>
274+ <span className="ml-auto truncate font-mono text-xs text-faint">{slug}</span>
275+ </div>
276+ <div className="grid grid-cols-4 gap-1.5">
277+ <MoreTile to={`/${slug}/-/apps`} icon={<LayoutGrid size={20} />} label="All apps" />
278+ <MoreTile icon={<Store size={20} />} label="Marketplace" disabled note="Coming" />
279+ {shown.map((key) => (
280+ <MoreTile key={key} to={appOf(key).path(slug)} icon={appIcon(key, 20)} label={appOf(key).name} />
281+ ))}
282+ <MoreTile to={appOf("artifacts").path(slug)} icon={appIcon("artifacts", 20)} label="Artifacts" />
283+ <MoreTile to={appOf("people").path(slug)} icon={appIcon("people", 20)} label="People" />
284+ <MoreTile to={appOf("workspace").path(slug)} icon={appIcon("workspace", 20)} label="Workspace" />
285+ <MoreTile
286+ icon={<Avatar name={user.username} image={user.avatar} size={24} />}
287+ label="You and help"
288+ onClick={() => {
289+ onOpenChange(false);
290+ setAccount(true);
184291 }}
185− className="flex h-full flex-col items-center justify-center gap-0.5 transition-colors active:bg-raised/60"
186− >
187− <span className={`relative flex h-7 w-12 items-center justify-center rounded-full transition-colors ${current ? "bg-[#2c2c33] text-fg" : "text-muted"}`}>
188− {tab.icon}
189− {count > 0 && (
190− <span
191− className={`absolute -top-1 right-0.5 min-w-[1.125rem] rounded-full px-1 text-center text-[0.625rem] leading-[1.125rem] font-bold tabular-nums ring-2 ring-[#0b0b0d] ${
192− tab.badge?.loud ? "bg-accent text-bg" : "bg-[#3d3d45] text-fg"
193− }`}
194− >
195− {count > 99 ? "99+" : count}
196− </span>
197− )}
198− </span>
199− <span className={`text-[0.6875rem] font-medium ${current ? "text-fg" : "text-faint"}`}>{tab.label}</span>
200− </Link>
201− </li>
202− );
203− })}
204− </ul>
205− </nav>
292+ />
293+ </div>
294+ </Primitive.Content>
295+ </Primitive.Portal>
296+ </Primitive.Root>
297+ <AccountSheet open={account} onOpenChange={setAccount} user={user} />
298+ </>
206299 );
207300 }
208−
209301 /**
210302 * A sheet that rises from the bottom of a phone, with a handle to drag it
211303 * back down. Escape, a tap outside or a drag of more than 90px closes it.
300392 {children}
301393 </section>
302394 );
303−}
304−
305−function displayName(membership: Membership): string {
306− return membership.name?.trim() || membership.slug;
307395 }
308396
309397 /** Your status, away and pausing notifications, as the account menu has them on a computer. */
345433 }
346434
347435 /**
348− * The phone's everything-else: the workspace avatar at the top left opens
349− * it. Docs and the workspace's own pages first, then your workspaces,
350− * help, and your account. The mode you are in has its own menu (its
351− * sidebar), behind the button beside the avatar.
436+ * You and help, on a phone: your status, your profile and settings, the
437+ * documentation, support, status and the keyboard's shortcuts, and signing
438+ * out, as the account menu has them on a computer.
352439 */
353−export function AvatarSheetButton({ user, workspace }: { user: User; workspace: Membership }) {
354− const [open, setOpen] = useState(false);
355− const { pathname } = useLocation();
440+export function AccountSheet({ open, onOpenChange, user }: { open: boolean; onOpenChange: (open: boolean) => void; user: User }) {
356441 const submit = useSubmit();
357− useEffect(() => setOpen(false), [pathname]);
358− const slug = workspace.slug;
359− const owner = workspace.role === "owner";
360442 const [editing, setEditing] = useState(false);
443+ const [keys, setKeys] = useState(false);
361444 return (
362445 <>
363− <button
364− type="button"
365− aria-label={`${displayName(workspace)}: workspaces, settings and account`}
366− onClick={() => setOpen(true)}
367− className="-ml-1 flex size-10 shrink-0 items-center justify-center rounded-xl transition-transform active:scale-95 md:hidden"
368− >
369− <Avatar name={slug} image={workspace.avatar} size={32} square />
370− </button>
371− <BottomSheet open={open} onOpenChange={setOpen} title="Workspaces, settings and account">
446+ <BottomSheet open={open} onOpenChange={onOpenChange} title="You and help">
372447 <div className="flex items-center gap-3 px-3 pt-1 pb-3">
373− <Avatar name={slug} image={workspace.avatar} size={44} square />
448+ <Avatar name={user.username} image={user.avatar} size={44} />
374449 <div className="min-w-0">
375− <p className="truncate text-base font-semibold">{displayName(workspace)}</p>
376− <p className="truncate font-mono text-xs text-muted">g1t.sh/{slug}</p>
450+ <p className="truncate text-base font-semibold">@{shownUsername(user)}</p>
377451 </div>
378452 </div>
379− <SheetGroup title="Workspace">
380− {/* Artifacts has no tab of its own for someone with Code: it leads here. */}
381− {hasCodeAccess(workspace) && (
382− <SheetRow to={`/${slug}/-/artifacts`} icon={<Shapes />} end={<ChevronRight size={16} className="text-faint" />}>
383− Artifacts
384− </SheetRow>
385− )}
386− <SheetRow to={`/${slug}/-/workspace`} icon={<Building2 />}>
387− Overview
388− </SheetRow>
389− <SheetRow to={`/${slug}/-/people`} icon={<Users />}>
390− People
391− </SheetRow>
392− <SheetRow to={`/${slug}/-/teams`} icon={<UsersRound />}>
393− Teams
453+ <SheetGroup title="You">
454+ <OwnPresenceRows
455+ onEdit={() => {
456+ onOpenChange(false);
457+ setEditing(true);
458+ }}
459+ />
460+ <SheetRow to={`/u/${user.username}`} icon={<CircleUserRound />}>
461+ Your profile
394462 </SheetRow>
395− <SheetRow to={`/${slug}/-/usage`} icon={<BarChart3 />}>
396− Usage and billing
397− </SheetRow>
398− <SheetRow to={`/${slug}/-/integrations`} icon={<Plug />}>
399− Integrations
400− </SheetRow>
401− <SheetRow to={owner ? `/${slug}/-/settings` : `/${slug}/-/repositories`} icon={<Settings />}>
402− Settings
463+ <SheetRow to="/settings" icon={<Settings />}>
464+ Your settings
403465 </SheetRow>
404466 </SheetGroup>
405− <SheetGroup title="Switch workspace">
406− {(user.workspaces ?? []).map((membership) => (
407− <SheetRow
408− key={membership.slug}
409− to={`/${membership.slug}/-/home`}
410− icon={<Avatar name={membership.slug} image={membership.avatar} size={24} square />}
411− end={membership.slug === slug ? <Check size={18} className="text-accent" /> : null}
412− >
413− {displayName(membership)}
414− </SheetRow>
415− ))}
416− <SheetRow to="/workspaces/new" icon={<Plus />}>
417− New workspace
467+ <SheetGroup title="Help">
468+ <SheetRow href="https://docs.g1t.sh/" icon={<BookOpen />}>
469+ Documentation
418470 </SheetRow>
419− </SheetGroup>
420− <SheetGroup title="Help">
421471 <SheetRow to="/support" icon={<LifeBuoy />}>
422472 Support
423473 </SheetRow>
424− <SheetRow href="https://docs.g1t.sh/" icon={<BookOpen />}>
425− Documentation
426− </SheetRow>
427474 <SheetRow href={STATUS_URL} icon={<Activity />}>
428475 Status
429476 </SheetRow>
430− </SheetGroup>
431− <SheetGroup title={`@${shownUsername(user)}`}>
432− <OwnPresenceRows
433− onEdit={() => {
434− setOpen(false);
435− setEditing(true);
477+ <SheetRow
478+ icon={<Keyboard />}
479+ onClick={() => {
480+ onOpenChange(false);
481+ setKeys(true);
436482 }}
437− />
438− <SheetRow to={`/u/${user.username}`} icon={<CircleUserRound />}>
439− Your profile
440− </SheetRow>
441− <SheetRow to="/settings" icon={<Settings />}>
442− Your settings
483+ >
484+ Keyboard shortcuts
443485 </SheetRow>
486+ </SheetGroup>
487+ <SheetGroup title="Account">
444488 <SheetRow icon={<LogOut />} onClick={() => submit(null, { method: "post", action: "/logout" })}>
445489 Sign out
446490 </SheetRow>
447491 </SheetGroup>
448492 </BottomSheet>
449493 <StatusDialog open={editing} onOpenChange={setEditing} />
494+ <ShortcutsDialog open={keys} onOpenChange={setKeys} />
450495 </>
451496 );
452497 }
477522 can,
478523 settings,
479524 counts,
525+ always = false,
480526 }: {
481527 /** `/<namespace>/<name>`. */
482528 base: string;
485531 /** Whether they see its settings (lib/access.ts `seesSettings`), as the sidebar decides. */
486532 settings?: boolean;
487533 counts?: { issues: number; pulls: number };
534+ /** On a computer too: for a visitor, who has no sidebar to list them. */
535+ always?: boolean;
488536 }) {
489537 const { pathname } = useLocation();
490538 const rest = pathname.toLowerCase().startsWith(base.toLowerCase()) ? pathname.slice(base.length) : "";
497545 current ? "bg-raised font-medium text-fg ring-1 ring-line-strong" : "text-muted active:bg-raised/60"
498546 }`;
499547 return (
500− <TabStrip label="Project" className="-mx-4 gap-1 px-4 md:hidden">
548+ <TabStrip label="Project" className={`-mx-4 gap-1 px-4 ${always ? "" : "md:hidden"}`}>
501549 {pages.map((page) => {
502550 const current = at === page;
503551 const link = page === "overview" ? { label: "Overview", path: "" } : PROJECT_PAGE_LINKS[page];
+1−1
2323 } from "../../lib/notify-client";
2424
2525 const LEVELS: { level: NotifyLevel; label: string; detail: string }[] = [
26− { level: "all", label: "Everything", detail: "Every message in conversations you're in, and everything in your inbox." },
26+ { level: "all", label: "Everything", detail: "Every message in conversations you're in, and everything in your notifications." },
2727 {
2828 level: "dms_mentions",
2929 label: "Direct messages, mentions and replies",
+2−2
1−import { AtSign, BellRing, CornerDownLeft, GitPullRequest, Hourglass, Inbox, MessageCircle, MessagesSquare, X } from "lucide-react";
1+import { AtSign, BellRing, CornerDownLeft, GitPullRequest, Hourglass, Bell, MessageCircle, MessagesSquare, X } from "lucide-react";
22 import { type FormEvent, type ReactNode, useLayoutEffect, useRef, useState } from "react";
33
44 import type { FeedNotification, NotificationKind } from "@g1t/contracts";
1313 dm: { label: "Direct message", icon: <MessageCircle /> },
1414 mention: { label: "Mentioned you", icon: <AtSign /> },
1515 thread_reply: { label: "Replied in a thread", icon: <MessagesSquare /> },
16− inbox: { label: "Inbox", icon: <Inbox /> },
16+ inbox: { label: "Notifications", icon: <Bell /> },
1717 agent_waiting: { label: "Waiting on you", icon: <Hourglass /> },
1818 approval: { label: "Needs your review", icon: <GitPullRequest /> },
1919 };
+6−7
1313 * per step, chosen with the pills.
1414 *
1515 * It is an honest miniature of the app: the same rail (the workspace on
16− * the top bar's line, then Home, Code, Chat, Docs, Agents and the Inbox,
16+ * the top bar's line, then Today, Code, Chat, Docs, Agents and Notifications,
1717 * with names), each mode's sidebar with its real sections and words, the
1818 * same top bar (where you are, Explore, Docs, Ask g1t, the inbox and +),
1919 * which Chat gives up to the conversation's own header as the app does,
4444 GitMerge,
4545 GitPullRequest,
4646 Hash,
47− House,
48− Inbox,
47+ Sun,
4948 MessagesSquare,
5049 Network,
5150 Paperclip,
349348
350349 /** The rail's modes, in the app's order (components/rail.tsx), with its icons. */
351350 const RAIL: { key: string; icon: ReactNode; label: string; target?: CursorTarget; scene?: Scene; badge?: number }[] = [
352− { key: "home", icon: <House size={15} />, label: "Home" },
351+ { key: "home", icon: <Sun size={15} />, label: "Today" },
353352 { key: "code", icon: <Code2 size={15} />, label: "Code", target: "rail-code", scene: "code" },
354353 { key: "chat", icon: <MessagesSquare size={15} />, label: "Chat", target: "rail-chat", scene: "chat", badge: 3 },
355354 { key: "artifacts", icon: <Shapes size={15} />, label: "Artifacts", target: "rail-docs", scene: "docs" },
356355 { key: "agents", icon: <Sparkles size={15} />, label: "Agents", target: "rail-agents", scene: "agents" },
357− { key: "inbox", icon: <Inbox size={15} />, label: "Inbox", badge: 2 },
356+ { key: "inbox", icon: <Bell size={15} />, label: "Notifications", badge: 2 },
358357 ];
359358
360359 /** One rail item: the square behind its icon, filled for the mode you are in, and its name under it. */
12871286
12881287 /** The phone's tab bar, as the app has it (components/mobile.tsx). */
12891288 const TABS: { scene: Scene | null; icon: ReactNode; label: string }[] = [
1290− { scene: null, icon: <House size={16} />, label: "Home" },
1289+ { scene: null, icon: <Sun size={16} />, label: "Today" },
12911290 { scene: "code", icon: <Code2 size={16} />, label: "Code" },
12921291 { scene: "chat", icon: <MessagesSquare size={16} />, label: "Chat" },
12931292 { scene: "agents", icon: <Sparkles size={16} />, label: "Agents" },
1294− { scene: null, icon: <Inbox size={16} />, label: "Inbox" },
1293+ { scene: null, icon: <Bell size={16} />, label: "Notifications" },
12951294 ];
12961295
12971296 function Phone({ frame }: { frame: Frame }) {
+193−0
1+/**
2+ * The public frame's header (lib/chrome.ts, `public`): for a visitor who is
3+ * not signed in, on every page that is not about signing in. The logo,
4+ * search, Explore and Docs, and signing in or up; on a phone the links fold
5+ * into one menu. Someone still confirming their address gets their
6+ * account's menu in place of signing in.
7+ */
8+import { BookOpen, ChevronDown, Compass, CreditCard, LogIn, LogOut, Menu, Plus, Search, Settings } from "lucide-react";
9+import { useState } from "react";
10+import { Form, Link, NavLink, useParams, useSubmit } from "react-router";
11+
12+import type { User } from "@g1t/contracts";
13+
14+import { CommandPalette, type PaletteCommand, PaletteKey, usePaletteShortcut } from "./command-palette";
15+import { Logo } from "./logo";
16+import { Avatar, ButtonLink, notACredential } from "./ui";
17+import {
18+ DropdownMenu,
19+ DropdownMenuContent,
20+ DropdownMenuItem,
21+ DropdownMenuLabel,
22+ DropdownMenuSeparator,
23+ DropdownMenuTrigger,
24+} from "./ui/dropdown-menu";
25+import { useSignUpCopy } from "../lib/registration";
26+
27+function HeaderLink({ to, children }: { to: string; children: React.ReactNode }) {
28+ return (
29+ <NavLink
30+ to={to}
31+ className={({ isActive }) =>
32+ `rounded-md px-2.5 py-1.5 text-sm transition-colors hover:bg-raised hover:text-fg ${
33+ isActive ? "text-fg" : "text-muted"
34+ }`
35+ }
36+ >
37+ {children}
38+ </NavLink>
39+ );
40+}
41+
42+/** What the palette offers someone without the app's sidebar. */
43+const PUBLIC_COMMANDS: PaletteCommand[] = [
44+ { label: "Explore", hint: "Public projects", to: "/explore", icon: <Compass size={15} /> },
45+ { label: "Search g1t", hint: "Repositories, code, issues, people", to: "/search", icon: <Search size={15} /> },
46+ { label: "Pricing", to: "/pricing", icon: <CreditCard size={15} /> },
47+ { label: "Documentation", to: "https://docs.g1t.sh/", icon: <BookOpen size={15} /> },
48+ { label: "Sign in", to: "/login", icon: <LogIn size={15} /> },
49+ { label: "Sign up", to: "/register", icon: <Plus size={15} /> },
50+];
51+
52+/** Sign up: the sign-up page says whether it takes an invite. */
53+function SignUpButton() {
54+ const copy = useSignUpCopy();
55+ return <ButtonLink to="/register">{copy.primary}</ButtonLink>;
56+}
57+
58+export function PublicHeader({ user }: { user: User | null | undefined }) {
59+ const submit = useSubmit();
60+ const signUp = useSignUpCopy();
61+ const [palette, setPalette] = useState(false);
62+ usePaletteShortcut(() => setPalette((open) => !open));
63+ const params = useParams();
64+ const repo = params.owner && params.repo ? `${params.owner}/${params.repo}` : null;
65+ return (
66+ <header className="sticky top-0 z-40 border-b border-line bg-surface/85 backdrop-blur">
67+ <div className="mx-auto flex h-14 max-w-6xl items-center gap-2 px-4">
68+ <Link to="/" aria-label="g1t home" className="mr-2 flex">
69+ <Logo />
70+ </Link>
71+ <Form action="/search" role="search" className="relative hidden grow sm:block sm:max-w-xs">
72+ <Search
73+ size={14}
74+ className="pointer-events-none absolute top-1/2 left-3 -translate-y-1/2 text-faint"
75+ />
76+ <input
77+ name="q"
78+ {...notACredential()}
79+ placeholder="Search g1t"
80+ aria-label="Search g1t"
81+ className="w-full rounded-md border border-line bg-bg py-1.5 pr-12 pl-8 text-sm outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-accent-dim"
82+ />
83+ <PaletteKey className="pointer-events-none absolute top-1/2 right-2 -translate-y-1/2 rounded bg-raised px-1.5 font-mono text-[0.625rem] text-muted ring-1 ring-line" />
84+ </Form>
85+ <button
86+ type="button"
87+ aria-label="Search g1t"
88+ onClick={() => setPalette(true)}
89+ className="flex size-10 items-center justify-center rounded-md text-muted transition-colors hover:bg-raised hover:text-fg sm:hidden"
90+ >
91+ <Search size={18} />
92+ </button>
93+ <CommandPalette
94+ open={palette}
95+ onOpenChange={setPalette}
96+ commands={user ? PUBLIC_COMMANDS.slice(0, 4) : PUBLIC_COMMANDS.map((command) => (command.to === "/register" ? { ...command, label: signUp.primary } : command))}
97+ repo={repo}
98+ />
99+ <nav aria-label="Main" className="hidden items-center gap-0.5 sm:flex">
100+ <HeaderLink to="/explore">Explore</HeaderLink>
101+ <HeaderLink to="https://docs.g1t.sh/">Docs</HeaderLink>
102+ </nav>
103+ <div className="ml-auto flex items-center gap-2">
104+ {/* On a phone the links fold into one menu, so the bar fits. */}
105+ <DropdownMenu>
106+ <DropdownMenuTrigger
107+ aria-label="Menu"
108+ className="flex size-10 items-center justify-center rounded-md text-muted outline-none transition-colors hover:bg-raised hover:text-fg focus-visible:ring-2 focus-visible:ring-accent data-[state=open]:bg-raised data-[state=open]:text-fg sm:hidden"
109+ >
110+ <Menu size={18} />
111+ </DropdownMenuTrigger>
112+ <DropdownMenuContent align="end" className="w-52">
113+ <DropdownMenuItem asChild className="min-h-11">
114+ <Link to="/explore">
115+ <Compass />
116+ Explore
117+ </Link>
118+ </DropdownMenuItem>
119+ <DropdownMenuItem asChild className="min-h-11">
120+ <Link to="https://docs.g1t.sh/">
121+ <BookOpen />
122+ Docs
123+ </Link>
124+ </DropdownMenuItem>
125+ {!user && (
126+ <>
127+ <DropdownMenuSeparator />
128+ <DropdownMenuItem asChild className="min-h-11">
129+ <Link to="/login">
130+ <LogIn />
131+ Sign in
132+ </Link>
133+ </DropdownMenuItem>
134+ </>
135+ )}
136+ </DropdownMenuContent>
137+ </DropdownMenu>
138+ {user ? (
139+ <>
140+ <DropdownMenu>
141+ <DropdownMenuTrigger
142+ aria-label="Account menu"
143+ className="flex items-center gap-1.5 rounded-md p-1 outline-none transition-colors hover:bg-raised focus-visible:ring-2 focus-visible:ring-accent data-[state=open]:bg-raised"
144+ >
145+ <Avatar name={user.username} image={user.avatar} size={24} />
146+ <ChevronDown size={14} className="text-faint" />
147+ </DropdownMenuTrigger>
148+ <DropdownMenuContent align="end" className="w-56">
149+ <DropdownMenuLabel>
150+ Signed in as{" "}
151+ <span className="font-mono font-medium text-fg">
152+ {user.username}
153+ </span>
154+ </DropdownMenuLabel>
155+ <DropdownMenuSeparator />
156+ <DropdownMenuItem asChild>
157+ <Link to="/settings/profile">
158+ <Settings />
159+ Settings
160+ </Link>
161+ </DropdownMenuItem>
162+ <DropdownMenuItem asChild>
163+ <Link to="https://docs.g1t.sh/quickstart/">
164+ <BookOpen />
165+ Documentation
166+ </Link>
167+ </DropdownMenuItem>
168+ <DropdownMenuSeparator />
169+ {/* Submitted from here: the menu closes on select, and a button
170+ that has left the page cannot submit a form. */}
171+ <DropdownMenuItem
172+ onSelect={() => submit(null, { method: "post", action: "/logout" })}
173+ >
174+ <LogOut />
175+ Sign out
176+ </DropdownMenuItem>
177+ </DropdownMenuContent>
178+ </DropdownMenu>
179+ </>
180+ ) : (
181+ <>
182+ <span className="hidden sm:contents">
183+ <HeaderLink to="/login">Sign in</HeaderLink>
184+ </span>
185+ <SignUpButton />
186+ </>
187+ )}
188+ </div>
189+ </div>
190+ </header>
191+ );
192+}
193+
+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.