Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 1 | import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; |
| 2 | ||
| 3 | import { useSignedIn } from "./session"; | |
| 4 | ||
| 5 | /** | |
| 6 | * Code, through the REST API (docs.g1t.sh/reference/api/). The API | |
| 7 | * answers in snake_case, so these are its shapes, not the camelCase ones | |
| 8 | * in @g1t/contracts that the services use among themselves. Only the | |
| 9 | * fields the app shows are named. | |
| 10 | */ | |
| 11 | ||
| The phone app no longer has g1t's own agents, to match the site. The Agents tab is gone from the bar, and Customize (skills, connectors and plugins) is gone with its row in More. New message finds and messages people only, and its search and heading say so. A card an agent posted earlier still shows with its title, state, details and links, but its other buttons are no longer drawn, since chat no longer answers them; an old agent's messages and direct messages still show, under the name chat gives them, marked Deleted agent. The app stops asking for agents:run when it signs in. An old g1t:// link to Agents, the skill library, Customize or the Marketplace opens Home, while the integrations directory opens on the site. Notifications, the sign-in screen and the access-token error no longer mention agents waiting on you or agent tokens, and the sign-in screen speaks of reviewing what you and your coding agents make. Pull requests still show the coding agent they were made with. | 12 | /** `agent` only on what g1t's own agents wrote before they were retired; shown with an Agent badge. */ |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 13 | export type ApiUser = { id: string; username: string; display_username?: string; kind?: "user" | "workspace" | "agent" | "system" }; |
| 14 | ||
| 15 | export type ApiRepo = { | |
| 16 | id: string; | |
| 17 | namespace: string; | |
| 18 | name: string; | |
| 19 | description: string | null; | |
| 20 | is_private: boolean; | |
| 21 | default_branch: string; | |
| 22 | created_at: string; | |
| 23 | archived_at?: string | null; | |
| 24 | }; | |
| 25 | ||
| 26 | export type ApiIssue = { | |
| 27 | id: string; | |
| 28 | number: number; | |
| 29 | title: string; | |
| 30 | body: string; | |
| 31 | labels: string[]; | |
| 32 | state: "open" | "closed"; | |
| 33 | reason: "completed" | "not_planned" | null; | |
| 34 | author: ApiUser; | |
| 35 | created_at: string; | |
| 36 | updated_at: string; | |
| 37 | comment_count: number; | |
| 38 | pull_count: number; | |
| 39 | }; | |
| 40 | ||
| 41 | export type CheckStatus = "queued" | "running" | "passed" | "failed" | "errored" | null; | |
| 42 | ||
| 43 | export type ApiPull = { | |
| 44 | id: string; | |
| 45 | number: number; | |
| 46 | issue: number | null; | |
| 47 | title: string; | |
| 48 | body: string | null; | |
| The phone app no longer has g1t's own agents, to match the site. The Agents tab is gone from the bar, and Customize (skills, connectors and plugins) is gone with its row in More. New message finds and messages people only, and its search and heading say so. A card an agent posted earlier still shows with its title, state, details and links, but its other buttons are no longer drawn, since chat no longer answers them; an old agent's messages and direct messages still show, under the name chat gives them, marked Deleted agent. The app stops asking for agents:run when it signs in. An old g1t:// link to Agents, the skill library, Customize or the Marketplace opens Home, while the integrations directory opens on the site. Notifications, the sign-in screen and the access-token error no longer mention agents waiting on you or agent tokens, and the sign-in screen speaks of reviewing what you and your coding agents make. Pull requests still show the coding agent they were made with. | 49 | /** The coding agent it was made with (`claude-code`, `codex`), from the session recorded on it; null when none was. */ |
| 50 | agent: string | null; | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 51 | status: "draft" | "open" | "merged" | "closed"; |
| 52 | branch: string | null; | |
| 53 | base: string; | |
| 54 | check_status: CheckStatus; | |
| 55 | files: { path: string; additions: number; deletions: number }[]; | |
| 56 | reviewers: string[]; | |
| 57 | labels: string[]; | |
| 58 | author: ApiUser; | |
| 59 | created_at: string; | |
| 60 | updated_at: string; | |
| 61 | merged_at: string | null; | |
| 62 | }; | |
| 63 | ||
| 64 | export type ApiComment = { | |
| 65 | id: string; | |
| 66 | kind: "comment" | "event"; | |
| 67 | author: ApiUser; | |
| 68 | body: string; | |
| 69 | path: string | null; | |
| 70 | line: number | null; | |
| 71 | verdict: string | null; | |
| 72 | created_at: string; | |
| 73 | }; | |
| 74 | ||
| 75 | export type ApiChanges = { | |
| 76 | base: string; | |
| 77 | head: string; | |
| 78 | truncated: boolean; | |
| 79 | files: { | |
| 80 | path: string; | |
| 81 | status: "added" | "modified" | "deleted"; | |
| 82 | additions: number; | |
| 83 | deletions: number; | |
| 84 | binary: boolean; | |
| 85 | hunks?: { lines: { kind: "context" | "add" | "delete"; old: number | null; new: number | null; text: string }[] }[]; | |
| 86 | }[]; | |
| 87 | }; | |
| 88 | ||
| 89 | const repoPath = (owner: string, name: string) => `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`; | |
| 90 | ||
| 91 | /** Repositories you can see, newest first (at most 50), in the workspace chosen. */ | |
| 92 | export function useRepos(search: string) { | |
| 93 | const { client, workspace } = useSignedIn(); | |
| 94 | return useQuery({ | |
| 95 | queryKey: ["code", "repos", search], | |
| 96 | queryFn: () => client.api<ApiRepo[]>(`/repos${search ? `?q=${encodeURIComponent(search)}` : ""}`), | |
| 97 | select: (repos) => (workspace ? repos.filter((repo) => repo.namespace.toLowerCase() === workspace.slug.toLowerCase()) : repos), | |
| 98 | }); | |
| 99 | } | |
| 100 | ||
| 101 | export function useRepo(owner: string, name: string) { | |
| 102 | const { client } = useSignedIn(); | |
| 103 | return useQuery({ queryKey: ["code", "repo", owner, name], queryFn: () => client.api<ApiRepo>(repoPath(owner, name)) }); | |
| 104 | } | |
| 105 | ||
| 106 | export function usePulls(owner: string, name: string, state: "open" | "closed" | "all") { | |
| 107 | const { client } = useSignedIn(); | |
| 108 | return useQuery({ | |
| 109 | queryKey: ["code", "pulls", owner, name, state], | |
| 110 | queryFn: () => client.api<ApiPull[]>(`${repoPath(owner, name)}/pulls?state=${state}`), | |
| 111 | }); | |
| 112 | } | |
| 113 | ||
| 114 | export function useIssues(owner: string, name: string, state: "open" | "closed" | "all") { | |
| 115 | const { client } = useSignedIn(); | |
| 116 | return useQuery({ | |
| 117 | queryKey: ["code", "issues", owner, name, state], | |
| 118 | queryFn: () => client.api<ApiIssue[]>(`${repoPath(owner, name)}/issues?state=${state}`), | |
| 119 | }); | |
| 120 | } | |
| 121 | ||
| 122 | export function usePull(owner: string, name: string, number: number) { | |
| 123 | const { client } = useSignedIn(); | |
| 124 | return useQuery({ | |
| 125 | queryKey: ["code", "pull", owner, name, number], | |
| 126 | queryFn: () => | |
| 127 | client.api<{ pull: ApiPull; issue: ApiIssue | null; comments: ApiComment[] }>(`${repoPath(owner, name)}/pulls/${number}`), | |
| 128 | }); | |
| 129 | } | |
| 130 | ||
| 131 | export function usePullChanges(owner: string, name: string, number: number, enabled: boolean) { | |
| 132 | const { client } = useSignedIn(); | |
| 133 | return useQuery({ | |
| 134 | queryKey: ["code", "pull-changes", owner, name, number], | |
| 135 | queryFn: () => client.api<ApiChanges>(`${repoPath(owner, name)}/pulls/${number}/changes`), | |
| 136 | enabled, | |
| 137 | }); | |
| 138 | } | |
| 139 | ||
| 140 | export function useIssue(owner: string, name: string, number: number) { | |
| 141 | const { client } = useSignedIn(); | |
| 142 | return useQuery({ | |
| 143 | queryKey: ["code", "issue", owner, name, number], | |
| 144 | queryFn: () => | |
| 145 | client.api<{ issue: ApiIssue; pulls: ApiPull[]; comments: ApiComment[] }>(`${repoPath(owner, name)}/issues/${number}`), | |
| 146 | }); | |
| 147 | } | |
| 148 | ||
| 149 | /** Comments on an issue (`POST /repos/{owner}/{name}/issues/{number}/comments`). */ | |
| 150 | export function useComment(owner: string, name: string, number: number) { | |
| 151 | const { client } = useSignedIn(); | |
| 152 | const queries = useQueryClient(); | |
| 153 | return useMutation({ | |
| 154 | mutationFn: (body: string) => | |
| 155 | client.api<ApiComment>(`${repoPath(owner, name)}/issues/${number}/comments`, { method: "POST", body: JSON.stringify({ body }) }), | |
| 156 | onSuccess: () => queries.invalidateQueries({ queryKey: ["code", "issue", owner, name, number] }), | |
| 157 | }); | |
| 158 | } | |
| 159 | ||
| 160 | /** Reviews a pull request (`POST /repos/{owner}/{name}/pulls/{number}/reviews`). */ | |
| 161 | export function useReview(owner: string, name: string, number: number) { | |
| 162 | const { client } = useSignedIn(); | |
| 163 | const queries = useQueryClient(); | |
| 164 | return useMutation({ | |
| 165 | mutationFn: (review: { verdict: "approve" | "request_changes"; body: string }) => | |
| 166 | client.api<ApiComment>(`${repoPath(owner, name)}/pulls/${number}/reviews`, { method: "POST", body: JSON.stringify(review) }), | |
| 167 | onSuccess: () => queries.invalidateQueries({ queryKey: ["code", "pull", owner, name, number] }), | |
| 168 | }); | |
| 169 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.