Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh

Commit

A session's transcript reads as what it said, with its tool calls between: "What it recorded" on a session's page (and What happened on a pull request's Session tab) drew every entry as a full-height row. A tool call said its tool twice ("bash bash: curl …", "g1t:issue g1t:issue: work"), a message's Markdown showed its ** and backticks as written, the prompt's <issue> and <comment> blocks were raw text in a <pre>, and a run of eight tool calls was eight bordered rows. Tool calls between two messages now run together (lib/session-entries.ts transcript): up to three in a tight list, more folded into one line ("7 tool calls · bash, write, read") that opens to it, each result folded under the call before it. Each call says its tool once (readToolCall takes the "<tool>: " off the front of its text, and finds the tool in the text when the entry has none, as from the Claude Code plugin): a command as code in one line, a long one unfolding in full; a file by its path with a file icon; an MCP tool by resource and action ("issue · claim", "pull request · create") with the g1t mark for g1t's, and its server's name for others (mcp__claude_ai_Google_Drive__… is Google Drive). Messages render with the site's Markdown, references linked within the repository. The prompt folds under its first line and opens to it rendered, its <issue> and <comment> blocks as quotes under a label (quotedBlocks). Relative times stay on prompts, messages, skills and notes. The sessions guide says how the transcript reads.

Checked:
apps/web typecheck (clean but for two errors in components/shell.tsx already at HEAD from 216cb234: the sidebar's projects type lacks kind and isPrivate); lib/session-entries and no-native-title tests; rendered in a Vite harness with realistic entries (bash, g1t:issue claim, write index.html, read /tmp/phone.png, Claude Code mcp__ names, a long curl, a Markdown report), folded and unfolded, and at the narrowest headless width (484 px) nothing past the edge.
G1t-Session:
ses_01m4z0hbcwey8ah68bfwxx3arm
syntaqxcommitted Parenta20fe87Browse files
6 files+356−520/6 viewed
+6−2
7171 you open one at a time (each with its kind, the task it was given, what
7272 it touched and what it reported), the skills used, what was touched
7373 (files, commits, branches, pull requests), and everything it recorded,
74− as it records it. A subagent's own page links back to the session that
75− started it.
74+ as it records it: its prompt folded, its messages formatted as they were
75+ written, and the tool calls between them a line each (a command as code,
76+ a file by its path, a g1t tool by what it did, such as *issue · claim*),
77+ more than three in a row folded into one line that opens to them; a
78+ long command and what a tool gave back open under its line. A
79+ subagent's own page links back to the session that started it.
7680 - **An issue's and a pull request's sessions**: every session tied to it
7781 in any way, with its subagents under it.
7882 - **Start a session** on an issue, for members of a workspace with an
+177−48
11 /**
2− * What a session recorded, step by step: prompts folded, messages in full,
3− * tool calls in a line each and their results folded. The pull request's
2+ * What a session recorded, step by step: the prompt folded, messages as
3+ * the markdown they are, and the tool calls between them run together, a
4+ * line each, with what came back folded under each. The pull request's
45 * Sessions page and a harness session's page both list them.
56 */
6−import { MessageSquare, Sparkles, StickyNote, Terminal, Wrench } from "lucide-react";
7+import { ChevronRight, FileText, MessageSquare, Plug, Sparkles, StickyNote, Terminal, Wrench } from "lucide-react";
8+import { type ReactNode, useState } from "react";
79
810 import type { SessionEntry } from "@g1t/contracts";
911
12+import { MarkGlyph } from "./logo";
13+import { Markdown } from "./markdown";
1014 import { TimeAgo } from "./ui";
1115 import { Badge } from "./ui/badge";
1216 import { Card } from "./ui/card";
17+import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./ui/collapsible";
1318 import { Hint } from "./ui/hint";
19+import { repoAt } from "../lib/markdown-plugins";
20+import { type ToolCall, type ToolStep, quotedBlocks, readToolCall, transcript } from "../lib/session-entries";
1421
15−function Entry({ entry }: { entry: SessionEntry }) {
16− const at = (
22+/** Runs of more tool calls than this fold into one line. */
23+const OPEN_RUN = 3;
24+/** What a tool was called with, longer than this, has the rest to unfold. */
25+const LONG_DETAIL = 90;
26+
27+function At({ at }: { at: string }) {
28+ return (
1729 <span className="shrink-0 text-xs text-faint">
18− <TimeAgo at={entry.at} />
30+ <TimeAgo at={at} />
1931 </span>
2032 );
21− if (entry.kind === "prompt") {
22− return (
23− <li className="px-4 py-3">
24− <details>
25− <summary className="flex cursor-pointer items-center gap-2 text-sm font-medium">
26− <Terminal size={14} className="text-accent" />
27− <span className="grow">Prompt</span>
28− {at}
29− </summary>
30− <pre className="mt-2 max-h-96 overflow-auto rounded-lg bg-bg p-3 text-xs whitespace-pre-wrap text-muted">{entry.text}</pre>
31− </details>
32− </li>
33− );
34− }
35− if (entry.kind === "message") {
33+}
34+
35+/** The prompt, folded under its first line. */
36+function Prompt({ entry, base }: { entry: SessionEntry; base?: string }) {
37+ const [open, setOpen] = useState(false);
38+ const first = entry.text.split("\n").find((line) => line.trim()) ?? "";
39+ return (
40+ <li className="px-4 py-3">
41+ <Collapsible open={open} onOpenChange={setOpen}>
42+ <div className="flex items-center gap-2">
43+ <Terminal size={14} className="shrink-0 text-accent" />
44+ <CollapsibleTrigger className="min-w-0 grow text-left">
45+ <span className="shrink-0 font-medium text-fg">Prompt</span>
46+ {!open && <span className="min-w-0 truncate text-xs text-faint">{first}</span>}
47+ <ChevronRight size={13} className="shrink-0" />
48+ </CollapsibleTrigger>
49+ <At at={entry.at} />
50+ </div>
51+ <CollapsibleContent>
52+ <Card tone="bg" radius="lg" className="mt-2 max-h-128 overflow-auto p-4 text-sm">
53+ <Markdown source={quotedBlocks(entry.text)} repo={repoAt(base)} />
54+ </Card>
55+ </CollapsibleContent>
56+ </Collapsible>
57+ </li>
58+ );
59+}
60+
61+/** What a tool was called on: g1t's tools by resource and action, a command as code, a file by its path. */
62+function ToolLabel({ call }: { call: ToolCall }) {
63+ if (call.server) {
3664 return (
37− <li className="flex gap-2.5 px-4 py-3 text-sm">
38− <MessageSquare size={14} className="mt-0.5 shrink-0 text-faint" />
39− <p className="min-w-0 grow whitespace-pre-wrap wrap-break-word">{entry.text}</p>
40− {at}
41− </li>
65+ <span className="flex min-w-0 items-baseline gap-1.5">
66+ {call.server !== "g1t" && <span className="shrink-0 text-faint">{call.server}</span>}
67+ <span className="shrink-0 font-medium text-fg-soft">{call.name}</span>
68+ {call.detail && (
69+ <>
70+ <span className="text-faint">·</span>
71+ <span className="min-w-0 truncate text-muted">{call.detail}</span>
72+ </>
73+ )}
74+ </span>
4275 );
4376 }
44− if (entry.kind === "tool_call") {
45− return (
46− <li className="flex gap-2.5 px-4 py-2 text-xs">
47− <Wrench size={13} className="mt-0.5 shrink-0 text-faint" />
48− <span className="shrink-0 font-medium text-muted">{entry.tool}</span>
49− <Hint label={entry.text}>
50− <code className="min-w-0 grow truncate font-mono text-fg/85">{entry.text}</code>
77+ return (
78+ <span className="flex min-w-0 items-center gap-2">
79+ <Badge shape="tag" className="font-mono font-normal">
80+ {call.name.toLowerCase()}
81+ </Badge>
82+ {call.detail && <code className="min-w-0 truncate font-mono text-fg/85">{call.detail}</code>}
83+ </span>
84+ );
85+}
86+
87+function ToolIcon({ call }: { call: ToolCall }) {
88+ if (call.server === "g1t") return <MarkGlyph className="h-3" />;
89+ const Icon = call.server ? Plug : call.sort === "shell" ? Terminal : call.sort === "file" ? FileText : Wrench;
90+ return <Icon size={13} className="text-faint" />;
91+}
92+
93+/** A block of text a tool was called with or gave back. */
94+function Block({ children }: { children: ReactNode }) {
95+ return (
96+ <Card asChild tone="bg" radius="lg" className="mt-1.5 max-h-72 overflow-auto px-3 py-2 font-mono text-xs whitespace-pre-wrap break-all text-muted">
97+ <pre>{children}</pre>
98+ </Card>
99+ );
100+}
101+
102+/** One tool call in a line; a long one, and what came back, unfold under it. */
103+function ToolRow({ step }: { step: ToolStep }) {
104+ const [open, setOpen] = useState(false);
105+ const call = step.call ? readToolCall(step.call) : null;
106+ const long = Boolean(call && call.detail.length > LONG_DETAIL);
107+ const label = call ? <ToolLabel call={call} /> : <span className="text-muted">Result</span>;
108+ return (
109+ <li className="flex gap-2 py-0.5 text-xs">
110+ <span className="flex h-4.5 w-4 shrink-0 items-center justify-center">{call ? <ToolIcon call={call} /> : <Wrench size={13} className="text-faint" />}</span>
111+ {long || step.results.length > 0 ? (
112+ <Collapsible open={open} onOpenChange={setOpen} className="min-w-0 grow">
113+ <CollapsibleTrigger className="flex w-full min-w-0 text-left text-xs">
114+ {label}
115+ <span className="ml-auto flex shrink-0 items-center gap-1 text-faint">
116+ {call && step.results.length > 0 && (step.results.length === 1 ? "result" : `${step.results.length} results`)}
117+ <ChevronRight size={12} />
118+ </span>
119+ </CollapsibleTrigger>
120+ <CollapsibleContent>
121+ {long && call && <Block>{call.detail}</Block>}
122+ {step.results.map((result) => (
123+ <Block key={result.seq}>{result.text}</Block>
124+ ))}
125+ </CollapsibleContent>
126+ </Collapsible>
127+ ) : (
128+ <Hint label={call && call.detail.length > 40 ? call.detail : null}>
129+ <span className="flex min-w-0 grow">{label}</span>
51130 </Hint>
52− </li>
53− );
131+ )}
132+ </li>
133+ );
134+}
135+
136+/** The tools of a run, each once, for its folded line. */
137+function toolNames(steps: ToolStep[]): string {
138+ const names = new Set<string>();
139+ for (const step of steps) {
140+ if (!step.call) continue;
141+ const call = readToolCall(step.call);
142+ names.add(call.server && call.server !== "g1t" ? call.server : call.name.toLowerCase());
54143 }
55− if (entry.kind === "tool_result") {
144+ return [...names].join(", ");
145+}
146+
147+/** Tool calls one after another: a few in a tight list, more folded into one line that opens to it. */
148+function ToolRun({ steps }: { steps: ToolStep[] }) {
149+ const [open, setOpen] = useState(false);
150+ const calls = steps.filter((step) => step.call).length;
151+ const list = (
152+ <ol className="space-y-0.5">
153+ {steps.map((step) => (
154+ <ToolRow key={(step.call ?? step.results[0]).seq} step={step} />
155+ ))}
156+ </ol>
157+ );
158+ if (steps.length <= OPEN_RUN) return <li className="px-4 py-2">{list}</li>;
159+ return (
160+ <li className="px-4 py-2">
161+ <Collapsible open={open} onOpenChange={setOpen}>
162+ <div className="flex items-center gap-2 text-xs">
163+ <span className="flex w-4 shrink-0 justify-center">
164+ <Wrench size={13} className="text-faint" />
165+ </span>
166+ <CollapsibleTrigger className="min-w-0 text-left text-xs">
167+ <span className="shrink-0 font-medium text-fg-soft">{calls === 1 ? "1 tool call" : `${calls} tool calls`}</span>
168+ {!open && <span className="min-w-0 truncate text-faint">{toolNames(steps)}</span>}
169+ <ChevronRight size={12} className="shrink-0" />
170+ </CollapsibleTrigger>
171+ </div>
172+ <CollapsibleContent className="mt-1">{list}</CollapsibleContent>
173+ </Collapsible>
174+ </li>
175+ );
176+}
177+
178+function Entry({ entry, base }: { entry: SessionEntry; base?: string }) {
179+ if (entry.kind === "prompt") return <Prompt entry={entry} base={base} />;
180+ if (entry.kind === "message") {
56181 return (
57− <li className="px-4 py-1.5 pl-10">
58− <details>
59− <summary className="cursor-pointer text-xs text-faint">Result</summary>
60− <pre className="mt-1.5 max-h-72 overflow-auto rounded-lg bg-bg p-3 text-xs text-muted">{entry.text}</pre>
61− </details>
182+ <li className="flex gap-2.5 px-4 py-3 text-sm">
183+ <MessageSquare size={14} className="mt-1 shrink-0 text-faint" />
184+ <div className="min-w-0 grow">
185+ <Markdown source={entry.text} repo={repoAt(base)} />
186+ </div>
187+ <At at={entry.at} />
62188 </li>
63189 );
64190 }
69195 <span className="min-w-0 grow">
70196 Used skill <Badge shape="tag" className="font-mono">{entry.text}</Badge>
71197 </span>
72− {at}
198+ <At at={entry.at} />
73199 </li>
74200 );
75201 }
77203 <li className="flex gap-2.5 px-4 py-2.5 text-xs text-muted">
78204 <StickyNote size={13} className="mt-0.5 shrink-0 text-faint" />
79205 <span className="min-w-0 grow">{entry.text}</span>
80− {at}
206+ <At at={entry.at} />
81207 </li>
82208 );
83209 }
84210
85−/** A session's entries in a card, oldest first. */
86−export function SessionEntries({ entries }: { entries: SessionEntry[] }) {
211+/**
212+ * A session's entries in a card, oldest first. `base`, the repository's
213+ * path (`/acme/web`), links `#12` and the like in what it wrote.
214+ */
215+export function SessionEntries({ entries, base }: { entries: SessionEntry[]; base?: string }) {
87216 return (
88217 <Card asChild divided>
89218 <ol>
90− {entries.map((entry) => (
91− <Entry key={entry.seq} entry={entry} />
92− ))}
219+ {transcript(entries).map((item) =>
220+ item.kind === "tools" ? <ToolRun key={item.first.seq} steps={item.steps} /> : <Entry key={item.entry.seq} entry={item.entry} base={base} />,
221+ )}
93222 </ol>
94223 </Card>
95224 );
+59−0
1+import assert from "node:assert/strict";
2+import { test } from "node:test";
3+
4+import type { SessionEntry } from "@g1t/contracts";
5+
6+import { quotedBlocks, readToolCall, transcript } from "./session-entries.ts";
7+
8+let seq = 0;
9+const entry = (kind: SessionEntry["kind"], text: string, tool: string | null = null): SessionEntry => ({ seq: ++seq, kind, text, tool, commit: null, at: "2026-10-14T00:00:00Z" });
10+
11+test("a tool call says its tool once", () => {
12+ assert.deepEqual(readToolCall({ text: "bash: cd /workspace/demo && git status --short", tool: "bash" }), {
13+ name: "bash",
14+ server: null,
15+ detail: "cd /workspace/demo && git status --short",
16+ sort: "shell",
17+ });
18+ assert.deepEqual(readToolCall({ text: "write: index.html", tool: "write" }), { name: "write", server: null, detail: "index.html", sort: "file" });
19+ // A text that does not repeat its tool is kept whole.
20+ assert.equal(readToolCall({ text: "ls -la", tool: "bash" }).detail, "ls -la");
21+ assert.equal(readToolCall({ text: "Glob", tool: "Glob" }).detail, "");
22+});
23+
24+test("an MCP tool reads by its server, resource and action", () => {
25+ assert.deepEqual(readToolCall({ text: "g1t:issue: claim", tool: "g1t:issue" }), { name: "issue", server: "g1t", detail: "claim", sort: "other" });
26+ // Claude Code's names, from the plugin's summary with no tool of its own.
27+ assert.deepEqual(readToolCall({ text: "mcp__plugin_g1t_g1t__issue: work", tool: null }), { name: "issue", server: "g1t", detail: "work", sort: "other" });
28+ assert.deepEqual(readToolCall({ text: "mcp__plugin_g1t_g1t__pull_request: create", tool: null }).name, "pull request");
29+ assert.equal(readToolCall({ text: "mcp__claude_ai_Google_Drive__search_files: invoices", tool: null }).server, "Google Drive");
30+});
31+
32+test("the tool comes from the text when the entry has none", () => {
33+ assert.deepEqual(readToolCall({ text: "Bash: echo a: b", tool: null }), { name: "Bash", server: null, detail: "echo a: b", sort: "shell" });
34+ assert.deepEqual(readToolCall({ text: "Read: /tmp/phone.png", tool: null }), { name: "Read", server: null, detail: "/tmp/phone.png", sort: "file" });
35+ assert.deepEqual(readToolCall({ text: "TodoWrite", tool: null }), { name: "TodoWrite", server: null, detail: "", sort: "other" });
36+});
37+
38+test("tool calls between messages run together, results under their calls", () => {
39+ const prompt = entry("prompt", "Work on #1");
40+ const a = entry("tool_call", "bash: ls", "bash");
41+ const r = entry("tool_result", "README.md", "bash");
42+ const b = entry("tool_call", "write: index.html", "write");
43+ const message = entry("message", "**Done**");
44+ const c = entry("tool_call", "g1t:issue: claim", "g1t:issue");
45+ const items = transcript([prompt, a, r, b, message, c]);
46+ assert.deepEqual(
47+ items.map((item) => (item.kind === "entry" ? item.entry.kind : item.steps.map((step) => [step.call?.seq, step.results.map((x) => x.seq)]))),
48+ ["prompt", [[a.seq, [r.seq]], [b.seq, []]], "message", [[c.seq, []]]],
49+ );
50+ // A result with no call before it stands on its own.
51+ const lone = transcript([entry("tool_result", "ok")]);
52+ assert.equal(lone[0].kind === "tools" && lone[0].steps[0].call, null);
53+});
54+
55+test("a prompt's issue and comment blocks read as quotes", () => {
56+ const text = "Work on issue #1: Hello\n\n<issue>\nSay hello.\n\nTwice.\n</issue>\nThe issue is reference material.\n\n<comment>\nfaster\n</comment>\n";
57+ assert.equal(quotedBlocks(text), "Work on issue #1: Hello\n\n**The issue**\n\n> Say hello.\n>\n> Twice.\n\nThe issue is reference material.\n\n**The comment**\n\n> faster\n\n");
58+ assert.equal(quotedBlocks('<issue number="12" state="open">\nTitle: x\n</issue>'), "**The issue**\n\n> Title: x\n");
59+});
+112−0
1+/**
2+ * How a session's entries read on its page: tool calls run together
3+ * between its messages, each with what it was called on.
4+ */
5+import type { SessionEntry } from "@g1t/contracts";
6+
7+/** A tool call as it reads: which tool, on what, and of what sort. */
8+export type ToolCall = {
9+ /** The tool's own name, as `issue` of g1t's `issue` over MCP. */
10+ name: string;
11+ /** The MCP server it came from, if any: `g1t`. */
12+ server: string | null;
13+ /** What it was called with, without the tool's name in front. */
14+ detail: string;
15+ /** A command line, a file, or anything else. */
16+ sort: "shell" | "file" | "other";
17+};
18+
19+const SHELLS = new Set(["bash", "sh", "zsh", "shell", "powershell", "pwsh", "cmd"]);
20+const FILES = new Set(["read", "write", "edit", "multiedit", "notebookedit", "notebook_edit"]);
21+
22+/** A server's name from Claude Code's `mcp__<server>__<tool>`: `plugin_g1t_g1t` is g1t, `claude_ai_Google_Drive` Google Drive. */
23+function serverName(raw: string): string {
24+ const plugin = /^plugin_[^_]+_(.+)$/.exec(raw);
25+ const name = plugin ? plugin[1] : raw.replace(/^claude_ai_/, "");
26+ return name.replaceAll("_", " ");
27+}
28+
29+/**
30+ * A tool call's entry, read: its tool (the entry's own, else the name its
31+ * text starts with), what it was called with, with that name taken off the
32+ * front where the text repeats it, and its MCP server (`g1t:issue` from a
33+ * session g1t ran, `mcp__plugin_g1t_g1t__issue` from Claude Code).
34+ */
35+export function readToolCall(entry: Pick<SessionEntry, "text" | "tool">): ToolCall {
36+ const text = entry.text.trim();
37+ let tool = entry.tool?.trim() || null;
38+ let detail = text;
39+ if (tool) {
40+ const prefix = `${tool}:`;
41+ if (text.toLowerCase() === tool.toLowerCase()) detail = "";
42+ else if (text.toLowerCase().startsWith(prefix.toLowerCase())) detail = text.slice(prefix.length).trim();
43+ } else {
44+ // A name, then ": " and what it was called with (the plugin's summary).
45+ const match = /^([A-Za-z][\w.:-]*?):\s+([\s\S]*)$/.exec(text) ?? (/^[A-Za-z][\w.:-]*$/.test(text) ? [text, text, ""] : null);
46+ if (match) {
47+ tool = match[1];
48+ detail = match[2].trim();
49+ }
50+ }
51+ let name = tool ?? "tool";
52+ let server: string | null = null;
53+ const mcp = /^mcp__(.+?)__(.+)$/.exec(name);
54+ if (mcp) {
55+ server = serverName(mcp[1]);
56+ name = mcp[2];
57+ } else if (name.includes(":")) {
58+ const at = name.indexOf(":");
59+ server = name.slice(0, at);
60+ name = name.slice(at + 1);
61+ }
62+ const key = name.toLowerCase();
63+ // An MCP tool's name reads as words: g1t's `pull_request` is "pull request".
64+ if (server) name = name.replaceAll("_", " ");
65+ const sort = server ? "other" : SHELLS.has(key) ? "shell" : FILES.has(key) && detail ? "file" : "other";
66+ return { name, server, detail, sort };
67+}
68+
69+/** One tool call and what came back from it. */
70+export type ToolStep = { call: SessionEntry | null; results: SessionEntry[] };
71+
72+/** A part of the transcript: one entry, or tool calls that ran one after another. */
73+export type TranscriptItem = { kind: "entry"; entry: SessionEntry } | { kind: "tools"; steps: ToolStep[]; first: SessionEntry };
74+
75+/**
76+ * Entries as the transcript shows them: prompts, messages, skills and notes
77+ * one by one, and each run of tool calls between them as one group, every
78+ * result under the call before it.
79+ */
80+export function transcript(entries: SessionEntry[]): TranscriptItem[] {
81+ const items: TranscriptItem[] = [];
82+ let group: Extract<TranscriptItem, { kind: "tools" }> | null = null;
83+ for (const entry of entries) {
84+ if (entry.kind !== "tool_call" && entry.kind !== "tool_result") {
85+ group = null;
86+ items.push({ kind: "entry", entry });
87+ continue;
88+ }
89+ if (!group) {
90+ group = { kind: "tools", steps: [], first: entry };
91+ items.push(group);
92+ }
93+ const last = group.steps.at(-1);
94+ if (entry.kind === "tool_call") group.steps.push({ call: entry, results: [] });
95+ else if (last) last.results.push(entry);
96+ else group.steps.push({ call: null, results: [entry] });
97+ }
98+ return items;
99+}
100+
101+/**
102+ * A prompt with g1t's `<issue>` and `<comment>` blocks (what it quotes of
103+ * an issue or a comment) as quotes under a label, so they read as markdown
104+ * rather than as tags that vanish.
105+ */
106+export function quotedBlocks(text: string): string {
107+ return text.replace(/^<(issue|comment)\b[^>\n]*>\n([\s\S]*?)\n<\/\1>[ \t]*$/gm, (_, tag: string, body: string) => {
108+ const label = tag === "issue" ? "**The issue**" : "**The comment**";
109+ // A blank line after it, so the text that follows is not part of the quote.
110+ return [label, "", ...body.split("\n").map((line) => (line ? `> ${line}` : ">")), ""].join("\n");
111+ });
112+}
+1−1
6464 <p className="mt-3 text-sm text-muted">Nothing recorded yet.</p>
6565 ) : (
6666 <div className="mt-3">
67− <SessionEntries entries={shown} />
67+ <SessionEntries entries={shown} base={base} />
6868 </div>
6969 )}
7070 {entries.length >= 2000 && (
+1−1
466466 {entries.length === 0 ? (
467467 <EmptyState title="Nothing recorded yet">Prompts, messages, tool calls, skills and notes show here as the session records them.</EmptyState>
468468 ) : (
469− <SessionEntries entries={entries} />
469+ <SessionEntries entries={entries} base={base} />
470470 )}
471471 </Section>
472472 </div>

Comments

Sign in to comment.