Skip to content
110 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.

The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.1/**
2 * The doc kind: BlockNote on `XmlFragment("document-store")`, comments in
3 * the `threads` map, Markdown as its text rendition. The same document,
4 * code and behaviour as Docs' pages (src/blocks.ts, src/markdown.ts,
5 * src/edits.ts, src/citations.ts, src/threads.ts), which PageRoom still
6 * uses until Phase 7 retires it; those modules move here then. Pure.
7 */
8import type { DocEditTarget } from "@g1t/contracts";
9import * as Y from "yjs";
10
11import { seed as seedBlocks } from "../../blocks.ts";
12import { chunkMarkdown } from "../../chunks.ts";
13import { bodyCitations } from "../../citations.ts";
14import { applyEdit, findTarget, rangeIds, rangeMarkdown, restoreFrom } from "../../edits.ts";
15import { citationNodes, documentMarkdown, mentionedIds, outline, searchText } from "../../markdown.ts";
16import type { FolioOrigin, KindModel, Rendition } from "../types.ts";
17
18/** Every folio id a text links to, for backlinks (as `linkedFolioIds` in @g1t/contracts; kept here so this module stays pure for Node's tests). */
19export function linkedFolioIds(text: string): string[] {
20 return [...new Set(text.match(/fol_[0-9a-hjkmnp-tv-z]{26}/g) ?? [])];
21}
22
23/** Where BlockNote keeps the document. */
24export const DOC_FRAGMENT = "document-store";
25/** The most a doc's Markdown may be. */
26export const DOC_MAX_TEXT = 512 * 1024;
27/** Lines a card shows. */
28const PREVIEW_LINES = 4;
29
30export function docFragment(doc: Y.Doc): Y.XmlFragment {
31 return doc.getXmlFragment(DOC_FRAGMENT);
32}
33
34/** The first lines of a doc, as plain text, for its card. */
35export function previewLines(markdown: string, max = PREVIEW_LINES): string[] {
36 return searchText(markdown)
37 .split("\n")
38 .map((l) => l.replace(/\s+/g, " ").trim())
39 .filter(Boolean)
40 .slice(0, max)
41 .map((l) => (l.length > 120 ? `${l.slice(0, 119)}…` : l));
42}
43
44/** A target's current Markdown and blocks, or null when it is gone. */
45export function docTarget(doc: Y.Doc, target: DocEditTarget): { markdown: string; block_ids: string[] } | null {
46 const fragment = docFragment(doc);
47 const range = findTarget(fragment, target);
48 if (!range) return null;
49 return { markdown: rangeMarkdown(fragment, range), block_ids: rangeIds(fragment, range) };
50}
51
52/** Where each target is now, for marking open suggestions in the editor (null: gone). */
53export function docTargets(doc: Y.Doc, targets: DocEditTarget[]): (string[] | null)[] {
54 const fragment = docFragment(doc);
55 return targets.map((t) => {
56 const range = findTarget(fragment, t);
57 return range ? rangeIds(fragment, range) : null;
58 });
59}
60
61function describe(target: DocEditTarget): string {
62 switch (target.kind) {
63 case "append":
64 return "Added to the end";
65 case "document":
66 return "Rewrote the doc";
67 case "section":
68 return `Changed the section "${target.heading}"`;
69 case "blocks":
70 return "Changed some blocks";
71 }
72}
73
74export const doc: KindModel = {
75 kind: "doc",
76 isEmpty: (d) => docFragment(d).length === 0,
77 seed(d, init) {
78 seedBlocks(d, docFragment(d), String(init.text ?? "").slice(0, DOC_MAX_TEXT));
79 },
80 render(d): Rendition {
81 const fragment = docFragment(d);
82 const text = documentMarkdown(fragment);
83 return {
84 text,
85 mentions: mentionedIds(fragment).users,
86 links: linkedFolioIds(text),
87 citations: bodyCitations(citationNodes(fragment), text),
88 preview: { kind: "doc", lines: previewLines(text) },
89 };
90 },
91 chunks: (text, title) => chunkMarkdown(text, title),
92 read(d) {
93 const fragment = docFragment(d);
94 return { content: documentMarkdown(fragment), blocks: outline(fragment) };
95 },
96 applyAgentEdit(d, edit, origin: FolioOrigin) {
97 if (edit.kind !== "doc") return { applied: false, summary: "That edit is for another kind of artifact." };
98 const applied = applyEdit(d, docFragment(d), edit.target, String(edit.markdown ?? "").slice(0, DOC_MAX_TEXT), origin);
99 return { applied, summary: applied ? describe(edit.target) : "That part of the doc isn't there." };
100 },
101 restore(d, old, origin) {
102 restoreFrom(d, docFragment(d), docFragment(old), origin);
103 },
104 restoreText(d, text, origin) {
105 applyEdit(d, docFragment(d), { kind: "document" }, text, origin);
106 },
107 validate(d) {
108 return documentMarkdown(docFragment(d)).length > DOC_MAX_TEXT * 2 ? "This doc is too long." : null;
109 },
110};