Skip to content
313 linesCodeBlameRaw

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.

A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were1/**
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.2 * Memory, as a project's and a workspace's pages show it: what coding agents
3 * (with the MCP server's remember) and people saved, each with where it came
4 * from, to pin, edit or forget.
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were5 * Both pages post to their own action with the intents in `memoryAction`.
6 */
7import { Bot, Brain, Pencil, Pin, PinOff, Trash2, User } from "lucide-react";
8import { useEffect, useRef, useState } from "react";
9import { Link, useFetcher } from "react-router";
10
11import { MEMORY_KINDS, type Memory, type MemoryKind, type MemoryScope, type Result, type User as Actor } from "@g1t/contracts";
12
Merge branch 'worktree-agent-a985c01f43538af01'13import { SubmitButton, TimeAgo } from "./ui";
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were14import {
15 AlertDialog,
16 AlertDialogAction,
17 AlertDialogCancel,
18 AlertDialogContent,
19 AlertDialogDescription,
20 AlertDialogFooter,
21 AlertDialogHeader,
22 AlertDialogTitle,
23 AlertDialogTrigger,
24} from "./ui/alert-dialog";
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.25import { Button } from "./ui/button";
26import { Card } from "./ui/card";
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were27import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog";
28import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
Merge branch 'worktree-agent-ae1299e92e4462012'29import { Hint } from "./ui/hint";
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were30import { Switch } from "./ui/switch";
31
32const KIND: Record<MemoryKind, { label: string; about: string; tone: string }> = {
33 fact: { label: "Fact", about: "How something is", tone: "text-info border-info/40" },
34 convention: { label: "Convention", about: "How things are done here", tone: "text-accent border-accent/40" },
35 decision: { label: "Decision", about: "Something chosen, and why", tone: "text-merged border-merged/40" },
36 gotcha: { label: "Gotcha", about: "A trap to avoid", tone: "text-warn border-warn/40" },
37};
38
39const TEXTAREA =
40 "w-full rounded-md border border-line bg-bg px-3 py-2 text-sm outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-accent-dim";
41
42type Done = { ok: boolean; error?: string } | undefined;
43
44function KindBadge({ kind }: { kind: MemoryKind }) {
45 return (
46 <span className={`inline-flex shrink-0 items-center rounded-full border px-2 py-px text-[0.6875rem] ${KIND[kind].tone}`}>
47 {KIND[kind].label}
48 </span>
49 );
50}
51
52function KindSelect({ value }: { value?: MemoryKind }) {
53 return (
54 <Select name="kind" defaultValue={value ?? "fact"}>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit55 <SelectTrigger size="sm" className="w-40" aria-label="Kind of memory">
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were56 <SelectValue />
57 </SelectTrigger>
58 <SelectContent>
59 {MEMORY_KINDS.map((kind) => (
60 <SelectItem key={kind} value={kind} description={KIND[kind].about}>
61 {KIND[kind].label}
62 </SelectItem>
63 ))}
64 </SelectContent>
65 </Select>
66 );
67}
68
69/** Where a memory came from, linked where it can be. */
70function Source({ memory }: { memory: Memory }) {
71 const { source } = memory;
72 const repo = source.repo;
73 const where =
74 repo && source.number != null ? (
75 <>
76 {" "}
77 on{" "}
78 <Link to={`/${repo.namespace}/${repo.name}/pull/${source.number}`} className="hover:text-fg">
79 {memory.scope === "workspace" && `${repo.name}`}#{source.number}
80 </Link>
81 </>
82 ) : repo && memory.scope === "workspace" ? (
83 <>
84 {" "}
85 in <Link to={`/${repo.namespace}/${repo.name}`} className="hover:text-fg">{repo.name}</Link>
86 </>
87 ) : null;
88 if (source.kind === "person") {
89 return (
90 <span className="inline-flex items-center gap-1">
91 <User size={11} />
92 {memory.createdBy}
93 {where}
94 </span>
95 );
96 }
97 return (
98 <span className="inline-flex items-center gap-1">
99 <Bot size={11} />
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.100 {memory.createdBy}
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were101 {where}
102 </span>
103 );
104}
105
106function EditMemory({ memory, action }: { memory: Memory; action: string }) {
107 const fetcher = useFetcher<Done>();
108 const [open, setOpen] = useState(false);
109 useEffect(() => {
110 if (fetcher.state === "idle" && fetcher.data?.ok) setOpen(false);
111 }, [fetcher.state, fetcher.data]);
112 return (
113 <Dialog open={open} onOpenChange={setOpen}>
Phones reach every page: each mode's menu from the top bar or by tapping the tab you're on, Docs first in the avatar sheet, a project's pages as a swipeable strip; dialogs as bottom sheets above the keyboard, safe areas, menus kept on screen, 40px targets, touch-visible controls, save bars above the tab bar114 <DialogTrigger aria-label="Edit" className="rounded p-1 text-faint max-sm:p-2.5 transition-colors hover:bg-raised hover:text-fg">
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were115 <Pencil size={13} />
116 </DialogTrigger>
117 <DialogContent>
118 <DialogHeader>
119 <DialogTitle>Edit memory</DialogTitle>
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.120 <DialogDescription>Agents that recall it get the new wording from now on.</DialogDescription>
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were121 </DialogHeader>
122 <fetcher.Form method="post" action={action} className="space-y-3">
123 <input type="hidden" name="intent" value="update" />
124 <input type="hidden" name="id" value={memory.id} />
125 <textarea name="text" required rows={4} maxLength={1000} defaultValue={memory.text} className={TEXTAREA} />
126 <div className="flex items-center justify-between gap-3">
127 <KindSelect value={memory.kind} />
Merge branch 'worktree-agent-a985c01f43538af01'128 <SubmitButton fetcher={fetcher} pending="Saving…">
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were129 Save
Merge branch 'worktree-agent-a985c01f43538af01'130 </SubmitButton>
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were131 </div>
132 {fetcher.data?.error && <p className="text-sm text-danger">{fetcher.data.error}</p>}
133 </fetcher.Form>
134 </DialogContent>
135 </Dialog>
136 );
137}
138
139function MemoryItem({ memory, action, editable }: { memory: Memory; action: string; editable: boolean }) {
140 const fetcher = useFetcher<Done>();
141 const pinned = fetcher.formData ? fetcher.formData.get("pinned") === "true" : memory.pinned;
142 const gone = fetcher.formData?.get("intent") === "delete";
143 if (gone) return null;
144 return (
145 <li className="group flex gap-3 px-4 py-3">
146 <div className="min-w-0 grow">
147 <div className="flex flex-wrap items-center gap-2">
148 <KindBadge kind={memory.kind} />
149 {pinned && (
150 <span className="inline-flex items-center gap-1 text-[0.6875rem] text-accent">
151 <Pin size={11} />
152 pinned
153 </span>
154 )}
155 </div>
Phones reach every page: each mode's menu from the top bar or by tapping the tab you're on, Docs first in the avatar sheet, a project's pages as a swipeable strip; dialogs as bottom sheets above the keyboard, safe areas, menus kept on screen, 40px targets, touch-visible controls, save bars above the tab bar156 <p className="mt-1.5 text-sm whitespace-pre-wrap wrap-break-word">{memory.text}</p>
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were157 <p className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-faint">
158 <Source memory={memory} />
159 <span>
160 added <TimeAgo at={memory.createdAt} />
161 </span>
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.162 <span>{memory.lastUsedAt ? <>recalled <TimeAgo at={memory.lastUsedAt} /></> : "not recalled yet"}</span>
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were163 </p>
164 {fetcher.data?.error && <p className="mt-1.5 text-xs text-danger">{fetcher.data.error}</p>}
165 </div>
166 {editable && (
167 <div className="flex shrink-0 items-start gap-0.5">
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.168 <Hint label={pinned ? "Unpin" : "Pin: recalled first"}>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.169 <Button
Merge branch 'worktree-agent-ae1299e92e4462012'170 type="button"
171 aria-label={pinned ? "Unpin" : "Pin"}
172 onClick={() => fetcher.submit({ intent: "update", id: memory.id, pinned: String(!pinned) }, { method: "post", action })}
173 disabled={fetcher.state !== "idle"}
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.174 variant="ghost"
175 size="inline"
176 className="rounded p-1 text-faint max-sm:p-2.5"
Merge branch 'worktree-agent-ae1299e92e4462012'177 >
178 {pinned ? <PinOff size={13} /> : <Pin size={13} />}
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.179 </Button>
Merge branch 'worktree-agent-ae1299e92e4462012'180 </Hint>
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were181 <EditMemory memory={memory} action={action} />
182 <AlertDialog>
Phones reach every page: each mode's menu from the top bar or by tapping the tab you're on, Docs first in the avatar sheet, a project's pages as a swipeable strip; dialogs as bottom sheets above the keyboard, safe areas, menus kept on screen, 40px targets, touch-visible controls, save bars above the tab bar183 <AlertDialogTrigger aria-label="Forget" className="rounded p-1 text-faint max-sm:p-2.5 transition-colors hover:bg-raised hover:text-danger">
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were184 <Trash2 size={13} />
185 </AlertDialogTrigger>
186 <AlertDialogContent>
187 <AlertDialogHeader>
188 <AlertDialogTitle>Forget this?</AlertDialogTitle>
189 <AlertDialogDescription>
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.190 No agent recalls it again. This cannot be undone.
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were191 </AlertDialogDescription>
192 </AlertDialogHeader>
193 <AlertDialogFooter>
194 <AlertDialogCancel>Keep it</AlertDialogCancel>
195 <AlertDialogAction onClick={() => fetcher.submit({ intent: "delete", id: memory.id }, { method: "post", action })}>
196 Forget
197 </AlertDialogAction>
198 </AlertDialogFooter>
199 </AlertDialogContent>
200 </AlertDialog>
201 </div>
202 )}
203 </li>
204 );
205}
206
207export function MemoryList({
208 memories,
209 action,
210 editable,
211 empty,
212}: {
213 memories: Memory[];
214 action: string;
215 editable: boolean;
216 empty: string;
217}) {
218 if (memories.length === 0) {
219 return <p className="rounded-xl border border-dashed border-line px-4 py-6 text-sm text-muted">{empty}</p>;
220 }
221 return (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.222 <Card asChild divided>
223 <ul>
224 {memories.map((memory) => (
225 <MemoryItem key={memory.id} memory={memory} action={action} editable={editable} />
226 ))}
227 </ul>
228 </Card>
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were229 );
230}
231
232/** A form to add a memory at one level. */
233export function AddMemory({ scope, action, placeholder }: { scope: MemoryScope; action: string; placeholder: string }) {
234 const fetcher = useFetcher<Done>();
235 const form = useRef<HTMLFormElement>(null);
236 useEffect(() => {
237 if (fetcher.state === "idle" && fetcher.data?.ok) form.current?.reset();
238 }, [fetcher.state, fetcher.data]);
239 return (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.240 <Card asChild className="p-4">
241 <fetcher.Form ref={form} method="post" action={action}>
242 <input type="hidden" name="intent" value="add" />
243 <input type="hidden" name="scope" value={scope} />
244 <label className="flex items-center gap-2 text-sm font-medium">
245 <Brain size={15} className="text-accent" />
246 Add to {scope === "workspace" ? "the workspace's" : "this project's"} memory
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were247 </label>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.248 <textarea name="text" required rows={2} maxLength={1000} placeholder={placeholder} className={`${TEXTAREA} mt-3`} />
249 <div className="mt-3 flex flex-wrap items-center gap-3">
250 <KindSelect />
251 <label className="flex items-center gap-2 text-xs text-muted">
252 <Switch name="pinned" value="true" size="sm" />
253 Pin it
254 </label>
255 <span className="grow" />
256 <SubmitButton fetcher={fetcher} pending="Adding…">
257 Add
258 </SubmitButton>
259 </div>
260 {fetcher.data?.error && <p className="mt-2 text-sm text-danger">{fetcher.data.error}</p>}
261 <p className="mt-2 text-xs text-faint">
262 One fact, convention, decision or gotcha each. Never a secret: say where it lives instead, and
263 g1t refuses anything that looks like a key or a token.
264 </p>
265 </fetcher.Form>
266 </Card>
A file named test.js or spec.rb counts as a test: confidence no longer says tests not added when they were267 );
268}
269
270type MemoryApi = {
271 addMemory(actor: Actor, workspace: string, memory: { scope: MemoryScope; repo?: { namespace: string; name: string } | null; text: string; kind?: MemoryKind; pinned?: boolean }): Promise<Result<Memory>>;
272 updateMemory(actor: Actor, workspace: string, id: string, change: { text?: string; kind?: MemoryKind; pinned?: boolean }): Promise<Result<Memory>>;
273 deleteMemory(actor: Actor, workspace: string, id: string): Promise<Result<boolean>>;
274};
275
276/** The action both memory pages share: add, update and delete. */
277export async function memoryAction(
278 api: MemoryApi,
279 actor: Actor,
280 workspace: string,
281 repo: { namespace: string; name: string } | null,
282 form: FormData,
283): Promise<Done> {
284 const intent = String(form.get("intent") ?? "");
285 const kindValue = String(form.get("kind") ?? "");
286 const kind = MEMORY_KINDS.includes(kindValue as MemoryKind) ? (kindValue as MemoryKind) : undefined;
287 if (intent === "add") {
288 const scope: MemoryScope = form.get("scope") === "workspace" ? "workspace" : "project";
289 const added = await api.addMemory(actor, workspace, {
290 scope,
291 repo,
292 text: String(form.get("text") ?? ""),
293 kind,
294 pinned: form.get("pinned") === "true",
295 });
296 return added.ok ? { ok: true } : { ok: false, error: added.error.message };
297 }
298 if (intent === "update") {
299 const pinned = form.get("pinned");
300 const text = form.get("text");
301 const changed = await api.updateMemory(actor, workspace, String(form.get("id") ?? ""), {
302 text: text == null ? undefined : String(text),
303 kind,
304 pinned: pinned == null ? undefined : pinned === "true",
305 });
306 return changed.ok ? { ok: true } : { ok: false, error: changed.error.message };
307 }
308 if (intent === "delete") {
309 const gone = await api.deleteMemory(actor, workspace, String(form.get("id") ?? ""));
310 return gone.ok ? { ok: true } : { ok: false, error: gone.error.message };
311 }
312 return { ok: false, error: "Unknown action." };
313}

This file's history is long; its oldest lines are credited to the oldest commit read.