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.
| Merge branch 'chat-sidebar' into fast-push | 1 | import { BellOff, Bot, ChevronDown, Compass, Hash, Lock, Pin, PinOff, Plus, Search } from "lucide-react"; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 2 | import { ContextMenu } from "radix-ui"; |
| Chat and workspace agents: channels, DMs and named agents you talk to | 3 | import { type ReactNode, useEffect, useState } from "react"; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 4 | import { Link, NavLink, useFetcher, useLocation, useNavigate } from "react-router"; |
| Chat and workspace agents: channels, DMs and named agents you talk to | 5 | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 6 | import type { ChatSidebarEntry, WorkspaceAgent } from "@g1t/contracts"; |
| Chat and workspace agents: channels, DMs and named agents you talk to | 7 | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 8 | import { conversationCache } from "./conversation-cache"; |
| 9 | import { CreateChannelButton, NewMessageButton, useChatData, useChatSend, useChatSidebar } from "./actions"; | |
| Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules. | 10 | import { DndMenu } from "./dnd-menu"; |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 11 | import { MemberAvatar, PairAvatar, StatusDot, statusLabel } from "./marks"; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 12 | import { AgentAvatar } from "../agent-avatar"; |
| One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers | 13 | import { PersonStatusEmoji } from "../presence"; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 14 | import { isOrchestrator } from "../orchestrator"; |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 15 | import { Badge } from "../ui/badge"; |
| 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. | 16 | import { Button } from "../ui/button"; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 17 | import { Hint } from "../ui/hint"; |
| Chat and workspace agents: channels, DMs and named agents you talk to | 18 | import { Skeleton } from "../ui/skeleton"; |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 19 | import { type ChatFilter, agentDmOf, agentsAmong, channelPath, filterEntries, mixedDm, sections } from "../../lib/chat"; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 20 | import { feedConnected } from "../../lib/notify-client"; |
| 21 | import { FALLBACK_REFRESH_MS } from "../../lib/notify-store"; | |
| Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules. | 22 | import { setMutedConversations } from "../../lib/sound-events"; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 23 | |
| 24 | /** How often the sidebar asks again while the tab is shown and the feed socket is down. */ | |
| 25 | const REFRESH_MS = FALLBACK_REFRESH_MS; | |
| 26 | ||
| 27 | const ROW = | |
| The left-hand navigation takes less room and reads at a glance. The dock becomes a rail: a 56-pixel column of icons with the g1t mark on top, the current app marked by a filled square, hover hints naming each with its shortcut, the apps you pinned in a scrolling band, and Apps, People, Workspace and your account at the foot. Each mode's sidebar sits flush against it, 240 pixels wide, opening on Search or jump to and listing its groups under small labels in 36-pixel rows; Ctrl B hides it and the page grows into the room. Every mode's list is built on the shadcn Sidebar now in the kit, so Workspace, Code, People, Notifications, account settings, Chat, Artifacts and Agents all share one frame; the phone keeps its bottom bar and drawer. The workspaces guide describes the rail, the sidebar and the page. | 28 | "group/row flex h-9 items-center gap-2 rounded-md pr-1.5 pl-2 text-[0.8125rem] transition-colors max-md:h-11 max-md:text-[0.9375rem] max-md:active:bg-raised"; |
| Chat and workspace agents: channels, DMs and named agents you talk to | 29 | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 30 | /** Unread on a row: lavender for a mention, quiet for the rest. */ |
| 31 | function Count({ entry }: { entry: ChatSidebarEntry | undefined }) { | |
| 32 | if (!entry || entry.muted) return null; | |
| 33 | const count = entry.mentions > 0 ? entry.mentions : entry.unread; | |
| 34 | if (count <= 0) return null; | |
| 35 | return ( | |
| 36 | <span | |
| 37 | className={`min-w-5 shrink-0 rounded-full group-hover/row:invisible px-1.5 text-center text-[0.6875rem] leading-[1.125rem] font-semibold tabular-nums ${ | |
| 38 | entry.mentions > 0 ? "bg-accent text-bg" : "bg-line-strong text-fg" | |
| 39 | }`} | |
| 40 | > | |
| 41 | {count} | |
| 42 | </span> | |
| 43 | ); | |
| 44 | } | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 45 | |
| 46 | /** | |
| Merge branch 'chat-sidebar' into fast-push | 47 | * Chat mode's sidebar: what you pinned, the channels (and a way to browse |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 48 | * the rest), then direct messages in two buckets so it is always clear who |
| 49 | * is on the other side: People, your conversations with people, the latest | |
| 50 | * first (a conversation with a person and an agent in it is here, marked | |
| 51 | * as such), and Agents, your conversations with agents alone, the latest | |
| 52 | * first, then the agents you have not talked to yet, g1t first: one click | |
| 53 | * opens a conversation with any of them. Sections fold, and stay folded on | |
| 54 | * this device. All, Unread and Mentions filter every section. It refreshes | |
| 55 | * itself while the tab is shown, so unread counts move without a reload. | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 56 | */ |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 57 | export function ChatSidebar({ slug, heading = true }: { slug: string; heading?: boolean }) { |
| Chat and workspace agents: channels, DMs and named agents you talk to | 58 | const data = useChatData(); |
| 59 | const { sidebar, refresh } = useChatSidebar(); | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 60 | const send = useChatSend(slug); |
| Chat and workspace agents: channels, DMs and named agents you talk to | 61 | const [filter, setFilter] = useState<ChatFilter>("all"); |
| 62 | const [query, setQuery] = useState(""); | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 63 | const [opening, setOpening] = useState<string | null>(null); |
| Chat and workspace agents: channels, DMs and named agents you talk to | 64 | const navigate = useNavigate(); |
| 65 | const { pathname } = useLocation(); | |
| 66 | ||
| 67 | useEffect(() => { | |
| 68 | if (!data) return; | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 69 | // Notify: counts arrive live over the feed socket; this asks again only while it is down. |
| Chat and workspace agents: channels, DMs and named agents you talk to | 70 | const tick = () => { |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 71 | if (document.visibilityState === "visible" && !feedConnected()) refresh(); |
| Chat and workspace agents: channels, DMs and named agents you talk to | 72 | }; |
| 73 | const timer = setInterval(tick, REFRESH_MS); | |
| 74 | document.addEventListener("visibilitychange", tick); | |
| 75 | return () => { | |
| 76 | clearInterval(timer); | |
| 77 | document.removeEventListener("visibilitychange", tick); | |
| 78 | }; | |
| 79 | }, [data, refresh]); | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 80 | |
| 81 | // Pins show at once and go back if the service refuses. | |
| 82 | const [pins, setPins] = useState<Map<string, boolean>>(new Map()); | |
| 83 | useEffect(() => setPins(new Map()), [sidebar]); | |
| Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules. | 84 | // Sounds leave muted conversations alone (lib/sound-events.ts). |
| 85 | useEffect(() => { | |
| 86 | if (sidebar) setMutedConversations(sidebar.entries.filter((e) => e.muted).map((e) => e.channel.id)); | |
| 87 | }, [sidebar]); | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 88 | const entries = (sidebar?.entries ?? []).map((e) => (pins.has(e.channel.id) ? { ...e, starred: pins.get(e.channel.id)! } : e)); |
| Chat and workspace agents: channels, DMs and named agents you talk to | 89 | const shown = filterEntries(entries, filter, query); |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 90 | const { pinned, channels, people, agents: agentConversations } = sections(shown); |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 91 | const { agentDms } = sections(entries); |
| Chat and workspace agents: channels, DMs and named agents you talk to | 92 | const loading = !data; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 93 | const owner = data?.role === "owner"; |
| 94 | ||
| Merge branch 'chat-sidebar' into fast-push | 95 | // The agents you have not talked to yet, g1t first: one click opens a |
| 96 | // conversation, which then lists under Direct messages like anyone's. | |
| 97 | // Nothing of theirs is unread, so Unread and Mentions leave them out. | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 98 | const q = query.trim().toLowerCase().replace(/^@/, ""); |
| Merge branch 'chat-sidebar' into fast-push | 99 | const agents = |
| 100 | filter !== "all" | |
| 101 | ? [] | |
| 102 | : [...(data?.agents ?? [])] | |
| 103 | .filter((agent) => !agentDms.has(agent.id)) | |
| 104 | .sort((a, b) => Number(isOrchestrator(b)) - Number(isOrchestrator(a)) || a.display_name.localeCompare(b.display_name)) | |
| 105 | .filter((agent) => !q || agent.handle.includes(q) || agent.display_name.toLowerCase().includes(q) || agent.role.toLowerCase().includes(q)); | |
| 106 | ||
| 107 | // Folded sections, remembered per workspace on this device. A folded | |
| 108 | // section still shows what is unread and the conversation open now. | |
| 109 | const [folded, setFolded] = useState<ReadonlySet<string>>(new Set()); | |
| 110 | useEffect(() => setFolded(readFolded(slug)), [slug]); | |
| 111 | const fold = (id: string) => | |
| 112 | setFolded((now) => { | |
| 113 | const next = new Set(now); | |
| 114 | if (!next.delete(id)) next.add(id); | |
| 115 | writeFolded(slug, next); | |
| 116 | return next; | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 117 | }); |
| Merge branch 'chat-sidebar' into fast-push | 118 | const keepShown = (entry: ChatSidebarEntry) => { |
| 119 | const to = channelPath(slug, entry.channel); | |
| 120 | return pathname === to || pathname.startsWith(`${to}/`) || (!entry.muted && (entry.unread > 0 || entry.mentions > 0)); | |
| 121 | }; | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 122 | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 123 | /** The direct message with an agent: opened, or made on first use. */ |
| 124 | const openAgent = async (agent: WorkspaceAgent, pin = false) => { | |
| 125 | const had = agentDms.get(agent.id); | |
| 126 | if (had && !pin) return navigate(channelPath(slug, had.channel)); | |
| 127 | setOpening(agent.id); | |
| 128 | const opened = had ? { ok: true as const, value: { channel: had.channel, to: channelPath(slug, had.channel) } } : await send<{ channel: ChatSidebarEntry["channel"]; to: string }>({ intent: "dm", members: [`agent:${agent.id}`] }); | |
| 129 | if (opened.ok && pin) await send({ intent: "preferences", channel_id: opened.value.channel.id, starred: !had?.starred }); | |
| 130 | setOpening(null); | |
| 131 | if (opened.ok && !pin) navigate(opened.value.to); | |
| 132 | if (pin) refresh(); | |
| 133 | }; | |
| 134 | const togglePin = async (entry: ChatSidebarEntry) => { | |
| 135 | const next = !entry.starred; | |
| 136 | setPins((now) => new Map(now).set(entry.channel.id, next)); | |
| 137 | const done = await send({ intent: "preferences", channel_id: entry.channel.id, starred: next }); | |
| 138 | if (!done.ok) | |
| 139 | setPins((now) => { | |
| 140 | const back = new Map(now); | |
| 141 | back.delete(entry.channel.id); | |
| 142 | return back; | |
| 143 | }); | |
| 144 | }; | |
| 145 | ||
| 146 | const conversation = (entry: ChatSidebarEntry) => ( | |
| 147 | <ConversationRow key={entry.channel.id} entry={entry} slug={slug} pathname={pathname} agents={data?.agents ?? []} onPin={() => void togglePin(entry)} /> | |
| 148 | ); | |
| 149 | ||
| Chat and workspace agents: channels, DMs and named agents you talk to | 150 | return ( |
| One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers | 151 | // The colour behind it, for the dots cut out of people's avatars. |
| The left-hand navigation takes less room and reads at a glance. The dock becomes a rail: a 56-pixel column of icons with the g1t mark on top, the current app marked by a filled square, hover hints naming each with its shortcut, the apps you pinned in a scrolling band, and Apps, People, Workspace and your account at the foot. Each mode's sidebar sits flush against it, 240 pixels wide, opening on Search or jump to and listing its groups under small labels in 36-pixel rows; Ctrl B hides it and the page grows into the room. Every mode's list is built on the shadcn Sidebar now in the kit, so Workspace, Code, People, Notifications, account settings, Chat, Artifacts and Agents all share one frame; the phone keeps its bottom bar and drawer. The workspaces guide describes the rail, the sidebar and the page. | 152 | <div className="flex h-full flex-col [--chat-sidebar-bg:var(--color-sidebar)]"> |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 153 | {heading && ( |
| 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. | 154 | <div className="flex h-9 shrink-0 items-center justify-between pr-1 pl-3"> |
| The left-hand navigation takes less room and reads at a glance. The dock becomes a rail: a 56-pixel column of icons with the g1t mark on top, the current app marked by a filled square, hover hints naming each with its shortcut, the apps you pinned in a scrolling band, and Apps, People, Workspace and your account at the foot. Each mode's sidebar sits flush against it, 240 pixels wide, opening on Search or jump to and listing its groups under small labels in 36-pixel rows; Ctrl B hides it and the page grows into the room. Every mode's list is built on the shadcn Sidebar now in the kit, so Workspace, Code, People, Notifications, account settings, Chat, Artifacts and Agents all share one frame; the phone keeps its bottom bar and drawer. The workspaces guide describes the rail, the sidebar and the page. | 155 | <h2 className="text-[0.6875rem] font-medium tracking-wide text-faint uppercase">Chat</h2> |
| Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules. | 156 | <div className="flex items-center gap-0.5"> |
| 157 | <DndMenu /> | |
| 158 | <NewMessageButton slug={slug} /> | |
| 159 | </div> | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 160 | </div> |
| 161 | )} | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 162 | <div className="space-y-2.5 px-2.5 pt-3"> |
| Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules. | 163 | <div className="flex items-center gap-2"> |
| 164 | <label className="flex h-8 min-w-0 grow items-center gap-2 rounded-md bg-surface px-2.5 text-[0.8125rem] ring-1 ring-line transition-shadow focus-within:ring-accent-dim/70"> | |
| 165 | <Search size={14} className="shrink-0 text-faint" /> | |
| 166 | <input | |
| 167 | value={query} | |
| 168 | onChange={(event) => setQuery(event.target.value)} | |
| 169 | onKeyDown={(event) => { | |
| 170 | if (event.key === "Enter") { | |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 171 | const first = [...pinned, ...channels, ...people, ...agentConversations][0]; |
| Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules. | 172 | if (first) { |
| 173 | navigate(channelPath(slug, first.channel)); | |
| 174 | setQuery(""); | |
| 175 | } else if (agents[0]) void openAgent(agents[0]); | |
| 176 | } else if (event.key === "Escape") setQuery(""); | |
| 177 | }} | |
| 178 | placeholder="Jump to channel or person" | |
| 179 | aria-label="Jump to channel or person" | |
| 180 | autoComplete="off" | |
| 181 | data-1p-ignore | |
| 182 | className="min-w-0 grow bg-transparent text-fg outline-none placeholder:text-faint" | |
| 183 | /> | |
| 184 | </label> | |
| 185 | {/* A phone has no heading row: Do not disturb sits by the search box instead. */} | |
| 186 | {!heading && <DndMenu className="size-8 max-md:size-11" />} | |
| 187 | </div> | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 188 | <div role="radiogroup" aria-label="Show" className="grid grid-cols-3 rounded-md bg-surface p-0.5 ring-1 ring-line"> |
| 189 | {(["all", "unread", "mentions"] as const).map((key) => ( | |
| 190 | <button | |
| 191 | key={key} | |
| 192 | type="button" | |
| 193 | role="radio" | |
| 194 | aria-checked={filter === key} | |
| 195 | onClick={() => setFilter(key)} | |
| 196 | className={`h-6 rounded-[5px] text-xs font-medium capitalize transition-colors ${ | |
| 197 | filter === key ? "bg-raised text-fg shadow-sm ring-1 ring-line-strong" : "text-muted hover:text-fg" | |
| 198 | }`} | |
| 199 | > | |
| 200 | {key} | |
| 201 | </button> | |
| 202 | ))} | |
| 203 | </div> | |
| 204 | </div> | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 205 | {/* A phone's list (no heading): room at the end so the compose button never covers the last row. */} |
| 206 | <nav | |
| 207 | aria-label="Conversations" | |
| 208 | className={`min-h-0 grow overflow-y-auto px-2.5 pt-3 [scrollbar-width:thin] ${heading ? "pb-4" : "pb-[calc(5.5rem+env(safe-area-inset-bottom))]"}`} | |
| 209 | > | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 210 | {loading ? ( |
| 211 | <SidebarSkeleton /> | |
| 212 | ) : !sidebar ? ( | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 213 | <p className="px-2 py-3 text-xs leading-relaxed text-faint">Chat didn't answer. Your conversations will show here once it does.</p> |
| Chat and workspace agents: channels, DMs and named agents you talk to | 214 | ) : ( |
| 215 | <> | |
| Merge branch 'chat-sidebar' into fast-push | 216 | {pinned.length > 0 && ( |
| 217 | <Section id="pinned" title="Pinned" folded={folded} onFold={fold} keep={pinned.filter(keepShown).map(conversation)}> | |
| 218 | {pinned.map(conversation)} | |
| 219 | </Section> | |
| 220 | )} | |
| 221 | <Section | |
| 222 | id="channels" | |
| 223 | title="Channels" | |
| 224 | folded={folded} | |
| 225 | onFold={fold} | |
| 226 | action={<CreateChannelButton slug={slug} />} | |
| 227 | keep={channels.filter(keepShown).map(conversation)} | |
| 228 | > | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 229 | {channels.map(conversation)} |
| Chat and workspace agents: channels, DMs and named agents you talk to | 230 | {channels.length === 0 && filter === "all" && !query && <Empty>No channels yet.</Empty>} |
| Merge branch 'chat-sidebar' into fast-push | 231 | {filter === "all" && !query && ( |
| 232 | <ExtraRow to={`/${slug}/-/chat/browse`} icon={<Compass size={15} />} count={sidebar.browsable > 0 ? sidebar.browsable : undefined} countLabel="to join"> | |
| 233 | Browse channels | |
| 234 | </ExtraRow> | |
| 235 | )} | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 236 | </Section> |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 237 | <Section |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 238 | id="people" |
| 239 | title="People" | |
| Merge branch 'chat-sidebar' into fast-push | 240 | folded={folded} |
| 241 | onFold={fold} | |
| 242 | action={<NewMessageButton slug={slug} />} | |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 243 | keep={people.filter(keepShown).map(conversation)} |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 244 | > |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 245 | {people.map(conversation)} |
| 246 | {people.length === 0 && filter === "all" && !query && <Empty>Message a teammate.</Empty>} | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 247 | </Section> |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 248 | {(agentConversations.length > 0 || agents.length > 0 || (filter === "all" && !query)) && ( |
| Merge branch 'chat-sidebar' into fast-push | 249 | <Section |
| 250 | id="agents" | |
| 251 | title="Agents" | |
| 252 | folded={folded} | |
| 253 | onFold={fold} | |
| 254 | action={ | |
| 255 | owner ? ( | |
| 256 | <Hint label="New agent"> | |
| 257 | <Link | |
| 258 | to={`/${slug}/-/agents/new`} | |
| 259 | aria-label="New agent" | |
| 260 | className="flex size-7 items-center justify-center rounded-md text-faint transition-colors hover:bg-raised hover:text-fg" | |
| 261 | > | |
| 262 | <Plus size={14} /> | |
| 263 | </Link> | |
| 264 | </Hint> | |
| 265 | ) : null | |
| 266 | } | |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 267 | keep={agentConversations.filter(keepShown).map(conversation)} |
| Merge branch 'chat-sidebar' into fast-push | 268 | > |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 269 | {agentConversations.map(conversation)} |
| 270 | {agentConversations.length === 0 && agents.length === 0 && filter === "all" && !query && <Empty>Message an agent.</Empty>} | |
| Merge branch 'chat-sidebar' into fast-push | 271 | {agents.map((agent) => ( |
| 272 | <AgentRow | |
| 273 | key={agent.id} | |
| 274 | agent={agent} | |
| 275 | busy={opening === agent.id} | |
| 276 | onOpen={() => void openAgent(agent)} | |
| 277 | onPin={() => void openAgent(agent, true)} | |
| 278 | /> | |
| 279 | ))} | |
| 280 | {filter === "all" && !query && ( | |
| 281 | <ExtraRow to={`/${slug}/-/agents`} icon={<Bot size={15} />}> | |
| 282 | {(data?.agents ?? []).length === 0 ? (owner ? "Hire an agent" : "No agents yet") : "All agents"} | |
| 283 | </ExtraRow> | |
| 284 | )} | |
| 285 | </Section> | |
| 286 | )} | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 287 | {shown.length === 0 && agents.length === 0 && (filter !== "all" || query) && ( |
| 288 | <p className="px-2 py-2 text-xs text-faint">{query ? "Nothing matches." : filter === "unread" ? "You're all caught up." : "No mentions."}</p> | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 289 | )} |
| 290 | </> | |
| 291 | )} | |
| 292 | </nav> | |
| 293 | </div> | |
| 294 | ); | |
| 295 | } | |
| 296 | ||
| Merge branch 'chat-sidebar' into fast-push | 297 | const FOLDED_KEY = (slug: string) => `g1t:chat-folded:${slug}`; |
| 298 | ||
| 299 | /** The sections folded in this workspace, as last left; none if storage is not there. */ | |
| 300 | function readFolded(slug: string): ReadonlySet<string> { | |
| 301 | try { | |
| 302 | const saved = JSON.parse(localStorage.getItem(FOLDED_KEY(slug)) ?? "[]"); | |
| 303 | return new Set(Array.isArray(saved) ? saved.filter((id): id is string => typeof id === "string") : []); | |
| 304 | } catch { | |
| 305 | return new Set(); | |
| 306 | } | |
| 307 | } | |
| 308 | ||
| 309 | function writeFolded(slug: string, folded: ReadonlySet<string>) { | |
| 310 | try { | |
| 311 | if (folded.size === 0) localStorage.removeItem(FOLDED_KEY(slug)); | |
| 312 | else localStorage.setItem(FOLDED_KEY(slug), JSON.stringify([...folded])); | |
| 313 | } catch { | |
| 314 | // Private windows and blocked storage: folding still works until a reload. | |
| 315 | } | |
| 316 | } | |
| 317 | ||
| 318 | /** | |
| 319 | * A titled part of the sidebar that folds away. Folded, it keeps `keep` | |
| 320 | * showing: the unread conversations and the open one. | |
| 321 | */ | |
| 322 | function Section({ | |
| 323 | id, | |
| 324 | title, | |
| 325 | folded, | |
| 326 | onFold, | |
| 327 | action, | |
| 328 | keep, | |
| 329 | children, | |
| 330 | }: { | |
| 331 | id: string; | |
| 332 | title: string; | |
| 333 | folded: ReadonlySet<string>; | |
| 334 | onFold: (id: string) => void; | |
| 335 | action?: ReactNode; | |
| 336 | keep?: ReactNode[]; | |
| 337 | children: ReactNode; | |
| 338 | }) { | |
| 339 | const open = !folded.has(id); | |
| 340 | const list = `chat-section-${id}`; | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 341 | return ( |
| 342 | <section className="mb-3"> | |
| 343 | <div className="group/head flex h-7 items-center justify-between pr-0.5 pl-1"> | |
| 344 | <button | |
| 345 | type="button" | |
| Merge branch 'chat-sidebar' into fast-push | 346 | onClick={() => onFold(id)} |
| Chat and workspace agents: channels, DMs and named agents you talk to | 347 | aria-expanded={open} |
| Merge branch 'chat-sidebar' into fast-push | 348 | aria-controls={list} |
| The left-hand navigation takes less room and reads at a glance. The dock becomes a rail: a 56-pixel column of icons with the g1t mark on top, the current app marked by a filled square, hover hints naming each with its shortcut, the apps you pinned in a scrolling band, and Apps, People, Workspace and your account at the foot. Each mode's sidebar sits flush against it, 240 pixels wide, opening on Search or jump to and listing its groups under small labels in 36-pixel rows; Ctrl B hides it and the page grows into the room. Every mode's list is built on the shadcn Sidebar now in the kit, so Workspace, Code, People, Notifications, account settings, Chat, Artifacts and Agents all share one frame; the phone keeps its bottom bar and drawer. The workspaces guide describes the rail, the sidebar and the page. | 349 | className="flex min-w-0 grow items-center gap-1 rounded px-1 py-1 text-left text-[0.6875rem] font-medium tracking-wide text-faint uppercase transition-colors hover:text-muted focus-visible:ring-2 focus-visible:ring-accent focus-visible:outline-none max-md:text-[0.8125rem]" |
| Chat and workspace agents: channels, DMs and named agents you talk to | 350 | > |
| Merge branch 'chat-sidebar' into fast-push | 351 | <ChevronDown size={12} className={`shrink-0 transition-transform ${open ? "" : "-rotate-90"}`} /> |
| Chat and workspace agents: channels, DMs and named agents you talk to | 352 | {title} |
| 353 | </button> | |
| Merge branch 'chat-sidebar' into fast-push | 354 | <span className="opacity-70 transition-opacity group-hover/head:opacity-100 focus-within:opacity-100 [@media(hover:none)]:opacity-100">{action}</span> |
| Chat and workspace agents: channels, DMs and named agents you talk to | 355 | </div> |
| Merge branch 'chat-sidebar' into fast-push | 356 | {(open || (keep && keep.length > 0)) && ( |
| 357 | <ul id={list} className="space-y-px"> | |
| 358 | {open ? children : keep} | |
| 359 | </ul> | |
| 360 | )} | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 361 | </section> |
| 362 | ); | |
| 363 | } | |
| 364 | ||
| Merge branch 'chat-sidebar' into fast-push | 365 | /** A quieter row at the end of a section: somewhere to go rather than a conversation. */ |
| 366 | function ExtraRow({ to, icon, count, countLabel, children }: { to: string; icon: ReactNode; count?: number; countLabel?: string; children: ReactNode }) { | |
| 367 | return ( | |
| 368 | <li> | |
| 369 | <NavLink | |
| 370 | to={to} | |
| 371 | end | |
| The left-hand navigation takes less room and reads at a glance. The dock becomes a rail: a 56-pixel column of icons with the g1t mark on top, the current app marked by a filled square, hover hints naming each with its shortcut, the apps you pinned in a scrolling band, and Apps, People, Workspace and your account at the foot. Each mode's sidebar sits flush against it, 240 pixels wide, opening on Search or jump to and listing its groups under small labels in 36-pixel rows; Ctrl B hides it and the page grows into the room. Every mode's list is built on the shadcn Sidebar now in the kit, so Workspace, Code, People, Notifications, account settings, Chat, Artifacts and Agents all share one frame; the phone keeps its bottom bar and drawer. The workspaces guide describes the rail, the sidebar and the page. | 372 | className={({ isActive }) => `${ROW} ${isActive ? "bg-sidebar-accent text-fg" : "text-faint hover:bg-sidebar-accent/60 hover:text-muted"}`} |
| Merge branch 'chat-sidebar' into fast-push | 373 | > |
| 374 | <span className="flex w-4.5 shrink-0 justify-center">{icon}</span> | |
| 375 | <span className="min-w-0 grow truncate">{children}</span> | |
| 376 | {count != null && ( | |
| 377 | <span className="shrink-0 text-xs tabular-nums"> | |
| 378 | {count} | |
| 379 | {countLabel && <span className="sr-only"> {countLabel}</span>} | |
| 380 | </span> | |
| 381 | )} | |
| 382 | </NavLink> | |
| 383 | </li> | |
| 384 | ); | |
| 385 | } | |
| 386 | ||
| Chat and workspace agents: channels, DMs and named agents you talk to | 387 | function Empty({ children }: { children: ReactNode }) { |
| 388 | return <li className="px-2 py-1 text-xs text-faint">{children}</li>; | |
| 389 | } | |
| 390 | ||
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 391 | /** Pin or unpin, on hover or focus; the same as the context menu's first item. */ |
| 392 | function PinButton({ pinned, onPin, label }: { pinned: boolean; onPin: () => void; label: string }) { | |
| 393 | return ( | |
| 394 | <Hint label={pinned ? "Unpin" : "Pin to the top"}> | |
| 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. | 395 | <Button |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 396 | type="button" |
| 397 | aria-label={`${pinned ? "Unpin" : "Pin"} ${label}`} | |
| 398 | onClick={(event) => { | |
| 399 | event.preventDefault(); | |
| 400 | event.stopPropagation(); | |
| 401 | onPin(); | |
| 402 | }} | |
| 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. | 403 | variant="secondary" size="icon" |
| 404 | className="absolute top-1/2 right-1 hidden size-6 -translate-y-1/2 rounded text-faint group-hover/row:flex group-focus-within/row:flex hover:text-fg [@media(hover:none)]:hidden" | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 405 | > |
| 406 | {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. | 407 | </Button> |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 408 | </Hint> |
| 409 | ); | |
| 410 | } | |
| 411 | ||
| 412 | /** Right-click on a row: pin it, or open it. */ | |
| 413 | function RowMenu({ pinned, onPin, to, children }: { pinned: boolean; onPin: () => void; to?: string; children: ReactNode }) { | |
| 414 | const navigate = useNavigate(); | |
| 415 | const item = | |
| 416 | "flex cursor-default items-center gap-2 rounded-md px-2 py-1.5 text-[0.8125rem] text-fg/90 outline-none select-none data-highlighted:bg-line data-highlighted:text-fg [&_svg]:size-4 [&_svg]:text-muted"; | |
| 417 | return ( | |
| 418 | <ContextMenu.Root> | |
| 419 | <ContextMenu.Trigger asChild>{children}</ContextMenu.Trigger> | |
| 420 | <ContextMenu.Portal> | |
| 421 | <ContextMenu.Content className="z-50 min-w-44 rounded-lg border border-line-strong bg-raised p-1 shadow-xl shadow-black/40 data-[state=open]:animate-pop-in"> | |
| 422 | <ContextMenu.Item className={item} onSelect={onPin}> | |
| 423 | {pinned ? <PinOff /> : <Pin />} | |
| 424 | {pinned ? "Unpin" : "Pin to the top"} | |
| 425 | </ContextMenu.Item> | |
| 426 | {to && ( | |
| 427 | <ContextMenu.Item className={item} onSelect={() => navigate(to)}> | |
| 428 | <Hash /> | |
| 429 | Open | |
| 430 | </ContextMenu.Item> | |
| 431 | )} | |
| 432 | </ContextMenu.Content> | |
| 433 | </ContextMenu.Portal> | |
| 434 | </ContextMenu.Root> | |
| 435 | ); | |
| 436 | } | |
| 437 | ||
| 438 | /** | |
| 439 | * Reads a conversation ahead, on hover, focus or the start of a touch, so | |
| 440 | * opening it draws at once (its route's `clientLoader` keeps it). | |
| 441 | */ | |
| 442 | function usePrefetchConversation(to: string) { | |
| 443 | const fetcher = useFetcher(); | |
| 444 | const load = () => { | |
| 445 | if (fetcher.state === "idle" && fetcher.data === undefined && !conversationCache.has(to)) fetcher.load(to); | |
| 446 | }; | |
| 447 | return { onPointerEnter: load, onFocus: load, onTouchStart: load }; | |
| 448 | } | |
| 449 | ||
| 450 | /** One channel or direct message: its mark, its name, its unread count, and a pin. */ | |
| 451 | function ConversationRow({ | |
| 452 | entry, | |
| 453 | slug, | |
| 454 | pathname, | |
| 455 | agents, | |
| 456 | onPin, | |
| 457 | }: { | |
| 458 | entry: ChatSidebarEntry; | |
| 459 | slug: string; | |
| 460 | pathname: string; | |
| 461 | agents: WorkspaceAgent[]; | |
| 462 | onPin: () => void; | |
| 463 | }) { | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 464 | const to = channelPath(slug, entry.channel); |
| 465 | const current = pathname === to || pathname.startsWith(`${to}/`); | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 466 | const prefetch = usePrefetchConversation(to); |
| Chat and workspace agents: channels, DMs and named agents you talk to | 467 | const unread = !entry.muted && (entry.unread > 0 || entry.mentions > 0); |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 468 | const agentId = agentDmOf(entry); |
| 469 | const agent = agentId ? agents.find((a) => a.id === agentId) : null; | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 470 | const other = entry.others[0]; |
| Merge branch 'chat-sidebar' into fast-push | 471 | const g1t = agent ? isOrchestrator(agent) : false; |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 472 | // A person and an agent together: both faces, the agent's marked, and the word. |
| 473 | const mixed = mixedDm(entry); | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 474 | const icon = agent ? ( |
| Merge branch 'chat-sidebar' into fast-push | 475 | <AgentFace agent={agent} g1t={g1t} /> |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 476 | ) : mixed ? ( |
| 477 | <PairAvatar person={entry.others.find((m) => m.kind === "user")!} agent={agentsAmong(entry.others)[0]!} size={20} ring="var(--chat-sidebar-bg)" /> | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 478 | ) : entry.channel.kind === "dm" && other ? ( |
| One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers | 479 | // One person: their dot, cut out of the sidebar behind it. |
| 480 | <MemberAvatar member={other} size={18} presence={entry.others.length === 1} ring="var(--chat-sidebar-bg)" /> | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 481 | ) : entry.channel.private ? ( |
| 482 | <Lock size={14} /> | |
| 483 | ) : ( | |
| 484 | <Hash size={15} /> | |
| 485 | ); | |
| 486 | return ( | |
| 487 | <li className="group/row relative"> | |
| 488 | <RowMenu pinned={entry.starred} onPin={onPin} to={to}> | |
| 489 | <NavLink | |
| 490 | to={to} | |
| 491 | {...prefetch} | |
| 492 | aria-current={current ? "page" : undefined} | |
| 493 | className={`${ROW} ${ | |
| The left-hand navigation takes less room and reads at a glance. The dock becomes a rail: a 56-pixel column of icons with the g1t mark on top, the current app marked by a filled square, hover hints naming each with its shortcut, the apps you pinned in a scrolling band, and Apps, People, Workspace and your account at the foot. Each mode's sidebar sits flush against it, 240 pixels wide, opening on Search or jump to and listing its groups under small labels in 36-pixel rows; Ctrl B hides it and the page grows into the room. Every mode's list is built on the shadcn Sidebar now in the kit, so Workspace, Code, People, Notifications, account settings, Chat, Artifacts and Agents all share one frame; the phone keeps its bottom bar and drawer. The workspaces guide describes the rail, the sidebar and the page. | 494 | current ? "bg-sidebar-accent text-fg" : unread ? "text-fg hover:bg-sidebar-accent/60" : entry.muted ? "text-faint hover:bg-sidebar-accent/60 hover:text-muted" : "text-muted hover:bg-sidebar-accent/60 hover:text-fg" |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 495 | }`} |
| 496 | > | |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 497 | <span className={`flex shrink-0 justify-center ${mixed ? "w-7" : "w-4.5"} ${current || unread ? "text-muted" : "text-faint"}`}>{icon}</span> |
| One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers | 498 | <span className={`min-w-0 grow truncate ${unread ? "font-semibold" : current ? "font-medium" : ""}`}> |
| Merge branch 'chat-sidebar' into fast-push | 499 | {agent ? agent.display_name : entry.title} |
| 500 | {agent && <span className="ml-1.5 text-[0.6875rem] font-normal text-faint">{g1t ? "orchestrator" : agent.title}</span>} | |
| One kind of access token; presence and status; usernames keep their case; the tour is a miniature of the real app; icons for password managers | 501 | {entry.channel.kind === "dm" && other?.kind === "user" && entry.others.length === 1 && ( |
| 502 | <PersonStatusEmoji person={{ id: other.id, username: other.name }} size={13} className="ml-1.5 align-[-2px]" inert /> | |
| 503 | )} | |
| 504 | </span> | |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 505 | {mixed && ( |
| 506 | <Badge tone="accent" className="shrink-0 px-1.5 font-normal"> | |
| 507 | with an agent | |
| 508 | </Badge> | |
| 509 | )} | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 510 | {entry.muted && <BellOff size={12} className="shrink-0 text-faint" aria-label="Muted" />} |
| 511 | <Count entry={entry} /> | |
| 512 | </NavLink> | |
| 513 | </RowMenu> | |
| 514 | <PinButton pinned={entry.starred} onPin={onPin} label={entry.title} /> | |
| 515 | </li> | |
| 516 | ); | |
| 517 | } | |
| 518 | ||
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 519 | /** |
| 520 | * An agent's face in a row, the agent marker ringed in the sidebar's own | |
| 521 | * colour; any but g1t carries its status in the other corner. | |
| 522 | */ | |
| Merge branch 'chat-sidebar' into fast-push | 523 | function AgentFace({ agent, g1t }: { agent: WorkspaceAgent; g1t: boolean }) { |
| 524 | return ( | |
| 525 | <span className="relative inline-flex shrink-0"> | |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 526 | <AgentAvatar agent={{ ...agent, builtin: g1t }} size={18} ring="var(--chat-sidebar-bg)" /> |
| Merge branch 'chat-sidebar' into fast-push | 527 | {!g1t && agent.status !== "idle" && ( |
| Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent. | 528 | <span className="absolute -top-1 -right-1 flex rounded-full p-[2px]" style={{ background: "var(--chat-sidebar-bg)" }}> |
| Merge branch 'chat-sidebar' into fast-push | 529 | <StatusDot status={agent.status} className="size-[7px] shadow-none" /> |
| 530 | </span> | |
| 531 | )} | |
| 532 | </span> | |
| 533 | ); | |
| 534 | } | |
| 535 | ||
| 536 | /** An agent you have not talked to yet: its face, name and title. Opening it starts your conversation. */ | |
| 537 | function AgentRow({ agent, busy, onOpen, onPin }: { agent: WorkspaceAgent; busy: boolean; onOpen: () => void; onPin: () => void }) { | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 538 | const g1t = isOrchestrator(agent); |
| Chat and workspace agents: channels, DMs and named agents you talk to | 539 | return ( |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 540 | <li className="group/row relative"> |
| Merge branch 'chat-sidebar' into fast-push | 541 | <RowMenu pinned={false} onPin={onPin}> |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 542 | <button |
| 543 | type="button" | |
| 544 | onClick={onOpen} | |
| 545 | aria-busy={busy || undefined} | |
| Merge branch 'chat-sidebar' into fast-push | 546 | aria-label={`Message ${agent.display_name}, ${g1t ? "orchestrator" : agent.title}${g1t ? "" : `, ${statusLabel(agent.status).toLowerCase()}`}`} |
| The left-hand navigation takes less room and reads at a glance. The dock becomes a rail: a 56-pixel column of icons with the g1t mark on top, the current app marked by a filled square, hover hints naming each with its shortcut, the apps you pinned in a scrolling band, and Apps, People, Workspace and your account at the foot. Each mode's sidebar sits flush against it, 240 pixels wide, opening on Search or jump to and listing its groups under small labels in 36-pixel rows; Ctrl B hides it and the page grows into the room. Every mode's list is built on the shadcn Sidebar now in the kit, so Workspace, Code, People, Notifications, account settings, Chat, Artifacts and Agents all share one frame; the phone keeps its bottom bar and drawer. The workspaces guide describes the rail, the sidebar and the page. | 547 | className={`${ROW} w-full text-left text-muted hover:bg-sidebar-accent/60 hover:text-fg ${busy ? "opacity-60" : ""}`} |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 548 | > |
| Merge branch 'chat-sidebar' into fast-push | 549 | <span className="flex w-4.5 shrink-0 justify-center"> |
| 550 | <AgentFace agent={agent} g1t={g1t} /> | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 551 | </span> |
| Merge branch 'chat-sidebar' into fast-push | 552 | <span className="min-w-0 grow truncate"> |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 553 | {agent.display_name} |
| Merge branch 'chat-sidebar' into fast-push | 554 | <span className="ml-1.5 text-[0.6875rem] text-faint">{g1t ? "orchestrator" : agent.title}</span> |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 555 | </span> |
| 556 | </button> | |
| 557 | </RowMenu> | |
| Merge branch 'chat-sidebar' into fast-push | 558 | <PinButton pinned={false} onPin={onPin} label={agent.display_name} /> |
| Chat and workspace agents: channels, DMs and named agents you talk to | 559 | </li> |
| 560 | ); | |
| 561 | } | |
| 562 | ||
| 563 | function SidebarSkeleton() { | |
| 564 | return ( | |
| 565 | <div className="space-y-2 px-2 pt-1" aria-busy="true"> | |
| 566 | <Skeleton className="h-3 w-16" /> | |
| 567 | {[70, 52, 84, 60].map((w) => ( | |
| 568 | <div key={w} className="flex h-6 items-center gap-2"> | |
| 569 | <Skeleton className="size-4 rounded" /> | |
| 570 | <Skeleton className="h-3" style={{ width: `${w}%` }} /> | |
| 571 | </div> | |
| 572 | ))} | |
| 573 | <Skeleton className="mt-4 h-3 w-24" /> | |
| 574 | {[64, 48, 72].map((w) => ( | |
| 575 | <div key={w} className="flex h-6 items-center gap-2"> | |
| 576 | <Skeleton className="size-4 rounded-full" /> | |
| 577 | <Skeleton className="h-3" style={{ width: `${w}%` }} /> | |
| 578 | </div> | |
| 579 | ))} | |
| 580 | </div> | |
| 581 | ); | |
| 582 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.