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 | */ | |
| 8 | import type { DocEditTarget } from "@g1t/contracts"; | |
| 9 | import * as Y from "yjs"; | |
| 10 | ||
| 11 | import { seed as seedBlocks } from "../../blocks.ts"; | |
| 12 | import { chunkMarkdown } from "../../chunks.ts"; | |
| 13 | import { bodyCitations } from "../../citations.ts"; | |
| 14 | import { applyEdit, findTarget, rangeIds, rangeMarkdown, restoreFrom } from "../../edits.ts"; | |
| 15 | import { citationNodes, documentMarkdown, mentionedIds, outline, searchText } from "../../markdown.ts"; | |
| 16 | import 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). */ | |
| 19 | export 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. */ | |
| 24 | export const DOC_FRAGMENT = "document-store"; | |
| 25 | /** The most a doc's Markdown may be. */ | |
| 26 | export const DOC_MAX_TEXT = 512 * 1024; | |
| 27 | /** Lines a card shows. */ | |
| 28 | const PREVIEW_LINES = 4; | |
| 29 | ||
| 30 | export 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. */ | |
| 35 | export 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. */ | |
| 45 | export 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). */ | |
| 53 | export 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 | ||
| 61 | function 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 | ||
| 74 | export 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 | }; |