Skip to content
114 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Artifacts is a mode in the rail, the shell and the phone's tab bar in place of Docs: its home lists everything you can open by day or as cards under All, Yours and Shared with you, with search, filters and tiles to start a doc (slides, designs and dashboards say they are coming soon), and its sidebar holds favorites, spaces, Private, Shared, projects' docs and the trash.1/**
2 * Each kind of artifact as the site shows it: its icon and colour, whether
3 * it can be made yet, and its page body. One line per kind, so each kind's
The docs folder is gone, and what it held lives where people read it: how a self-hosted g1t runs and how to deploy g1t to Cloudflare are pages on docs.g1t.sh under Run g1t yourself, and speed, rate limits and operating g1t.sh are sections of CONTRIBUTING.md; code that cited a file in docs/ now points to the page or section that covers it, or says what it means itself, and applied migrations and the runner images are left as they were.4 * phase adds its own (slides, design and dashboards are "Coming soon"
5 * until theirs ships). Bodies are lazy, so
Artifacts is a mode in the rail, the shell and the phone's tab bar in place of Docs: its home lists everything you can open by day or as cards under All, Yours and Shared with you, with search, filters and tiles to start a doc (slides, designs and dashboards say they are coming soon), and its sidebar holds favorites, spaces, Private, Shared, projects' docs and the trash.6 * Home and the sidebar carry no editor code.
7 */
8import { FOLIO_KIND_LABELS, type DocRole, type Folio, type FolioKind, type FolioPage, type FolioPreview } from "@g1t/contracts";
9import { FileText, LayoutDashboard, type LucideIcon, PenTool, Presentation } from "lucide-react";
10import { type ComponentType, type LazyExoticComponent, lazy } from "react";
11
12import type { LiveStatus } from "./provider";
13import type { Presence } from "./shell";
14
15/**
16 * What a kind's page body is given: the folio's page and the folio as it
17 * is now, the viewer's role, and the header's comment toggle; it tells the
18 * header who is here, how the connection is, and what changed live.
19 */
20export type FolioBodyProps = {
21 slug: string;
22 page: FolioPage;
23 folio: Folio;
24 role: DocRole;
25 showComments: boolean;
26 onPresence: (people: Presence[]) => void;
27 onStatus: (status: LiveStatus) => void;
28 /** A rename or another change to the folio, from the room. */
29 onFolio: (folio: Folio) => void;
30 /** The viewer's role changed; null: their access ended. */
31 onRole: (role: DocRole | null) => void;
32 onError: (message: string) => void;
33};
34
35export type FolioKindUi = {
36 label: string;
37 icon: LucideIcon;
38 /** Tailwind classes for the kind's icon on its soft square. */
39 tone: string;
40 /** False: its tile says "Coming soon" and nothing makes one yet. */
41 ready: boolean;
42 beta?: boolean;
43 /** Can be edited on a phone (docs can; a design canvas can't). */
44 mobileEditable: boolean;
45 Body: LazyExoticComponent<ComponentType<FolioBodyProps>> | null;
46};
47
48export const FOLIO_KIND_UI: Record<FolioKind, FolioKindUi> = {
49 doc: { label: FOLIO_KIND_LABELS.doc, icon: FileText, tone: "text-info bg-info/12", ready: true, mobileEditable: true, Body: lazy(() => import("./doc/body")) },
50 slides: { label: FOLIO_KIND_LABELS.slides, icon: Presentation, tone: "text-warn bg-warn/12", ready: false, mobileEditable: false, Body: null },
51 design: { label: FOLIO_KIND_LABELS.design, icon: PenTool, tone: "text-merged bg-merged/12", ready: false, mobileEditable: false, Body: null },
52 dashboard: { label: FOLIO_KIND_LABELS.dashboard, icon: LayoutDashboard, tone: "text-success bg-success/12", ready: false, beta: true, mobileEditable: false, Body: null },
53};
54
55/** A kind's icon in its colour on a soft square, as list rows and tiles show it. */
56export function KindIcon({ kind, size = 16, box = 28 }: { kind: FolioKind; size?: number; box?: number }) {
57 const ui = FOLIO_KIND_UI[kind];
58 const Icon = ui.icon;
59 return (
60 <span className={`flex shrink-0 items-center justify-center rounded-md ${ui.tone}`} style={{ width: box, height: box }} aria-hidden="true">
61 <Icon size={size} />
62 </span>
63 );
64}
65
66/** A folio's own icon: its emoji, or its kind's icon in its colour (no square). */
67export function FolioGlyph({ folio, size = 15 }: { folio: { kind: FolioKind; icon: string | null }; size?: number }) {
68 if (folio.icon) {
69 return (
70 <span className="leading-none" style={{ fontSize: size - 1 }} aria-hidden="true">
71 {folio.icon}
72 </span>
73 );
74 }
75 const ui = FOLIO_KIND_UI[folio.kind];
76 const Icon = ui.icon;
77 return <Icon size={size} className={ui.tone.split(" ")[0]} aria-hidden="true" />;
78}
79
80/**
81 * A card's picture, drawn from `preview` (written when the folio is
82 * saved; never data values): a doc's first lines, a deck's first slide, a
83 * design's frame, a dashboard's tile boxes.
84 */
85export function FolioThumbnail({ folio }: { folio: Pick<Folio, "kind" | "preview" | "excerpt" | "title"> }) {
86 const preview: FolioPreview | null = folio.preview;
87 if (preview?.kind === "doc" || (!preview && folio.kind === "doc")) {
88 const lines = preview?.kind === "doc" ? preview.lines : folio.excerpt ? [folio.excerpt] : [];
89 return (
90 <div className="h-full overflow-hidden bg-bg px-4 pt-4 text-[0.6875rem] leading-relaxed text-muted">
91 <p className="mb-1.5 line-clamp-1 text-xs font-semibold text-fg-soft">{folio.title || "Untitled"}</p>
92 {lines.length ? (
93 lines.slice(0, 6).map((line, i) => (
94 <p key={i} className="line-clamp-2">
95 {line}
96 </p>
97 ))
98 ) : (
99 <div className="space-y-1.5 pt-1" aria-hidden="true">
100 <div className="h-1.5 w-11/12 rounded bg-line" />
101 <div className="h-1.5 w-4/5 rounded bg-line" />
102 <div className="h-1.5 w-2/3 rounded bg-line" />
103 </div>
104 )}
105 </div>
106 );
107 }
108 // The other kinds draw their own once they ship; until then, the kind's mark.
109 return (
110 <div className="flex h-full items-center justify-center bg-bg">
111 <KindIcon kind={folio.kind} size={22} box={44} />
112 </div>
113 );
114}