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.

Commit

Routines has its place in the app, every control off: a mode of its own in the rail after Artifacts (for every member, not only those with Code), in the phone's More sheet and the command palette, at /<workspace>/-/routines, full width like Home and Apps. A routine is standing work for @g1t, and not only about code: say what, say when (a schedule, once, when something happens, by hand), say where it lands (your DM, a channel, an artifact, a pull request), and whose it is (yours, run as you; or the workspace's, kept by a team). The page draws all of it and none of it can be used: the composer is a disabled fieldset, Yours shows an empty list and the workspace's and Runs tabs are disabled, and the starting points (Keep up, Team, Code, Watch) cannot be picked. A notice says Routines are not on yet. Nothing is read or saved; the loader only checks membership. Checked: apps/web npm run typecheck, node --test app/lib/workspace-nav.test.ts and app/lib/apps.test.ts (19; new: /-/routines is the routines mode), and the page rendered alone with mock data in headless Chrome at 1280 and 390 wide. The rail, the More sheet and the palette were not looked at in a browser.

G1t-Session:
ses_01m50mr529evxafazhzv6vasds
syntaqxcommitted Parent6df87f2Browse files
9 files+377−80/9 viewed
+2−1
1−import { Bell, Building2, Code2, House, LayoutGrid, Lock, MessagesSquare, Pin, Plug, Search, Shapes, Users } from "lucide-react";
1+import { Bell, Building2, Code2, House, LayoutGrid, Lock, MessagesSquare, Pin, Plug, Repeat, Search, Shapes, Users } from "lucide-react";
22 import { type ReactNode, useEffect, useMemo, useState } from "react";
33 import { Link, useFetcher, useFetchers } from "react-router";
44
1717 notifications: <Bell size={size} />,
1818 code: <Code2 size={size} />,
1919 artifacts: <Shapes size={size} />,
20+ routines: <Repeat size={size} />,
2021 people: <Users size={size} />,
2122 workspace: <Building2 size={size} />,
2223 };
+1−0
305305 <MoreTile key={app.key} to={app.to} icon={app.icon(26)} label={app.name} />
306306 ))}
307307 <MoreTile to={appOf("artifacts").path(slug)} icon={appIcon("artifacts", 20)} label="Artifacts" />
308+ <MoreTile to={appOf("routines").path(slug)} icon={appIcon("routines", 20)} label="Routines" />
308309 <MoreTile to={appOf("people").path(slug)} icon={appIcon("people", 20)} label="People" />
309310 <MoreTile to={appOf("workspace").path(slug)} icon={appIcon("workspace", 20)} label="Workspace" />
310311 {onExplore ? (
+1−0
240240 },
241241 ...(code ? [{ key: "code" as const, label: "Code" }] : []),
242242 { key: "artifacts", label: "Artifacts" },
243+ { key: "routines", label: "Routines" },
243244 ];
244245 const shown = pins.map((key) => pinnedApp(key, slug)).filter((app) => app != null);
245246 // A pinned app is current on its own page and the pages under it.
+6−4
1−import { Activity, BarChart3, BrickWall, Building2, ChartLine, Flag, MessagesSquare, Bell, BookMarked, BookOpen, Bookmark, Blocks, Box, Check, ChevronDown, ChevronLeft, ChevronRight, CircleDot, GripVertical, CircleUserRound, Code2, Compass, CreditCard, Fingerprint, Gauge, GitBranch, GitPullRequest, Globe, History, Keyboard, KeyRound, Layers, LayoutDashboard, LayoutGrid, LifeBuoy, Lock, LogOut, Mail, MonitorCog, MonitorDown, Package, PlayCircle, Plug, Plus, Rocket, Search, ServerCog, Settings, Shapes, Shield, ShieldCheck, Scale, Smile, Sparkles, Star, House, Ticket, TrendingUp, UserPlus, UserRoundKey, Users, UsersRound, Webhook, X } from "lucide-react";
1+import { Activity, BarChart3, BrickWall, Building2, ChartLine, Flag, MessagesSquare, Bell, BookMarked, BookOpen, Bookmark, Blocks, Box, Check, ChevronDown, ChevronLeft, ChevronRight, CircleDot, GripVertical, CircleUserRound, Code2, Compass, CreditCard, Fingerprint, Gauge, GitBranch, GitPullRequest, Globe, History, Keyboard, KeyRound, Layers, LayoutDashboard, LayoutGrid, LifeBuoy, Lock, LogOut, Mail, MonitorCog, MonitorDown, Package, PlayCircle, Plug, Plus, Repeat, Rocket, Search, ServerCog, Settings, Shapes, Shield, ShieldCheck, Scale, Smile, Sparkles, Star, House, Ticket, TrendingUp, UserPlus, UserRoundKey, Users, UsersRound, Webhook, X } from "lucide-react";
22 import { Fragment, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
33 import { Link, useFetcher, useLocation, useNavigation, useRouteLoaderData, useSubmit } from "react-router";
44
15821582 work: "Work",
15831583 chat: "Chat",
15841584 artifacts: "Artifacts",
1585+ routines: "Routines",
15851586 home: "Home",
15861587 apps: "Apps",
15871588 "code-access": "Code access",
17771778 { label: "Chat", to: `/${ws.slug}/-/chat`, icon: <MessagesSquare size={15} /> },
17781779 ...(hasCodeAccess(ws) ? [{ label: "Code overview", to: `/${ws.slug}/-/overview`, icon: <Code2 size={15} /> }] : []),
17791780 { label: "Artifacts", to: `/${ws.slug}/-/artifacts`, icon: <Shapes size={15} /> },
1781+ { label: "Routines", hint: "Standing work for @g1t (soon)", to: `/${ws.slug}/-/routines`, icon: <Repeat size={15} /> },
17801782 { label: "Apps", hint: "Everything installed that you can use", to: `/${ws.slug}/-/apps`, icon: <LayoutGrid size={15} /> },
17811783 { label: "Workspace", hint: "Billing, policies, settings", to: `/${ws.slug}/-/workspace`, icon: <Building2 size={15} /> },
17821784 ]
19011903 }
19021904
19031905 /**
1904− * Which sidebar sits beside the page: each mode's own, or none. Home and
1905− * Apps are pages at full width, and so are g1t's own pages, such as its
1906+ * Which sidebar sits beside the page: each mode's own, or none. Home,
1907+ * Apps and Routines are pages at full width, and so are g1t's own pages, such as its
19061908 * policies, which are no workspace's. g1t beyond your workspaces (Explore,
19071909 * a profile, another workspace's projects) has Explore's. Without a
19081910 * workspace, only your own settings and Notifications have one.
19091911 */
19101912 function sidebarFor(mode: ModeKey, workspace: boolean): Panel | null {
19111913 if (!workspace) return mode === "account" || mode === "notifications" ? mode : null;
1912− if (mode === "home" || mode === "apps" || mode === "site") return null;
1914+ if (mode === "home" || mode === "apps" || mode === "routines" || mode === "site") return null;
19131915 return mode;
19141916 }
19151917
+4−3
22 * The rail's built-in apps, the apps a workspace added, and the ones each
33 * person pins to their rail.
44 *
5− * Built-in apps (Home, Chat, Notifications, Code, Artifacts, People and
6− * Workspace) are always in the rail; their own pages (Projects, Usage,
5+ * Built-in apps (Home, Chat, Notifications, Code, Artifacts, Routines,
6+ * People and Workspace) are always in the rail; their own pages (Projects, Usage,
77 * Teams and the rest) are in their sidebars. Apps are the integrations
88 * the workspace connected, each opening where it is looked after. Each
99 * person pins the ones they want.
2626 import { CONNECTORS, type Connector, connectorPath } from "@g1t/contracts/connectors";
2727
2828 /** The apps that are always in the rail. */
29−export type BuiltinApp = "home" | "chat" | "notifications" | "code" | "artifacts" | "people" | "workspace";
29+export type BuiltinApp = "home" | "chat" | "notifications" | "code" | "artifacts" | "routines" | "people" | "workspace";
3030
3131 export type BuiltinInfo = {
3232 key: BuiltinApp;
4646 { key: "notifications", name: "Notifications", path: () => "/notifications" },
4747 { key: "code", name: "Code", path: under("overview"), code: true },
4848 { key: "artifacts", name: "Artifacts", path: under("artifacts") },
49+ { key: "routines", name: "Routines", path: under("routines") },
4950 { key: "people", name: "People", path: under("people") },
5051 { key: "workspace", name: "Workspace", path: under("workspace") },
5152 ];
+1−0
9191 assert.equal(modeOf("/acme/-/memory", "acme"), "code");
9292 assert.equal(modeOf("/acme/-/artifacts", "acme"), "artifacts");
9393 assert.equal(modeOf("/acme/-/artifacts/spaces/general", "acme"), "artifacts");
94+ assert.equal(modeOf("/acme/-/routines", "acme"), "routines");
9495 // Docs mode is gone: its old addresses are nobody's mode, so they fall to Code's not-found page.
9596 assert.equal(modeOf("/acme/-/docs", "acme"), "code");
9697 assert.equal(modeOf("/acme/-/overview", "acme"), "code");
+4−0
232232 | "notifications"
233233 | "code"
234234 | "artifacts"
235+ | "routines"
235236 | "people"
236237 | "workspace"
237238 | "apps"
244245 home: "home",
245246 chat: "chat",
246247 artifacts: "artifacts",
248+ routines: "routines",
247249 apps: "apps",
248250 people: "people",
249251 members: "workspace",
307309 return `/${slug}/-/chat`;
308310 case "artifacts":
309311 return `/${slug}/-/artifacts`;
312+ case "routines":
313+ return `/${slug}/-/routines`;
310314 case "code":
311315 return `/${slug}/-/overview`;
312316 case "people":
+3−0
217217 route("-/home/seen", "routes/workspace/home-seen.ts"),
218218 route("-/today", "routes/workspace/moved.ts", { id: "routes/workspace/moved-today" }),
219219 route("-/apps", "routes/workspace/apps.tsx"),
220+ // Routines: standing work for @g1t, on a schedule or when something
221+ // happens. Not on yet: the page shows what it will be, every control off.
222+ route("-/routines", "routes/workspace/routines.tsx"),
220223 // What Code's pages say to a member without Code.
221224 route("-/code-access", "routes/workspace/code-access.tsx"),
222225 route("-/security", "routes/workspace/security.tsx"),
+355−0
1+/**
2+ * Routines: standing work for @g1t. Someone says what to do, when, and
3+ * where the result lands, and it runs by itself after that, whether or not
4+ * anyone is at a keyboard. A routine is not only about code: a morning
5+ * brief, a digest posted to a channel, a watch on production or a topic,
6+ * a reminder, as much as triaging issues or fixing CI on main.
7+ *
8+ * Not on yet. The page is here so Routines has its place in the rail and
9+ * its shape is settled: every control is drawn and none can be used (the
10+ * composer is a disabled <fieldset>, the lists are empty, the starting
11+ * points cannot be picked). Nothing is read or saved.
12+ */
13+import {
14+ AtSign,
15+ Bell,
16+ CalendarClock,
17+ FileText,
18+ GitPullRequest,
19+ Hash,
20+ type LucideIcon,
21+ MousePointerClick,
22+ Plus,
23+ Repeat,
24+ Tag,
25+ Users,
26+ Zap,
27+} from "lucide-react";
28+import { data } from "react-router";
29+
30+import type { Route } from "./+types/routines";
31+import { EmptyState, SoonPill } from "../../components/ui";
32+import { Alert } from "../../components/ui/alert";
33+import { Badge } from "../../components/ui/badge";
34+import { Button } from "../../components/ui/button";
35+import { Card } from "../../components/ui/card";
36+import { Field, FieldDescription, FieldLabel } from "../../components/ui/field";
37+import { Input } from "../../components/ui/input";
38+import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../components/ui/tabs";
39+import { Textarea } from "../../components/ui/textarea";
40+import { ToggleGroup, ToggleGroupItem } from "../../components/ui/toggle-group";
41+import { page } from "../../lib/meta";
42+import { getViewer } from "../../lib/session.server";
43+
44+export function meta({ params, ...args }: Route.MetaArgs) {
45+ return page(args, { title: `Routines · ${params.owner} · g1t` });
46+}
47+
48+export async function loader({ params, context }: Route.LoaderArgs) {
49+ const viewer = getViewer(context);
50+ const slug = params.owner.toLowerCase();
51+ const membership = viewer?.workspaces?.find((m) => m.slug === slug);
52+ if (!viewer || !membership) throw data(null, { status: 404 });
53+ return { slug, name: membership.name?.trim() || slug };
54+}
55+
56+/** A starting point: what it asks, when it runs, and where what it does lands. */
57+type Starter = { title: string; prompt: string; when: { icon: LucideIcon; text: string }; lands: { icon: LucideIcon; text: string } };
58+
59+const SCHEDULE = CalendarClock;
60+const EVENT = Zap;
61+
62+/** Starting points, by what they are for: code is one kind among several. */
63+const STARTERS: { title: string; about: string; items: Starter[] }[] = [
64+ {
65+ title: "Keep up",
66+ about: "Yours alone: what needs you, gathered for you.",
67+ items: [
68+ {
69+ title: "Morning brief",
70+ prompt: "Tell me what shipped overnight, what is waiting on me, and what I said I would do today.",
71+ when: { icon: SCHEDULE, text: "Weekdays at 8:30" },
72+ lands: { icon: AtSign, text: "Your DM with @g1t" },
73+ },
74+ {
75+ title: "Follow up for me",
76+ prompt: "When a pull request I am reviewing has waited two days, nudge me. After four, tell its author I am behind.",
77+ when: { icon: EVENT, text: "When a review waits" },
78+ lands: { icon: Bell, text: "Your notifications" },
79+ },
80+ {
81+ title: "Remind me, with a draft",
82+ prompt: "Remind me to write the week's notes, with a first draft from what I did this week.",
83+ when: { icon: SCHEDULE, text: "Once, Friday at 16:00" },
84+ lands: { icon: AtSign, text: "Your DM with @g1t" },
85+ },
86+ ],
87+ },
88+ {
89+ title: "Team",
90+ about: "The workspace's: kept by a team, so they outlive anyone leaving.",
91+ items: [
92+ {
93+ title: "What changed",
94+ prompt: "Post what shipped yesterday, what is in review, and what is stuck, with links.",
95+ when: { icon: SCHEDULE, text: "Weekdays at 9:00" },
96+ lands: { icon: Hash, text: "#general" },
97+ },
98+ {
99+ title: "The week's decisions",
100+ prompt: "Collect the decisions made this week in sessions, pull requests and chat into one page, newest first.",
101+ when: { icon: SCHEDULE, text: "Fridays at 15:00" },
102+ lands: { icon: FileText, text: "An artifact" },
103+ },
104+ {
105+ title: "Thread digest",
106+ prompt: "When a thread passes 20 messages, sum up where it landed and what is still open.",
107+ when: { icon: EVENT, text: "When a thread grows" },
108+ lands: { icon: Hash, text: "A reply in the thread" },
109+ },
110+ ],
111+ },
112+ {
113+ title: "Code",
114+ about: "On a repository: what they change goes through review and checks like anyone's.",
115+ items: [
116+ {
117+ title: "Triage new issues",
118+ prompt: "Label each new issue, find what it duplicates, and ask the reporter for what is missing.",
119+ when: { icon: EVENT, text: "When an issue opens" },
120+ lands: { icon: Tag, text: "Labels and a comment" },
121+ },
122+ {
123+ title: "Fix CI on main",
124+ prompt: "Find why the run on main failed and open a pull request that fixes it.",
125+ when: { icon: EVENT, text: "When CI fails on main" },
126+ lands: { icon: GitPullRequest, text: "A draft pull request" },
127+ },
128+ {
129+ title: "Dependency pass",
130+ prompt: "Update what is out of date, read the changelogs, and fix what the updates break.",
131+ when: { icon: SCHEDULE, text: "Mondays at 6:00" },
132+ lands: { icon: GitPullRequest, text: "A draft pull request" },
133+ },
134+ {
135+ title: "Docs in step",
136+ prompt: "When a pull request merges, check the docs about what it changed still say what is true.",
137+ when: { icon: EVENT, text: "When a pull request merges" },
138+ lands: { icon: GitPullRequest, text: "A draft pull request" },
139+ },
140+ ],
141+ },
142+ {
143+ title: "Watch",
144+ about: "Keeping an eye on something, and saying only when it matters.",
145+ items: [
146+ {
147+ title: "Errors in production",
148+ prompt: "When errors jump, find the requests behind them and the change that started it.",
149+ when: { icon: EVENT, text: "When errors spike" },
150+ lands: { icon: Hash, text: "#incidents" },
151+ },
152+ {
153+ title: "Spend",
154+ prompt: "When spend passes 80% of the month's budget, say what is driving it and what could stop.",
155+ when: { icon: EVENT, text: "At 80% of budget" },
156+ lands: { icon: Users, text: "Workspace owners" },
157+ },
158+ {
159+ title: "A topic",
160+ prompt: "Read what is new on a topic through our connectors and keep a running brief, changes at the top.",
161+ when: { icon: SCHEDULE, text: "Daily at 7:00" },
162+ lands: { icon: FileText, text: "An artifact" },
163+ },
164+ ],
165+ },
166+];
167+
168+/** How a routine works, in three parts. */
169+const PARTS = [
170+ {
171+ icon: FileText,
172+ title: "Say what",
173+ body: "In plain words, as you would ask a person. About code, or about anything @g1t can reach: chat, artifacts, issues, production, and what your connectors let it read.",
174+ },
175+ {
176+ icon: Repeat,
177+ title: "Say when",
178+ body: "On a schedule or once; when something happens (an issue opens, CI fails, a thread grows, errors spike); or by hand and through the API.",
179+ },
180+ {
181+ icon: MousePointerClick,
182+ title: "Say where it lands",
183+ body: "A message, an artifact, a comment, an issue, a draft pull request. What it writes goes through the same review and checks as anyone's.",
184+ },
185+];
186+
187+export default function Routines({ loaderData }: Route.ComponentProps) {
188+ const { name } = loaderData;
189+ return (
190+ <main className="mx-auto max-w-5xl px-4 py-8 sm:px-8 sm:py-10">
191+ <div className="flex flex-wrap items-end justify-between gap-4">
192+ <div>
193+ <div className="flex items-center gap-2">
194+ <h1 className="text-2xl font-semibold tracking-tight">Routines</h1>
195+ <SoonPill />
196+ </div>
197+ <p className="mt-1 max-w-xl text-sm text-muted">
198+ Standing work for @g1t. Say what to do, when, and where it lands, and it gets done whether or not anyone is at a keyboard.
199+ </p>
200+ </div>
201+ <Button disabled>
202+ <Plus size={15} />
203+ New routine
204+ </Button>
205+ </div>
206+
207+ <Alert tone="info" className="mt-6" id="routines-soon">
208+ Routines are not on yet. This is what they will be: nothing here can be turned on, and nothing you type is kept.
209+ </Alert>
210+
211+ <Card asChild className="mt-6 p-4 sm:p-5">
212+ <section aria-labelledby="compose">
213+ <h2 id="compose" className="text-sm font-semibold">
214+ New routine
215+ </h2>
216+ <fieldset disabled aria-describedby="routines-soon" className="mt-4 flex min-w-0 flex-col gap-5">
217+ <Field>
218+ <FieldLabel htmlFor="routine-prompt">What should it do?</FieldLabel>
219+ <Textarea
220+ id="routine-prompt"
221+ placeholder="Every weekday at 9, post what shipped yesterday and what is waiting on review to #general."
222+ />
223+ <FieldDescription>Each run starts fresh, so say everything it needs to know.</FieldDescription>
224+ </Field>
225+ <div className="grid gap-5 md:grid-cols-2">
226+ <Field>
227+ <FieldLabel>When</FieldLabel>
228+ <ToggleGroup className="self-start" type="single" variant="outline" size="sm" defaultValue="schedule" disabled aria-label="When it runs">
229+ <ToggleGroupItem value="schedule">On a schedule</ToggleGroupItem>
230+ <ToggleGroupItem value="once">Once</ToggleGroupItem>
231+ <ToggleGroupItem value="event">When something happens</ToggleGroupItem>
232+ <ToggleGroupItem value="manual">By hand</ToggleGroupItem>
233+ </ToggleGroup>
234+ <Input aria-label="Schedule" placeholder="Every weekday at 9:00" />
235+ </Field>
236+ <Field>
237+ <FieldLabel>Where it lands</FieldLabel>
238+ <ToggleGroup className="self-start" type="single" variant="outline" size="sm" defaultValue="dm" disabled aria-label="Where it lands">
239+ <ToggleGroupItem value="dm">Your DM</ToggleGroupItem>
240+ <ToggleGroupItem value="channel">A channel</ToggleGroupItem>
241+ <ToggleGroupItem value="artifact">An artifact</ToggleGroupItem>
242+ <ToggleGroupItem value="pull">A pull request</ToggleGroupItem>
243+ </ToggleGroup>
244+ <FieldDescription>It writes nowhere else: anything bigger waits for a person to say yes.</FieldDescription>
245+ </Field>
246+ </div>
247+ <Field>
248+ <FieldLabel>Whose it is</FieldLabel>
249+ <ToggleGroup className="self-start" type="single" variant="outline" size="sm" defaultValue="mine" disabled aria-label="Whose it is">
250+ <ToggleGroupItem value="mine">Yours</ToggleGroupItem>
251+ <ToggleGroupItem value="workspace">{name}'s</ToggleGroupItem>
252+ </ToggleGroup>
253+ <FieldDescription>
254+ Yours runs as you, with what you can reach. {name}'s is kept by a team and seen by everyone in it.
255+ </FieldDescription>
256+ </Field>
257+ <div className="flex flex-wrap items-center justify-end gap-2 border-t border-line pt-4">
258+ <Button variant="outline">Try it once</Button>
259+ <Button>Create routine</Button>
260+ </div>
261+ </fieldset>
262+ </section>
263+ </Card>
264+
265+ <section aria-labelledby="listed" className="mt-10">
266+ <h2 id="listed" className="sr-only">
267+ Routines and their runs
268+ </h2>
269+ <Tabs defaultValue="yours">
270+ <TabsList aria-label="Which routines">
271+ <TabsTrigger value="yours">Yours</TabsTrigger>
272+ <TabsTrigger value="workspace" disabled>
273+ {name}'s
274+ </TabsTrigger>
275+ <TabsTrigger value="runs" disabled>
276+ Runs
277+ </TabsTrigger>
278+ </TabsList>
279+ <TabsContent value="yours">
280+ <EmptyState title="No routines yet">
281+ Each one will show here with when it last ran, what it did, and what it cost. Every run is a session you can open and read.
282+ </EmptyState>
283+ </TabsContent>
284+ </Tabs>
285+ </section>
286+
287+ <section aria-labelledby="starters" className="mt-10">
288+ <h2 id="starters" className="text-sm font-semibold">
289+ Start from one of these
290+ </h2>
291+ <p className="mt-1 text-sm text-muted">Code is one kind of routine among several.</p>
292+ <div className="mt-4 flex flex-col gap-8">
293+ {STARTERS.map((group) => (
294+ <div key={group.title}>
295+ <div className="flex flex-wrap items-baseline gap-x-3 gap-y-0.5">
296+ <h3 className="text-xs font-semibold tracking-wide text-muted uppercase">{group.title}</h3>
297+ <p className="text-xs text-faint">{group.about}</p>
298+ </div>
299+ <div className="mt-3 grid grid-cols-[repeat(auto-fill,minmax(15rem,1fr))] gap-2">
300+ {group.items.map((starter) => (
301+ <StarterCard key={starter.title} starter={starter} />
302+ ))}
303+ </div>
304+ </div>
305+ ))}
306+ </div>
307+ </section>
308+
309+ <section aria-labelledby="how" className="mt-12">
310+ <h2 id="how" className="text-sm font-semibold">
311+ How a routine works
312+ </h2>
313+ <div className="mt-3 grid gap-2 md:grid-cols-3">
314+ {PARTS.map(({ icon: Icon, title, body }) => (
315+ <Card key={title} tone="plain" radius="lg" className="p-4">
316+ <Icon size={16} className="text-accent" aria-hidden="true" />
317+ <h3 className="mt-2 text-sm font-medium">{title}</h3>
318+ <p className="mt-1 text-sm text-muted">{body}</p>
319+ </Card>
320+ ))}
321+ </div>
322+ <p className="mt-4 max-w-3xl text-sm text-muted">
323+ Every run is a session: titled, recorded and on {name}'s bill, so you can see what each routine did, when, and why. It thinks with
324+ your workspace's AI, or runs as your own agent on a runner, in a sandbox. Each has a budget per run and per day, and can be paused
325+ at any time.
326+ </p>
327+ </section>
328+ </main>
329+ );
330+}
331+
332+/** One starting point: its name, what it asks, when, and where it lands. Picking it comes with Routines. */
333+function StarterCard({ starter }: { starter: Starter }) {
334+ const When = starter.when.icon;
335+ const Lands = starter.lands.icon;
336+ return (
337+ <Card radius="lg" className="flex flex-col p-4" aria-disabled="true">
338+ <h4 className="text-sm font-medium">{starter.title}</h4>
339+ <p className="mt-1 grow text-sm text-muted">{starter.prompt}</p>
340+ <div className="mt-3 flex flex-wrap gap-1.5">
341+ <Badge>
342+ <When size={11} aria-hidden="true" />
343+ {starter.when.text}
344+ </Badge>
345+ <Badge>
346+ <Lands size={11} aria-hidden="true" />
347+ {starter.lands.text}
348+ </Badge>
349+ </div>
350+ <Button variant="outline" size="xs" className="mt-3 self-start" disabled>
351+ Use this
352+ </Button>
353+ </Card>
354+ );
355+}

Comments

Sign in to comment.