[{"data":1,"prerenderedAt":4339},["ShallowReactive",2],{"navigation_docs":3,"-guides-workspace":412,"-guides-workspace-surround":4334},[4,38,84,326,386],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":37},"Getting started","i-lucide-rocket","\u002Fgetting-started","1.getting-started",[10,15,20,24,28,32],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fgetting-started\u002Fintroduction","1.getting-started\u002F1.introduction","i-lucide-book-open",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":6},"Quick start: Nuxt","\u002Fgetting-started\u002Fquick-start-nuxt","1.getting-started\u002F3.quick-start-nuxt",{"title":25,"path":26,"stem":27,"icon":6},"Quick start: plain Vue","\u002Fgetting-started\u002Fquick-start-vue","1.getting-started\u002F4.quick-start-vue",{"title":29,"path":30,"stem":31,"icon":6},"Quick start: React","\u002Fgetting-started\u002Fquick-start-react","1.getting-started\u002F5.quick-start-react",{"title":33,"path":34,"stem":35,"icon":36},"Glossary","\u002Fgetting-started\u002Fglossary","1.getting-started\u002F6.glossary","i-lucide-book-a",false,{"title":39,"icon":40,"path":41,"stem":42,"children":43,"page":37},"Tutorial","i-lucide-graduation-cap","\u002Ftutorial","2.tutorial",[44,49,54,59,64,69,74,79],{"title":45,"path":46,"stem":47,"icon":48},"Tutorial: overview","\u002Ftutorial\u002Foverview","2.tutorial\u002F1.overview","i-lucide-map",{"title":50,"path":51,"stem":52,"icon":53},"Chapter 2: A simple editor","\u002Ftutorial\u002Fsimple-editor","2.tutorial\u002F2.simple-editor","i-lucide-pencil",{"title":55,"path":56,"stem":57,"icon":58},"Chapter 3: Compose features","\u002Ftutorial\u002Fcompose-features","2.tutorial\u002F3.compose-features","i-lucide-blocks",{"title":60,"path":61,"stem":62,"icon":63},"Chapter 4: Your own block, path A","\u002Ftutorial\u002Fown-block-path-a","2.tutorial\u002F4.own-block-path-a","i-lucide-square-plus",{"title":65,"path":66,"stem":67,"icon":68},"Chapter 5: Your own feature, path B","\u002Ftutorial\u002Fown-feature-path-b","2.tutorial\u002F5.own-feature-path-b","i-lucide-puzzle",{"title":70,"path":71,"stem":72,"icon":73},"Chapter 6: Backend and collaboration","\u002Ftutorial\u002Fbackend-and-collaboration","2.tutorial\u002F6.backend-and-collaboration","i-lucide-users",{"title":75,"path":76,"stem":77,"icon":78},"Chapter 7: AI","\u002Ftutorial\u002Fai","2.tutorial\u002F7.ai","i-lucide-sparkles",{"title":80,"path":81,"stem":82,"icon":83},"Chapter 8: The React surface","\u002Ftutorial\u002Freact-surface","2.tutorial\u002F8.react-surface","i-lucide-atom",{"title":85,"icon":14,"path":86,"stem":87,"children":88,"page":37},"Guides","\u002Fguides","3.guides",[89,93,98,103,108,112,116,121,126,130,134,139,144,149,154,159,164,169,174,179,184,189,194,199,204,209,213,218,223,228,233,237,242,247,251,256,261,266,271,276,281,286,291,295,302,307,311,316,321],{"title":90,"path":91,"stem":92,"icon":58},"Feature composition and entries","\u002Fguides\u002Fcomposition","3.guides\u002F1.composition",{"title":94,"path":95,"stem":96,"icon":97},"Media, mentions and transcript features","\u002Fguides\u002Fmedia-mentions-transcript","3.guides\u002F10.media-mentions-transcript","i-lucide-image",{"title":99,"path":100,"stem":101,"icon":102},"Authors and comments","\u002Fguides\u002Fauthors-and-comments","3.guides\u002F11.authors-and-comments","i-lucide-message-square-text",{"title":104,"path":105,"stem":106,"icon":107},"Themes (Obsidian)","\u002Fguides\u002Fthemes","3.guides\u002F12.themes","i-lucide-palette",{"title":109,"path":110,"stem":111,"icon":58},"Composites and the settings UI","\u002Fguides\u002Fcomposites","3.guides\u002F13.composites",{"title":113,"path":114,"stem":115,"icon":78},"AI","\u002Fguides\u002Fai","3.guides\u002F14.ai",{"title":117,"path":118,"stem":119,"icon":120},"Document tree","\u002Fguides\u002Fdocument-tree","3.guides\u002F15.document-tree","i-lucide-folder-tree",{"title":122,"path":123,"stem":124,"icon":125},"Markdown and code","\u002Fguides\u002Fmarkdown-and-code","3.guides\u002F16.markdown-and-code","i-lucide-file-code",{"title":127,"path":128,"stem":129,"icon":83},"Advanced editor in React","\u002Fguides\u002Fadvanced-react","3.guides\u002F17.advanced-react",{"title":131,"path":132,"stem":133,"icon":6},"Kitchen sink: learning path","\u002Fguides\u002Fadvanced-nuxt-example","3.guides\u002F18.advanced-nuxt-example",{"title":135,"path":136,"stem":137,"icon":138},"Production","\u002Fguides\u002Fproduction","3.guides\u002F19.production","i-lucide-server",{"title":140,"path":141,"stem":142,"icon":143},"Register the Nuxt module","\u002Fguides\u002Fnuxt-module","3.guides\u002F2.nuxt-module","i-lucide-package",{"title":145,"path":146,"stem":147,"icon":148},"Storage adapters, comment stores, Nitro host","\u002Fguides\u002Fstorage-adapters","3.guides\u002F20.storage-adapters","i-lucide-database",{"title":150,"path":151,"stem":152,"icon":153},"Migrating from #tiptap-editor","\u002Fguides\u002Fmigration","3.guides\u002F21.migration","i-lucide-arrow-right-left",{"title":155,"path":156,"stem":157,"icon":158},"Content tree mechanics","\u002Fguides\u002Fcontent-tree","3.guides\u002F22.content-tree","i-lucide-git-fork",{"title":160,"path":161,"stem":162,"icon":163},"Block capabilities, abilities and actions","\u002Fguides\u002Fblock-capabilities","3.guides\u002F23.block-capabilities","i-lucide-shield-check",{"title":165,"path":166,"stem":167,"icon":168},"Block types in code and JSON","\u002Fguides\u002Fblock-types","3.guides\u002F24.block-types","i-lucide-shapes",{"title":170,"path":171,"stem":172,"icon":173},"Derive methods and the Derivation index","\u002Fguides\u002Fderive-methods","3.guides\u002F25.derive-methods","i-lucide-git-branch-plus",{"title":175,"path":176,"stem":177,"icon":178},"Block properties and Property definitions","\u002Fguides\u002Fblock-properties","3.guides\u002F26.block-properties","i-lucide-sliders-horizontal",{"title":180,"path":181,"stem":182,"icon":183},"Image studio","\u002Fguides\u002Fimage-studio","3.guides\u002F27.image-studio","i-lucide-image-plus",{"title":185,"path":186,"stem":187,"icon":188},"Prüfung and Freigabestatus","\u002Fguides\u002Fpruefung","3.guides\u002F28.pruefung","i-lucide-stamp",{"title":190,"path":191,"stem":192,"icon":193},"Workspace: find, read, reference and embed Blocks across Documents","\u002Fguides\u002Fworkspace","3.guides\u002F29.workspace","i-lucide-network",{"title":195,"path":196,"stem":197,"icon":198},"EditorPorts: auth and storage","\u002Fguides\u002Feditor-ports","3.guides\u002F3.editor-ports","i-lucide-plug",{"title":200,"path":201,"stem":202,"icon":203},"Collections: an ordered list of Blocks","\u002Fguides\u002Fcollections","3.guides\u002F30.collections","i-lucide-list-checks",{"title":205,"path":206,"stem":207,"icon":208},"Data views: a query shown as table, list or board","\u002Fguides\u002Fdata-views","3.guides\u002F31.data-views","i-lucide-table-properties",{"title":210,"path":211,"stem":212,"icon":193},"Explore: backlinks, related Blocks and the trail","\u002Fguides\u002Fexplore","3.guides\u002F32.explore",{"title":214,"path":215,"stem":216,"icon":217},"Render: export a Block or a Document to a file","\u002Fguides\u002Frender","3.guides\u002F33.render","i-lucide-file-output",{"title":219,"path":220,"stem":221,"icon":222},"Importers: what a dropped file becomes","\u002Fguides\u002Fimporters","3.guides\u002F34.importers","i-lucide-file-input",{"title":224,"path":225,"stem":226,"icon":227},"Templates: start a Document from a Dokumentvorlage","\u002Fguides\u002Ftemplates","3.guides\u002F35.templates","i-lucide-file-stack",{"title":229,"path":230,"stem":231,"icon":232},"Citations","\u002Fguides\u002Fcitations","3.guides\u002F36.citations","i-lucide-quote",{"title":234,"path":235,"stem":236,"icon":163},"AI proposals and the export check","\u002Fguides\u002Fexport-check","3.guides\u002F37.export-check",{"title":238,"path":239,"stem":240,"icon":241},"Query: filter, sort, group and total Blocks","\u002Fguides\u002Fquery","3.guides\u002F38.query","i-lucide-list-filter",{"title":243,"path":244,"stem":245,"icon":246},"Edges: backlinks and authored connections","\u002Fguides\u002Fedges","3.guides\u002F39.edges","i-lucide-link-2",{"title":248,"path":249,"stem":250,"icon":163},"Server routes and authentication","\u002Fguides\u002Froutes-and-auth","3.guides\u002F4.routes-and-auth",{"title":252,"path":253,"stem":254,"icon":255},"Speech: text to audio, and subtitle files","\u002Fguides\u002Fspeech","3.guides\u002F41.speech","i-lucide-audio-lines",{"title":257,"path":258,"stem":259,"icon":260},"Graph: the Edges around a Block","\u002Fguides\u002Fgraph","3.guides\u002F42.graph","i-lucide-waypoints",{"title":262,"path":263,"stem":264,"icon":265},"PDF viewer: passages, anchors and quotes","\u002Fguides\u002Fpdf-viewer","3.guides\u002F43.pdf-viewer","i-lucide-file-search",{"title":267,"path":268,"stem":269,"icon":270},"Obsidian: import and export a vault","\u002Fguides\u002Fobsidian","3.guides\u002F44.obsidian","i-lucide-folder-input",{"title":272,"path":273,"stem":274,"icon":275},"Typst: render a Document as a PDF with citations","\u002Fguides\u002Ftypst","3.guides\u002F45.typst","i-lucide-file-text",{"title":277,"path":278,"stem":279,"icon":280},"Audio: read aloud, mixdown and ElevenLabs","\u002Fguides\u002Faudio","3.guides\u002F46.audio","i-lucide-audio-waveform",{"title":282,"path":283,"stem":284,"icon":285},"Slides: decks, slide layouts and exports","\u002Fguides\u002Fslides","3.guides\u002F47.slides","i-lucide-presentation",{"title":287,"path":288,"stem":289,"icon":290},"Knowledge graph: sources and concepts beside the Documents","\u002Fguides\u002Fknowledge-graph","3.guides\u002F49.knowledge-graph","i-lucide-share-2",{"title":292,"path":293,"stem":294,"icon":78},"AI configuration and secrets","\u002Fguides\u002Fai-configuration","3.guides\u002F5.ai-configuration",{"title":296,"path":297,"stem":298,"children":299,"icon":301},"Index on Postgres: typos, meaning and SQL pushdown","\u002Fguides\u002Findex-pg","3.guides\u002F50.index-pg",[300],{"title":296,"path":297,"stem":298,"icon":301},"i-lucide-database-zap",{"title":303,"path":304,"stem":305,"icon":306},"Charts: bar, line, pie and scatter over a query","\u002Fguides\u002Fcharts","3.guides\u002F51.charts","i-lucide-chart-column",{"title":308,"path":309,"stem":310,"icon":73},"Collaboration on the client","\u002Fguides\u002Fcollaboration","3.guides\u002F6.collaboration",{"title":312,"path":313,"stem":314,"icon":315},"Block UI (drag handle, hover actions)","\u002Fguides\u002Fblock-ui","3.guides\u002F7.block-ui","i-lucide-grip-vertical",{"title":317,"path":318,"stem":319,"icon":320},"Settings and app lifetime","\u002Fguides\u002Fsettings","3.guides\u002F8.settings","i-lucide-settings",{"title":322,"path":323,"stem":324,"icon":325},"Language","\u002Fguides\u002Flanguage","3.guides\u002F9.language","i-lucide-languages",{"title":327,"icon":14,"path":328,"stem":329,"children":330,"page":37},"Reference","\u002Freference","4.reference",[331,335,339,344,348,352,356,360,364,368,373,378,382],{"title":332,"path":333,"stem":334,"icon":143},"Public exports","\u002Freference\u002Fexports","4.reference\u002F1.exports",{"title":336,"path":337,"stem":338,"icon":173},"Derive method contract","\u002Freference\u002Fderive-methods","4.reference\u002F10.derive-methods",{"title":340,"path":341,"stem":342,"icon":343},"Changelog","\u002Freference\u002Fchangelog","4.reference\u002F11.changelog","i-lucide-history",{"title":345,"path":346,"stem":347,"icon":217},"Renderer port","\u002Freference\u002Frenderer-port","4.reference\u002F12.renderer-port",{"title":349,"path":350,"stem":351,"icon":255},"Speech port","\u002Freference\u002Fspeech-port","4.reference\u002F13.speech-port",{"title":353,"path":354,"stem":355,"icon":58},"Presets and features","\u002Freference\u002Fpresets-and-features","4.reference\u002F2.presets-and-features",{"title":357,"path":358,"stem":359,"icon":68},"Feature contract","\u002Freference\u002Ffeature-contract","4.reference\u002F3.feature-contract",{"title":361,"path":362,"stem":363,"icon":107},"CSS and UI-provider requirements","\u002Freference\u002Fcss-and-ui","4.reference\u002F4.css-and-ui",{"title":365,"path":366,"stem":367,"icon":148},"Vue-free advanced schema","\u002Freference\u002Fadvanced-schema","4.reference\u002F5.advanced-schema",{"title":369,"path":370,"stem":371,"icon":372},"Dependency rules","\u002Freference\u002Fdependency-rules","4.reference\u002F6.dependency-rules","i-lucide-git-branch",{"title":374,"path":375,"stem":376,"icon":377},"Commands","\u002Freference\u002Fcommands","4.reference\u002F7.commands","i-lucide-terminal",{"title":379,"path":380,"stem":381,"icon":163},"Block capability contract","\u002Freference\u002Fblock-capabilities","4.reference\u002F8.block-capabilities",{"title":383,"path":384,"stem":385,"icon":168},"Block type contract","\u002Freference\u002Fblock-types","4.reference\u002F9.block-types",{"title":387,"path":388,"stem":389,"children":390,"icon":393},"AI agents","\u002Fai-agents","5.ai-agents\u002F1.index",[391,394,398,402,407],{"title":392,"path":388,"stem":389,"icon":393},"Overview","i-lucide-bot",{"title":395,"path":396,"stem":397,"icon":198},"Integrate the editor","\u002Fai-agents\u002Fintegrate","5.ai-agents\u002F2.integrate",{"title":399,"path":400,"stem":401,"icon":68},"Add a block or feature","\u002Fai-agents\u002Fextend","5.ai-agents\u002F3.extend",{"title":403,"path":404,"stem":405,"icon":406},"Pitfalls and invariants","\u002Fai-agents\u002Finvariants","5.ai-agents\u002F4.invariants","i-lucide-shield-alert",{"title":408,"path":409,"stem":410,"icon":411},"Verify","\u002Fai-agents\u002Fverify","5.ai-agents\u002F5.verify","i-lucide-check-check",{"id":413,"title":190,"body":414,"description":4328,"extension":2538,"links":4329,"meta":4330,"navigation":4331,"path":191,"seo":4332,"stem":192,"__hash__":4333},"docs\u002F3.guides\u002F29.workspace.md",{"type":415,"value":416,"toc":4315},"minimark",[417,430,587,598,603,664,670,1039,1081,1108,1123,1639,1644,1697,1794,1816,2213,2217,2274,2277,2298,2381,2413,2503,2512,2516,2534,2556,2567,2580,2584,2607,2696,2754,3211,3214,3268,3274,3323,3332,3597,3600,3653,3657,3693,3737,3783,3815,3824,3828,3889,3898,3915,4233,4251,4281,4285,4311],[418,419,420,421,425,426,429],"p",{},"A ",[422,423,424],"strong",{},"Block address"," is the pair of a Document ID and a block ID. Block IDs are unique per Document only, so the block ID alone names a Block inside its own Document, and the address names it anywhere in the workspace. The workspace layer builds on that pair: a ",[422,427,428],{},"Block index"," holds one row per Block of every Document, and everything that crosses a Document boundary asks the index instead of opening the other Document.",[431,432,433,449],"table",{},[434,435,436],"thead",{},[437,438,439,443,446],"tr",{},[440,441,442],"th",{},"Piece",[440,444,445],{},"What it does",[440,447,448],{},"Port (capability)",[450,451,452,486,498,510,525,541,560,573],"tbody",{},[437,453,454,457,481],{},[455,456,428],"td",{},[455,458,459,460,464,465,464,468,464,471,464,474,477,478],{},"one row per Block of every Document; answers ",[461,462,463],"code",{},"get",", ",[461,466,467],{},"byDocument",[461,469,470],{},"documentIds",[461,472,473],{},"tags",[461,475,476],{},"search"," and ",[461,479,480],{},"edges",[455,482,483],{},[461,484,485],{},"index",[437,487,488,491,494],{},[455,489,490],{},"Block route",[455,492,493],{},"summary or content of one Block by address",[455,495,496],{},[461,497,485],{},[437,499,500,503,506],{},[455,501,502],{},"Workspace search",[455,504,505],{},"Blocks of the Documents the user may read whose title or text match",[455,507,508],{},[461,509,485],{},[437,511,512,515,521],{},[455,513,514],{},"Foreign references",[455,516,517,520],{},[461,518,519],{},"[@\u003Cdocument>#\u003Cblock>]"," chips and Derived blocks whose original is in another Document",[455,522,523],{},[461,524,485],{},[437,526,527,533,536],{},[455,528,529,530],{},"Document routes and ",[461,531,532],{},"DocumentMutator",[455,534,535],{},"list, create, move, remove and edit Documents from the server",[455,537,538],{},[461,539,540],{},"documents",[437,542,543,546,555],{},[455,544,545],{},"Tool providers",[455,547,548,549,477,552],{},"read-only tools of the document chat; ",[461,550,551],{},"find_blocks",[461,553,554],{},"read_block",[455,556,557,559],{},[461,558,485],{}," (for the two tools)",[437,561,562,565,568],{},[455,563,564],{},"Change set",[455,566,567],{},"edits the chat proposes for other Documents, decided per Document",[455,569,570],{},[461,571,572],{},"changeSets",[437,574,575,580,583],{},[455,576,577],{},[461,578,579],{},"blockEmbed",[455,581,582],{},"a Block of any Document shown read-only inside another",[455,584,585],{},[461,586,485],{},[418,588,589,590,593,594,597],{},"Each piece is offered only when its port is present: a missing port turns its routes into ",[461,591,592],{},"501",", its panel off and its tool answer into a sentence for the model. The split between what the library owns and what a host chooses is ADR-0003 (",[461,595,596],{},"docs\u002Fadr\u002F0003-one-host-now-apps-ejectable-later.md",").",[599,600,602],"h2",{"id":601},"the-block-index","The Block index",[418,604,605,608,609,464,612,615,616,619,620,622,623,626,627,464,630,633,634,637,638,464,641,464,644,464,646,464,649,652,653,477,656,659,660,663],{},[461,606,607],{},"BlockIndex"," (",[461,610,611],{},"EditorPorts.index",[461,613,614],{},"@skaile\u002Fforge-editor\u002Fserver",") is a ",[422,617,618],{},"projection"," of the Documents, not a second store. The ",[461,621,540],{}," port stays the truth, and the index can always be rebuilt from ",[461,624,625],{},"listContents",". A row (",[461,628,629],{},"BlockRow",[461,631,632],{},"@skaile\u002Fforge-editor\u002Fcore",") holds the Block address, the Block type (",[461,635,636],{},"type","; the node type for a Block that is not a Typed block), ",[461,639,640],{},"title",[461,642,643],{},"properties",[461,645,473],{},[461,647,648],{},"references",[461,650,651],{},"derivedFrom",", two content hashes (",[461,654,655],{},"hash",[461,657,658],{},"sourceHash",") and the plain ",[461,661,662],{},"text",". Every node that carries a block ID has a row; a node without an ID has none.",[418,665,666,669],{},[461,667,668],{},"projectDocument(content, documentId, options?)"," computes the rows of one Document. It is pure and framework-free, so the same content always gives the same rows:",[671,672,678],"pre",{"className":673,"code":674,"filename":675,"language":676,"meta":677,"style":677},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import type { JSONContent } from '@tiptap\u002Fcore'\nimport { type BlockRow, projectDocument } from '@skaile\u002Fforge-editor\u002Fcore'\n\n\u002F\u002F The Block index is a projection: the rows of a Document's content, one per node that carries a block ID.\n\u002F\u002F `projectDocument` is pure, so the same content always gives the same rows.\nexport function referencesOf(content: JSONContent, documentId: string): BlockRow['references'] {\n  return projectDocument(content, documentId).flatMap(row => row.references)\n}\n\n\u002F\u002F Every row holds its address, the Block type, the title, the plain text, the tags and the content hashes.\nexport function titlesOf(content: JSONContent, documentId: string): string[] {\n  return projectDocument(content, documentId)\n    .filter(row => row.title !== '')\n    .map(({ blockId, type, title }) => `${documentId}#${blockId} (${type}): ${title}`)\n}\n","docs\u002Fsnippets\u002Fworkspace-index-rows.ts","ts","",[461,679,680,716,744,751,758,764,822,866,872,877,883,918,935,964,1034],{"__ignoreMap":677},[681,682,685,689,692,696,700,703,706,709,713],"span",{"class":683,"line":684},"line",1,[681,686,688],{"class":687},"s7zQu","import",[681,690,691],{"class":687}," type",[681,693,695],{"class":694},"sMK4o"," {",[681,697,699],{"class":698},"sTEyZ"," JSONContent",[681,701,702],{"class":694}," }",[681,704,705],{"class":687}," from",[681,707,708],{"class":694}," '",[681,710,712],{"class":711},"sfazB","@tiptap\u002Fcore",[681,714,715],{"class":694},"'\n",[681,717,719,721,723,725,728,731,734,736,738,740,742],{"class":683,"line":718},2,[681,720,688],{"class":687},[681,722,695],{"class":694},[681,724,691],{"class":687},[681,726,727],{"class":698}," BlockRow",[681,729,730],{"class":694},",",[681,732,733],{"class":698}," projectDocument",[681,735,702],{"class":694},[681,737,705],{"class":687},[681,739,708],{"class":694},[681,741,632],{"class":711},[681,743,715],{"class":694},[681,745,747],{"class":683,"line":746},3,[681,748,750],{"emptyLinePlaceholder":749},true,"\n",[681,752,754],{"class":683,"line":753},4,[681,755,757],{"class":756},"sHwdD","\u002F\u002F The Block index is a projection: the rows of a Document's content, one per node that carries a block ID.\n",[681,759,761],{"class":683,"line":760},5,[681,762,763],{"class":756},"\u002F\u002F `projectDocument` is pure, so the same content always gives the same rows.\n",[681,765,767,770,774,778,781,785,788,791,793,796,798,801,804,806,809,812,814,816,819],{"class":683,"line":766},6,[681,768,769],{"class":687},"export",[681,771,773],{"class":772},"spNyl"," function",[681,775,777],{"class":776},"s2Zo4"," referencesOf",[681,779,780],{"class":694},"(",[681,782,784],{"class":783},"sHdIc","content",[681,786,787],{"class":694},":",[681,789,699],{"class":790},"sBMFI",[681,792,730],{"class":694},[681,794,795],{"class":783}," documentId",[681,797,787],{"class":694},[681,799,800],{"class":790}," string",[681,802,803],{"class":694},"):",[681,805,727],{"class":790},[681,807,808],{"class":698},"[",[681,810,811],{"class":694},"'",[681,813,648],{"class":711},[681,815,811],{"class":694},[681,817,818],{"class":698},"] ",[681,820,821],{"class":694},"{\n",[681,823,825,828,830,833,835,837,839,842,845,848,850,853,856,859,861,863],{"class":683,"line":824},7,[681,826,827],{"class":687},"  return",[681,829,733],{"class":776},[681,831,780],{"class":832},"swJcz",[681,834,784],{"class":698},[681,836,730],{"class":694},[681,838,795],{"class":698},[681,840,841],{"class":832},")",[681,843,844],{"class":694},".",[681,846,847],{"class":776},"flatMap",[681,849,780],{"class":832},[681,851,852],{"class":783},"row",[681,854,855],{"class":772}," =>",[681,857,858],{"class":698}," row",[681,860,844],{"class":694},[681,862,648],{"class":698},[681,864,865],{"class":832},")\n",[681,867,869],{"class":683,"line":868},8,[681,870,871],{"class":694},"}\n",[681,873,875],{"class":683,"line":874},9,[681,876,750],{"emptyLinePlaceholder":749},[681,878,880],{"class":683,"line":879},10,[681,881,882],{"class":756},"\u002F\u002F Every row holds its address, the Block type, the title, the plain text, the tags and the content hashes.\n",[681,884,886,888,890,893,895,897,899,901,903,905,907,909,911,913,916],{"class":683,"line":885},11,[681,887,769],{"class":687},[681,889,773],{"class":772},[681,891,892],{"class":776}," titlesOf",[681,894,780],{"class":694},[681,896,784],{"class":783},[681,898,787],{"class":694},[681,900,699],{"class":790},[681,902,730],{"class":694},[681,904,795],{"class":783},[681,906,787],{"class":694},[681,908,800],{"class":790},[681,910,803],{"class":694},[681,912,800],{"class":790},[681,914,915],{"class":698},"[] ",[681,917,821],{"class":694},[681,919,921,923,925,927,929,931,933],{"class":683,"line":920},12,[681,922,827],{"class":687},[681,924,733],{"class":776},[681,926,780],{"class":832},[681,928,784],{"class":698},[681,930,730],{"class":694},[681,932,795],{"class":698},[681,934,865],{"class":832},[681,936,938,941,944,946,948,950,952,954,956,959,962],{"class":683,"line":937},13,[681,939,940],{"class":694},"    .",[681,942,943],{"class":776},"filter",[681,945,780],{"class":832},[681,947,852],{"class":783},[681,949,855],{"class":772},[681,951,858],{"class":698},[681,953,844],{"class":694},[681,955,640],{"class":698},[681,957,958],{"class":694}," !==",[681,960,961],{"class":694}," ''",[681,963,865],{"class":832},[681,965,967,969,972,974,977,980,982,984,986,989,992,994,997,1000,1003,1006,1009,1012,1014,1016,1018,1020,1022,1025,1027,1029,1032],{"class":683,"line":966},14,[681,968,940],{"class":694},[681,970,971],{"class":776},"map",[681,973,780],{"class":832},[681,975,976],{"class":694},"({",[681,978,979],{"class":783}," blockId",[681,981,730],{"class":694},[681,983,691],{"class":783},[681,985,730],{"class":694},[681,987,988],{"class":783}," title",[681,990,991],{"class":694}," })",[681,993,855],{"class":772},[681,995,996],{"class":694}," `${",[681,998,999],{"class":698},"documentId",[681,1001,1002],{"class":694},"}",[681,1004,1005],{"class":711},"#",[681,1007,1008],{"class":694},"${",[681,1010,1011],{"class":698},"blockId",[681,1013,1002],{"class":694},[681,1015,608],{"class":711},[681,1017,1008],{"class":694},[681,1019,636],{"class":698},[681,1021,1002],{"class":694},[681,1023,1024],{"class":711},"): ",[681,1026,1008],{"class":694},[681,1028,640],{"class":698},[681,1030,1031],{"class":694},"}`",[681,1033,865],{"class":832},[681,1035,1037],{"class":683,"line":1036},15,[681,1038,871],{"class":694},[418,1040,1041,1042,1045,1046,1049,1050,1053,1054,1056,1057,1059,1060,464,1063,464,1066,1069,1070,1072,1073,1076,1077,1080],{},"The editor server keeps the index current. ",[461,1043,1044],{},"installEditorServer"," (and ",[461,1047,1048],{},"provideEditorServer"," for a Nuxt host) wraps the ports with ",[461,1051,1052],{},"withBlockIndex",": the index is built from ",[461,1055,625],{}," at start, with the live content of an open Document first, and every write through the ",[461,1058,540],{}," port (",[461,1061,1062],{},"storeContent",[461,1064,1065],{},"putDocument",[461,1067,1068],{},"deleteDocument",") re-projects the Document it touched. A failure of that maintenance is logged and never fails the write: the ",[461,1071,540],{}," port is the truth, and the next write of the Document re-projects it. A host route that stores content itself calls ",[461,1074,1075],{},"reindexDocument(id)"," of the ",[461,1078,1079],{},"EditorServer"," handle afterwards. Reads wait for the first build.",[418,1082,1083,1084,1087,1088,1090,1091,1094,1095,1098,1099,1101,1102,608,1104,1107],{},"The workspace tag list (",[461,1085,1086],{},"GET \u002Fapi\u002Feditor\u002Ftags",", the ",[461,1089,1005],{}," popup) is read from the Block index, so it reads no stored Document, with one exception: a Document that is currently open in the collaboration server is read from its live Y.Doc (",[461,1092,1093],{},"listWorkspaceTags"," in ",[461,1096,1097],{},"server\u002Fhandlers\u002Feditor.ts","). A tag added in one open Document then shows in another before the debounced store. The index adds to that, so a tag removed in an open Document stays listed until the store re-projects the Document. A host without the ",[461,1100,485],{}," port gets the tags from the stored content of ",[461,1103,540],{},[461,1105,1106],{},"listCurrentContents",") instead, again with the live Y.Doc for open Documents.",[418,1109,1110,1111,1114,1115,1118,1119,1122],{},"Add the port next to the ones a host already has. ",[461,1112,1113],{},"createWorkspaceTools()"," registers the chat's two read-only tools, and ",[461,1116,1117],{},"readableDocuments"," on ",[461,1120,1121],{},"identity"," is optional (see Workspace search):",[671,1124,1127],{"className":673,"code":1125,"filename":1126,"language":676,"meta":677,"style":677},"\u002F\u002F server\u002Fplugins\u002Feditor-adapter.ts\nimport type { JSONContent } from '@tiptap\u002Fcore'\nimport { createMemoryBlockIndex } from '@skaile\u002Fforge-editor\u002Fadapters\u002Fblock-index'\nimport { createWorkspaceTools } from '@skaile\u002Fforge-editor\u002Fserver'\nimport { provideEditorServer } from '@skaile\u002Fforge-editor\u002Fserver\u002Fnitro'\n\nexport default defineNitroPlugin(() => {\n  const contents = new Map\u003Cstring, JSONContent>()\n  provideEditorServer({\n    ports: {\n      identity: {\n        authenticate: async () => ({ id: 'demo', username: 'demo' }),\n        \u002F\u002F Optional: the Documents a user may read, so a search never has to ask `authorize` once per Document.\n        readableDocuments: async () => 'all',\n      },\n      documents: {\n        loadContent: async id => contents.get(id) ?? null,\n        storeContent: async (id, content) => { contents.set(id, content) },\n        listContents: async () => [...contents].map(([id, content]) => ({ id, content })),\n      },\n      \u002F\u002F The `index` capability: a projection of `documents`, rebuilt on start and kept current by the editor server.\n      index: createMemoryBlockIndex(),\n    },\n    \u002F\u002F The read-only tools of the document chat: `find_blocks` and `read_block`.\n    tools: createWorkspaceTools(),\n  })\n})\n\n\u002F\u002F Nuxt auto-imports `defineNitroPlugin` in `server\u002Fplugins`; this declaration lets the file typecheck on its own.\ndeclare function defineNitroPlugin(plugin: () => void): unknown\n","docs\u002Fsnippets\u002Fworkspace-ports.ts",[461,1128,1129,1134,1154,1174,1193,1213,1217,1237,1270,1279,1288,1297,1349,1354,1376,1381,1391,1425,1470,1533,1538,1544,1558,1564,1570,1584,1592,1599,1604,1610],{"__ignoreMap":677},[681,1130,1131],{"class":683,"line":684},[681,1132,1133],{"class":756},"\u002F\u002F server\u002Fplugins\u002Feditor-adapter.ts\n",[681,1135,1136,1138,1140,1142,1144,1146,1148,1150,1152],{"class":683,"line":718},[681,1137,688],{"class":687},[681,1139,691],{"class":687},[681,1141,695],{"class":694},[681,1143,699],{"class":698},[681,1145,702],{"class":694},[681,1147,705],{"class":687},[681,1149,708],{"class":694},[681,1151,712],{"class":711},[681,1153,715],{"class":694},[681,1155,1156,1158,1160,1163,1165,1167,1169,1172],{"class":683,"line":746},[681,1157,688],{"class":687},[681,1159,695],{"class":694},[681,1161,1162],{"class":698}," createMemoryBlockIndex",[681,1164,702],{"class":694},[681,1166,705],{"class":687},[681,1168,708],{"class":694},[681,1170,1171],{"class":711},"@skaile\u002Fforge-editor\u002Fadapters\u002Fblock-index",[681,1173,715],{"class":694},[681,1175,1176,1178,1180,1183,1185,1187,1189,1191],{"class":683,"line":753},[681,1177,688],{"class":687},[681,1179,695],{"class":694},[681,1181,1182],{"class":698}," createWorkspaceTools",[681,1184,702],{"class":694},[681,1186,705],{"class":687},[681,1188,708],{"class":694},[681,1190,614],{"class":711},[681,1192,715],{"class":694},[681,1194,1195,1197,1199,1202,1204,1206,1208,1211],{"class":683,"line":760},[681,1196,688],{"class":687},[681,1198,695],{"class":694},[681,1200,1201],{"class":698}," provideEditorServer",[681,1203,702],{"class":694},[681,1205,705],{"class":687},[681,1207,708],{"class":694},[681,1209,1210],{"class":711},"@skaile\u002Fforge-editor\u002Fserver\u002Fnitro",[681,1212,715],{"class":694},[681,1214,1215],{"class":683,"line":766},[681,1216,750],{"emptyLinePlaceholder":749},[681,1218,1219,1221,1224,1227,1229,1232,1234],{"class":683,"line":824},[681,1220,769],{"class":687},[681,1222,1223],{"class":687}," default",[681,1225,1226],{"class":776}," defineNitroPlugin",[681,1228,780],{"class":698},[681,1230,1231],{"class":694},"()",[681,1233,855],{"class":772},[681,1235,1236],{"class":694}," {\n",[681,1238,1239,1242,1245,1248,1251,1254,1257,1260,1262,1264,1267],{"class":683,"line":868},[681,1240,1241],{"class":772},"  const",[681,1243,1244],{"class":698}," contents",[681,1246,1247],{"class":694}," =",[681,1249,1250],{"class":694}," new",[681,1252,1253],{"class":776}," Map",[681,1255,1256],{"class":694},"\u003C",[681,1258,1259],{"class":790},"string",[681,1261,730],{"class":694},[681,1263,699],{"class":790},[681,1265,1266],{"class":694},">",[681,1268,1269],{"class":832},"()\n",[681,1271,1272,1275,1277],{"class":683,"line":874},[681,1273,1274],{"class":776},"  provideEditorServer",[681,1276,780],{"class":832},[681,1278,821],{"class":694},[681,1280,1281,1284,1286],{"class":683,"line":879},[681,1282,1283],{"class":832},"    ports",[681,1285,787],{"class":694},[681,1287,1236],{"class":694},[681,1289,1290,1293,1295],{"class":683,"line":885},[681,1291,1292],{"class":832},"      identity",[681,1294,787],{"class":694},[681,1296,1236],{"class":694},[681,1298,1299,1302,1304,1307,1310,1312,1314,1317,1320,1322,1324,1327,1329,1331,1334,1336,1338,1340,1342,1344,1346],{"class":683,"line":920},[681,1300,1301],{"class":776},"        authenticate",[681,1303,787],{"class":694},[681,1305,1306],{"class":772}," async",[681,1308,1309],{"class":694}," ()",[681,1311,855],{"class":772},[681,1313,608],{"class":832},[681,1315,1316],{"class":694},"{",[681,1318,1319],{"class":832}," id",[681,1321,787],{"class":694},[681,1323,708],{"class":694},[681,1325,1326],{"class":711},"demo",[681,1328,811],{"class":694},[681,1330,730],{"class":694},[681,1332,1333],{"class":832}," username",[681,1335,787],{"class":694},[681,1337,708],{"class":694},[681,1339,1326],{"class":711},[681,1341,811],{"class":694},[681,1343,702],{"class":694},[681,1345,841],{"class":832},[681,1347,1348],{"class":694},",\n",[681,1350,1351],{"class":683,"line":937},[681,1352,1353],{"class":756},"        \u002F\u002F Optional: the Documents a user may read, so a search never has to ask `authorize` once per Document.\n",[681,1355,1356,1359,1361,1363,1365,1367,1369,1372,1374],{"class":683,"line":966},[681,1357,1358],{"class":776},"        readableDocuments",[681,1360,787],{"class":694},[681,1362,1306],{"class":772},[681,1364,1309],{"class":694},[681,1366,855],{"class":772},[681,1368,708],{"class":694},[681,1370,1371],{"class":711},"all",[681,1373,811],{"class":694},[681,1375,1348],{"class":694},[681,1377,1378],{"class":683,"line":1036},[681,1379,1380],{"class":694},"      },\n",[681,1382,1384,1387,1389],{"class":683,"line":1383},16,[681,1385,1386],{"class":832},"      documents",[681,1388,787],{"class":694},[681,1390,1236],{"class":694},[681,1392,1394,1397,1399,1401,1403,1405,1407,1409,1411,1413,1416,1419,1422],{"class":683,"line":1393},17,[681,1395,1396],{"class":776},"        loadContent",[681,1398,787],{"class":694},[681,1400,1306],{"class":772},[681,1402,1319],{"class":783},[681,1404,855],{"class":772},[681,1406,1244],{"class":698},[681,1408,844],{"class":694},[681,1410,463],{"class":776},[681,1412,780],{"class":832},[681,1414,1415],{"class":698},"id",[681,1417,1418],{"class":832},") ",[681,1420,1421],{"class":694},"??",[681,1423,1424],{"class":694}," null,\n",[681,1426,1428,1431,1433,1435,1437,1439,1441,1444,1446,1448,1450,1452,1454,1457,1459,1461,1463,1465,1467],{"class":683,"line":1427},18,[681,1429,1430],{"class":776},"        storeContent",[681,1432,787],{"class":694},[681,1434,1306],{"class":772},[681,1436,608],{"class":694},[681,1438,1415],{"class":783},[681,1440,730],{"class":694},[681,1442,1443],{"class":783}," content",[681,1445,841],{"class":694},[681,1447,855],{"class":772},[681,1449,695],{"class":694},[681,1451,1244],{"class":698},[681,1453,844],{"class":694},[681,1455,1456],{"class":776},"set",[681,1458,780],{"class":832},[681,1460,1415],{"class":698},[681,1462,730],{"class":694},[681,1464,1443],{"class":698},[681,1466,1418],{"class":832},[681,1468,1469],{"class":694},"},\n",[681,1471,1473,1476,1478,1480,1482,1484,1487,1490,1493,1496,1498,1500,1502,1505,1507,1509,1511,1514,1516,1518,1520,1522,1524,1526,1528,1531],{"class":683,"line":1472},19,[681,1474,1475],{"class":776},"        listContents",[681,1477,787],{"class":694},[681,1479,1306],{"class":772},[681,1481,1309],{"class":694},[681,1483,855],{"class":772},[681,1485,1486],{"class":832}," [",[681,1488,1489],{"class":694},"...",[681,1491,1492],{"class":698},"contents",[681,1494,1495],{"class":832},"]",[681,1497,844],{"class":694},[681,1499,971],{"class":776},[681,1501,780],{"class":832},[681,1503,1504],{"class":694},"([",[681,1506,1415],{"class":783},[681,1508,730],{"class":694},[681,1510,1443],{"class":783},[681,1512,1513],{"class":694},"])",[681,1515,855],{"class":772},[681,1517,608],{"class":832},[681,1519,1316],{"class":694},[681,1521,1319],{"class":698},[681,1523,730],{"class":694},[681,1525,1443],{"class":698},[681,1527,702],{"class":694},[681,1529,1530],{"class":832},"))",[681,1532,1348],{"class":694},[681,1534,1536],{"class":683,"line":1535},20,[681,1537,1380],{"class":694},[681,1539,1541],{"class":683,"line":1540},21,[681,1542,1543],{"class":756},"      \u002F\u002F The `index` capability: a projection of `documents`, rebuilt on start and kept current by the editor server.\n",[681,1545,1547,1550,1552,1554,1556],{"class":683,"line":1546},22,[681,1548,1549],{"class":832},"      index",[681,1551,787],{"class":694},[681,1553,1162],{"class":776},[681,1555,1231],{"class":832},[681,1557,1348],{"class":694},[681,1559,1561],{"class":683,"line":1560},23,[681,1562,1563],{"class":694},"    },\n",[681,1565,1567],{"class":683,"line":1566},24,[681,1568,1569],{"class":756},"    \u002F\u002F The read-only tools of the document chat: `find_blocks` and `read_block`.\n",[681,1571,1573,1576,1578,1580,1582],{"class":683,"line":1572},25,[681,1574,1575],{"class":832},"    tools",[681,1577,787],{"class":694},[681,1579,1182],{"class":776},[681,1581,1231],{"class":832},[681,1583,1348],{"class":694},[681,1585,1587,1590],{"class":683,"line":1586},26,[681,1588,1589],{"class":694},"  }",[681,1591,865],{"class":832},[681,1593,1595,1597],{"class":683,"line":1594},27,[681,1596,1002],{"class":694},[681,1598,865],{"class":698},[681,1600,1602],{"class":683,"line":1601},28,[681,1603,750],{"emptyLinePlaceholder":749},[681,1605,1607],{"class":683,"line":1606},29,[681,1608,1609],{"class":756},"\u002F\u002F Nuxt auto-imports `defineNitroPlugin` in `server\u002Fplugins`; this declaration lets the file typecheck on its own.\n",[681,1611,1613,1616,1618,1620,1622,1625,1627,1629,1631,1634,1636],{"class":683,"line":1612},30,[681,1614,1615],{"class":772},"declare",[681,1617,773],{"class":772},[681,1619,1226],{"class":776},[681,1621,780],{"class":694},[681,1623,1624],{"class":776},"plugin",[681,1626,787],{"class":694},[681,1628,1309],{"class":694},[681,1630,855],{"class":772},[681,1632,1633],{"class":790}," void",[681,1635,803],{"class":694},[681,1637,1638],{"class":790}," unknown\n",[1640,1641,1643],"h3",{"id":1642},"adapters","Adapters",[431,1645,1646,1659],{},[434,1647,1648],{},[437,1649,1650,1653,1656],{},[440,1651,1652],{},"Entry",[440,1654,1655],{},"Factory",[440,1657,1658],{},"Use",[450,1660,1661,1675],{},[437,1662,1663,1667,1672],{},[455,1664,1665],{},[461,1666,1171],{},[455,1668,1669],{},[461,1670,1671],{},"createMemoryBlockIndex()",[455,1673,1674],{},"rows in memory by Document, then by block ID; gone with the process, so the server rebuilds them on start",[437,1676,1677,1682,1690],{},[455,1678,1679],{},[461,1680,1681],{},"@skaile\u002Fforge-editor\u002Fadapters\u002Fblock-index\u002Fdrizzle",[455,1683,1684,464,1687],{},[461,1685,1686],{},"createDrizzleBlockIndex({ db, tables: blockIndexTables() })",[461,1688,1689],{},"bootstrapBlockIndexTables(db)",[455,1691,1692,1693,1696],{},"the index in Postgres or PGlite through drizzle (",[461,1694,1695],{},"drizzle-orm",", optional peer)",[418,1698,1699,1700,608,1703,464,1706,464,1709,464,1712,464,1714,464,1716,464,1719,464,1722,1725,1726,1729,1730,608,1733,464,1736,464,1739,464,1741,464,1744,464,1747,464,1750,464,1753,1756,1757,1761,1762,1764,1765,1767,1768,1771,1772,1775,1776,1779,1780,1782,1783,1786,1787,1789,1790,1793],{},"The drizzle adapter keeps two tables. ",[461,1701,1702],{},"editor_block_index",[461,1704,1705],{},"document_id",[461,1707,1708],{},"block_id",[461,1710,1711],{},"seq",[461,1713,852],{},[461,1715,473],{},[461,1717,1718],{},"title_words",[461,1720,1721],{},"text_words",[461,1723,1724],{},"text_length"," and two generated ",[461,1727,1728],{},"tsvector"," columns with GIN indexes) holds the rows; ",[461,1731,1732],{},"editor_block_edges",[461,1734,1735],{},"from_document_id",[461,1737,1738],{},"from_block_id",[461,1740,1711],{},[461,1742,1743],{},"to_document_id",[461,1745,1746],{},"to_block_id",[461,1748,1749],{},"tag",[461,1751,1752],{},"kind",[461,1754,1755],{},"origin",", with an index on the target) holds their ",[1758,1759,1760],"a",{"href":244},"Edges",", written in the same transaction as the rows. The whole ",[461,1763,629],{}," sits in the ",[461,1766,852],{}," JSON column, so a later release can add row fields without a schema change. ",[461,1769,1770],{},"blockIndexTables()"," returns both tables (",[461,1773,1774],{},"{ blocks, edges }",") for the host's drizzle schema, so ",[461,1777,1778],{},"drizzle-kit"," migrates them; ",[461,1781,1689],{}," creates the tables and their indexes with ",[461,1784,1785],{},"CREATE ... IF NOT EXISTS"," for a host without migrations. ",[461,1788,476],{}," runs in the database and returns the same hits in the same order as the memory adapter: every query word must match the title or the text as a whole word or as the start of one, and a word in the title outranks any match in the text only. The adapters share one test suite (",[461,1791,1792],{},"test\u002Fsupport\u002Fblock-index-contract.ts","), so a third adapter can be held to the same contract.",[418,1795,1796,1797,608,1800,1803,1804,1807,1808,1811,1812,1815],{},"The reference app chooses its adapter with ",[461,1798,1799],{},"NUXT_BLOCK_INDEX",[461,1801,1802],{},"memory",", the default, or ",[461,1805,1806],{},"pglite",": Postgres in the process, in memory, rebuilt on every start). This is the PGlite start of ",[461,1809,1810],{},"server\u002Futils\u002Fblock-index.ts","; a host with a Postgres server passes its own drizzle database to ",[461,1813,1814],{},"createDrizzleBlockIndex"," instead:",[671,1817,1819],{"className":673,"code":1818,"filename":1810,"language":676,"meta":677,"style":677},"    byDocument: async documentId => (await started()).byDocument(documentId),\n    documentIds: async () => (await started()).documentIds(),\n    tags: async () => (await started()).tags(),\n    search: async (query, scope, options) => (await started()).search(query, scope, options),\n    edges: async (address, direction, scope) => (await started()).edges(address, direction, scope),\n    query: async (query, scope, env, definitions) => (await started()).query(query, scope, env, definitions),\n    ...(withVectors && {\n      nearest: async (vector, scope, options) => (await started()).nearest!(vector, scope, options),\n      related: async (address, scope, options) => (await started()).related!(address, scope, options),\n    }),\n  }\n",[461,1820,1821,1854,1883,1912,1966,2019,2079,2092,2147,2199,2208],{"__ignoreMap":677},[681,1822,1823,1826,1828,1830,1832,1834,1836,1839,1842,1845,1847,1849,1852],{"class":683,"line":684},[681,1824,1825],{"class":790},"    byDocument",[681,1827,787],{"class":694},[681,1829,1306],{"class":772},[681,1831,795],{"class":783},[681,1833,855],{"class":772},[681,1835,608],{"class":698},[681,1837,1838],{"class":687},"await",[681,1840,1841],{"class":776}," started",[681,1843,1844],{"class":698},"())",[681,1846,844],{"class":694},[681,1848,467],{"class":776},[681,1850,1851],{"class":698},"(documentId)",[681,1853,1348],{"class":694},[681,1855,1856,1859,1861,1863,1865,1867,1869,1871,1873,1875,1877,1879,1881],{"class":683,"line":718},[681,1857,1858],{"class":790},"    documentIds",[681,1860,787],{"class":694},[681,1862,1306],{"class":772},[681,1864,1309],{"class":694},[681,1866,855],{"class":772},[681,1868,608],{"class":698},[681,1870,1838],{"class":687},[681,1872,1841],{"class":776},[681,1874,1844],{"class":698},[681,1876,844],{"class":694},[681,1878,470],{"class":776},[681,1880,1231],{"class":698},[681,1882,1348],{"class":694},[681,1884,1885,1888,1890,1892,1894,1896,1898,1900,1902,1904,1906,1908,1910],{"class":683,"line":746},[681,1886,1887],{"class":790},"    tags",[681,1889,787],{"class":694},[681,1891,1306],{"class":772},[681,1893,1309],{"class":694},[681,1895,855],{"class":772},[681,1897,608],{"class":698},[681,1899,1838],{"class":687},[681,1901,1841],{"class":776},[681,1903,1844],{"class":698},[681,1905,844],{"class":694},[681,1907,473],{"class":776},[681,1909,1231],{"class":698},[681,1911,1348],{"class":694},[681,1913,1914,1917,1919,1921,1923,1926,1928,1931,1933,1936,1938,1940,1942,1944,1946,1948,1950,1952,1955,1957,1959,1961,1964],{"class":683,"line":753},[681,1915,1916],{"class":790},"    search",[681,1918,787],{"class":694},[681,1920,1306],{"class":772},[681,1922,608],{"class":694},[681,1924,1925],{"class":783},"query",[681,1927,730],{"class":694},[681,1929,1930],{"class":783}," scope",[681,1932,730],{"class":694},[681,1934,1935],{"class":783}," options",[681,1937,841],{"class":694},[681,1939,855],{"class":772},[681,1941,608],{"class":698},[681,1943,1838],{"class":687},[681,1945,1841],{"class":776},[681,1947,1844],{"class":698},[681,1949,844],{"class":694},[681,1951,476],{"class":776},[681,1953,1954],{"class":698},"(query",[681,1956,730],{"class":694},[681,1958,1930],{"class":698},[681,1960,730],{"class":694},[681,1962,1963],{"class":698}," options)",[681,1965,1348],{"class":694},[681,1967,1968,1971,1973,1975,1977,1980,1982,1985,1987,1989,1991,1993,1995,1997,1999,2001,2003,2005,2008,2010,2012,2014,2017],{"class":683,"line":760},[681,1969,1970],{"class":790},"    edges",[681,1972,787],{"class":694},[681,1974,1306],{"class":772},[681,1976,608],{"class":694},[681,1978,1979],{"class":783},"address",[681,1981,730],{"class":694},[681,1983,1984],{"class":783}," direction",[681,1986,730],{"class":694},[681,1988,1930],{"class":783},[681,1990,841],{"class":694},[681,1992,855],{"class":772},[681,1994,608],{"class":698},[681,1996,1838],{"class":687},[681,1998,1841],{"class":776},[681,2000,1844],{"class":698},[681,2002,844],{"class":694},[681,2004,480],{"class":776},[681,2006,2007],{"class":698},"(address",[681,2009,730],{"class":694},[681,2011,1984],{"class":698},[681,2013,730],{"class":694},[681,2015,2016],{"class":698}," scope)",[681,2018,1348],{"class":694},[681,2020,2021,2024,2026,2028,2030,2032,2034,2036,2038,2041,2043,2046,2048,2050,2052,2054,2056,2058,2060,2062,2064,2066,2068,2070,2072,2074,2077],{"class":683,"line":766},[681,2022,2023],{"class":790},"    query",[681,2025,787],{"class":694},[681,2027,1306],{"class":772},[681,2029,608],{"class":694},[681,2031,1925],{"class":783},[681,2033,730],{"class":694},[681,2035,1930],{"class":783},[681,2037,730],{"class":694},[681,2039,2040],{"class":783}," env",[681,2042,730],{"class":694},[681,2044,2045],{"class":783}," definitions",[681,2047,841],{"class":694},[681,2049,855],{"class":772},[681,2051,608],{"class":698},[681,2053,1838],{"class":687},[681,2055,1841],{"class":776},[681,2057,1844],{"class":698},[681,2059,844],{"class":694},[681,2061,1925],{"class":776},[681,2063,1954],{"class":698},[681,2065,730],{"class":694},[681,2067,1930],{"class":698},[681,2069,730],{"class":694},[681,2071,2040],{"class":698},[681,2073,730],{"class":694},[681,2075,2076],{"class":698}," definitions)",[681,2078,1348],{"class":694},[681,2080,2081,2084,2087,2090],{"class":683,"line":824},[681,2082,2083],{"class":694},"    ...",[681,2085,2086],{"class":698},"(withVectors ",[681,2088,2089],{"class":694},"&&",[681,2091,1236],{"class":694},[681,2093,2094,2097,2099,2101,2103,2106,2108,2110,2112,2114,2116,2118,2120,2122,2124,2126,2128,2131,2134,2137,2139,2141,2143,2145],{"class":683,"line":868},[681,2095,2096],{"class":776},"      nearest",[681,2098,787],{"class":694},[681,2100,1306],{"class":772},[681,2102,608],{"class":694},[681,2104,2105],{"class":783},"vector",[681,2107,730],{"class":694},[681,2109,1930],{"class":783},[681,2111,730],{"class":694},[681,2113,1935],{"class":783},[681,2115,841],{"class":694},[681,2117,855],{"class":772},[681,2119,608],{"class":698},[681,2121,1838],{"class":687},[681,2123,1841],{"class":776},[681,2125,1844],{"class":698},[681,2127,844],{"class":694},[681,2129,2130],{"class":776},"nearest",[681,2132,2133],{"class":694},"!",[681,2135,2136],{"class":698},"(vector",[681,2138,730],{"class":694},[681,2140,1930],{"class":698},[681,2142,730],{"class":694},[681,2144,1963],{"class":698},[681,2146,1348],{"class":694},[681,2148,2149,2152,2154,2156,2158,2160,2162,2164,2166,2168,2170,2172,2174,2176,2178,2180,2182,2185,2187,2189,2191,2193,2195,2197],{"class":683,"line":874},[681,2150,2151],{"class":776},"      related",[681,2153,787],{"class":694},[681,2155,1306],{"class":772},[681,2157,608],{"class":694},[681,2159,1979],{"class":783},[681,2161,730],{"class":694},[681,2163,1930],{"class":783},[681,2165,730],{"class":694},[681,2167,1935],{"class":783},[681,2169,841],{"class":694},[681,2171,855],{"class":772},[681,2173,608],{"class":698},[681,2175,1838],{"class":687},[681,2177,1841],{"class":776},[681,2179,1844],{"class":698},[681,2181,844],{"class":694},[681,2183,2184],{"class":776},"related",[681,2186,2133],{"class":694},[681,2188,2007],{"class":698},[681,2190,730],{"class":694},[681,2192,1930],{"class":698},[681,2194,730],{"class":694},[681,2196,1963],{"class":698},[681,2198,1348],{"class":694},[681,2200,2201,2204,2206],{"class":683,"line":879},[681,2202,2203],{"class":694},"    }",[681,2205,841],{"class":698},[681,2207,1348],{"class":694},[681,2209,2210],{"class":683,"line":885},[681,2211,2212],{"class":698},"  }\n",[599,2214,2216],{"id":2215},"the-block-route","The Block route",[418,2218,2219,2222,2223,464,2225,464,2227,464,2229,477,2231,2234,2235,2237,2238,2241,2242,2245,2246,2249,2250,2253,2254,2257,2258,2261,2262,2265,2266,2269,2270,2273],{},[461,2220,2221],{},"GET \u002Fapi\u002Feditor\u002Fblocks\u002F:documentId\u002F:blockId"," answers the summary of one Block: ",[461,2224,640],{},[461,2226,636],{},[461,2228,655],{},[461,2230,658],{},[461,2232,2233],{},"gone",". ",[461,2236,658],{}," is what the stale badge of a Derived block with an original in another Document compares against the ",[461,2239,2240],{},"inputHash"," its Derivation recorded. The server answers ",[461,2243,2244],{},"404"," for an unknown Block or Document (the HTTP client turns that into ",[461,2247,2248],{},"gone: true",") and ",[461,2251,2252],{},"403"," for a Document the user may not read. ",[461,2255,2256],{},"?content=block"," adds the Block's own nodes as TipTap JSON (",[461,2259,2260],{},"BlockSummary.content","), ",[461,2263,2264],{},"?content=children"," the Blocks nested in it; any other value is ",[461,2267,2268],{},"400",". The content is read from the Document's current content, unsaved edits of an open Document included. In the client the route is ",[461,2271,2272],{},"backend.index.blockGet(address, signal?, content?)","; foreign references and Embeds read through it.",[599,2275,502],{"id":2276},"workspace-search",[418,2278,2279,2282,2283,608,2286,464,2288,464,2290,464,2292,2294,2295,844],{},[461,2280,2281],{},"GET \u002Fapi\u002Feditor\u002Fsearch?q="," answers ",[461,2284,2285],{},"BlockSearchHit[]",[461,2287,999],{},[461,2289,1011],{},[461,2291,640],{},[461,2293,636],{},"), best first, 20 at most. A query is words: a Block matches when every word occurs in its title or text, as a whole word or as the start of one, ignoring case and diacritics; a Block with a word in its title ranks above one that has it only in its text. A query without a word answers ",[461,2296,2297],{},"[]",[418,2299,2300,2301,2304,2305,2308,2309,2312,2313,2316,2317,2308,2320,464,2323,464,2326,464,2329,2332,2333,2336,2337,2340,2341,2344,2345,2347,2348,2351,2352,2354,2355,2357,2358,2360,2361,2363,2364,2367,2368,2371,2372,2374,2375,2377,2378,2380],{},"Search only ever sees Documents the user may read. The route asks ",[461,2302,2303],{},"readableDocumentsOf(ports, user)"," for the scope (",[461,2306,2307],{},"DocumentScope",": ",[461,2310,2311],{},"'all'"," or a list of Document IDs) and hands it to ",[461,2314,2315],{},"BlockIndex.search",", so a Block of any other Document is never a candidate. There is one read rule for the whole workspace (",[461,2318,2319],{},"server\u002Freadable-documents.ts",[461,2321,2322],{},"documentAccess",[461,2324,2325],{},"mayReadDocument",[461,2327,2328],{},"mayEditDocument",[461,2330,2331],{},"readableAmong","), and the block route, the document GET, the tree, the ops route, the search and the chat's tools all ask it: ",[461,2334,2335],{},"identity.readableDocuments(user)"," decides when the host has it, else ",[461,2338,2339],{},"authorize(user, id)"," does (",[461,2342,2343],{},"null"," = not readable), and with neither every authenticated user may read every Document. A Document that ",[461,2346,1117],{}," names but ",[461,2349,2350],{},"authorize"," refuses is read-only. Without ",[461,2353,1117],{}," the scope is ",[461,2356,2311],{}," when there is no ",[461,2359,2350],{},", else every Document ",[461,2362,2350],{}," does not refuse; the candidates come from ",[461,2365,2366],{},"documents.listDocumentIds",", else ",[461,2369,2370],{},"index.documentIds()"," (the Documents that have Blocks, the only ones a search could find), else the IDs of ",[461,2373,625],{},". A host with many Documents implements ",[461,2376,1117],{},", because the derived scope costs one ",[461,2379,2350],{}," call per Document and request.",[418,2382,2383,2384,464,2387,2390,2391,2394,2395,464,2398,2401,2402,2405,2406,2409,2410,2412],{},"The search panel is a framework-free model (",[461,2385,2386],{},"createSearchModel",[461,2388,2389],{},"@skaile\u002Fforge-editor\u002Fsearch",") behind a ",[461,2392,2393],{},"SearchPanel"," in each framework (",[461,2396,2397],{},"@skaile\u002Fforge-editor\u002Fsearch\u002Fvue",[461,2399,2400],{},"@skaile\u002Fforge-editor\u002Fsearch\u002Freact","). The panel never opens a hit itself: its ",[461,2403,2404],{},"open"," property is the host's navigation, which opens the Document and reveals the Block. ",[461,2407,2408],{},"documentTitle"," is optional and turns a Document ID into a label. The panel appears only when the backend has the ",[461,2411,485],{}," capability. This is the reference app's layout:",[671,2414,2419],{"className":2415,"code":2416,"filename":2417,"language":2418,"meta":677,"style":677},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const selectedFolder = ref('')\n\n\u002F\u002F Workspace search: a hit in the open document is revealed in place; any other hit opens its document, whose page reveals\n\u002F\u002F the block (`?block=`) once the editor has synced.\nconst route = useRoute()\nconst activeEditor = useActiveEditor()\nconst { documents } = useDocuments()\nconst documentTitle = (id: string) => documents.value.find(document => document.id === id)?.title\n\n\u002F\u002F Edits the chat proposed for other documents wait here for the user's decision, per document.\nconst changeSets = useChangeSets()\nconst openDocument = (id: string) => navigateTo(`\u002Fdocuments\u002F${id}`)\n\n\u002F\u002F The Explore panel (backlinks, related blocks, trail; with a knowledge graph a fourth tab) shows the Block „Verknüpfungen anzeigen“\n\u002F\u002F in the inspector chose (`useExploreTarget`). The graph panel (`NUXT_PUBLIC_ADDONS_GRAPH=1`) draws the Edges around the open document, or around that Block.\nconst exploreTarget = useExploreTarget()\nconst { graph: graphEnabled } = addonFlagsOf(useRuntimeConfig().public.addons)\n","app\u002Flayouts\u002Fdefault.vue","vue",[461,2420,2421,2426,2430,2435,2440,2445,2450,2455,2460,2464,2469,2474,2479,2483,2488,2493,2498],{"__ignoreMap":677},[681,2422,2423],{"class":683,"line":684},[681,2424,2425],{"class":698},"const selectedFolder = ref('')\n",[681,2427,2428],{"class":683,"line":718},[681,2429,750],{"emptyLinePlaceholder":749},[681,2431,2432],{"class":683,"line":746},[681,2433,2434],{"class":698},"\u002F\u002F Workspace search: a hit in the open document is revealed in place; any other hit opens its document, whose page reveals\n",[681,2436,2437],{"class":683,"line":753},[681,2438,2439],{"class":698},"\u002F\u002F the block (`?block=`) once the editor has synced.\n",[681,2441,2442],{"class":683,"line":760},[681,2443,2444],{"class":698},"const route = useRoute()\n",[681,2446,2447],{"class":683,"line":766},[681,2448,2449],{"class":698},"const activeEditor = useActiveEditor()\n",[681,2451,2452],{"class":683,"line":824},[681,2453,2454],{"class":698},"const { documents } = useDocuments()\n",[681,2456,2457],{"class":683,"line":868},[681,2458,2459],{"class":698},"const documentTitle = (id: string) => documents.value.find(document => document.id === id)?.title\n",[681,2461,2462],{"class":683,"line":874},[681,2463,750],{"emptyLinePlaceholder":749},[681,2465,2466],{"class":683,"line":879},[681,2467,2468],{"class":698},"\u002F\u002F Edits the chat proposed for other documents wait here for the user's decision, per document.\n",[681,2470,2471],{"class":683,"line":885},[681,2472,2473],{"class":698},"const changeSets = useChangeSets()\n",[681,2475,2476],{"class":683,"line":920},[681,2477,2478],{"class":698},"const openDocument = (id: string) => navigateTo(`\u002Fdocuments\u002F${id}`)\n",[681,2480,2481],{"class":683,"line":937},[681,2482,750],{"emptyLinePlaceholder":749},[681,2484,2485],{"class":683,"line":966},[681,2486,2487],{"class":698},"\u002F\u002F The Explore panel (backlinks, related blocks, trail; with a knowledge graph a fourth tab) shows the Block „Verknüpfungen anzeigen“\n",[681,2489,2490],{"class":683,"line":1036},[681,2491,2492],{"class":698},"\u002F\u002F in the inspector chose (`useExploreTarget`). The graph panel (`NUXT_PUBLIC_ADDONS_GRAPH=1`) draws the Edges around the open document, or around that Block.\n",[681,2494,2495],{"class":683,"line":1383},[681,2496,2497],{"class":698},"const exploreTarget = useExploreTarget()\n",[681,2499,2500],{"class":683,"line":1393},[681,2501,2502],{"class":698},"const { graph: graphEnabled } = addonFlagsOf(useRuntimeConfig().public.addons)\n",[671,2504,2506],{"className":2415,"code":2505,"filename":2417,"language":2418,"meta":677,"style":677},"          to=\"\u002Fkitchen-sink\"\n",[461,2507,2508],{"__ignoreMap":677},[681,2509,2510],{"class":683,"line":684},[681,2511,2505],{"class":698},[599,2513,2515],{"id":2514},"references-to-blocks-of-other-documents","References to Blocks of other Documents",[418,2517,2518,2519,2522,2523,2525,2526,2529,2530,2533],{},"A reference chip (",[461,2520,2521],{},"ReferenceAttrs",") has an optional ",[461,2524,999],{},". Without one it points into its own Document, as before. With one it points at a Block of another Document, and its label is the live title of that Block, read through ",[461,2527,2528],{},"backend.index.blockGet"," and refreshed in the background; the cached ",[461,2531,2532],{},"label"," only stands in while the Block cannot be resolved. The Markdown form puts the Document in front of the block ID, percent-encoded:",[671,2535,2539],{"className":2536,"code":2537,"language":2538,"meta":677,"style":677},"language-md shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","[@minutes-2026-10#0d7c9a0e-3f1a-4a4e-9c52-1b8a6d3f2e11]\n[@minutes-2026-10#0d7c9a0e-3f1a-4a4e-9c52-1b8a6d3f2e11, S. 12]\n","md",[461,2540,2541,2551],{"__ignoreMap":677},[681,2542,2543,2545,2548],{"class":683,"line":684},[681,2544,808],{"class":694},[681,2546,2547],{"class":711},"@minutes-2026-10#0d7c9a0e-3f1a-4a4e-9c52-1b8a6d3f2e11",[681,2549,2550],{"class":694},"]\n",[681,2552,2553],{"class":683,"line":718},[681,2554,2555],{"class":698},"[@minutes-2026-10#0d7c9a0e-3f1a-4a4e-9c52-1b8a6d3f2e11, S. 12]\n",[418,2557,2558,2559,2562,2563,2566],{},"The second line adds a locator. A token without a Document keeps the short form ",[461,2560,2561],{},"[@\u003Cblock>]",". A token with a Document always becomes a chip, because the importing Document cannot tell whether the Block exists; the index resolves it or the chip shows it as gone. Typing ",[461,2564,2565],{},"[@"," offers Blocks of every Document through the search. The Markdown fence of a Typed block carries its block ID only when a reference chip or a field points at it, so the ID survives the Markdown round trip (ADR-0001).",[418,2568,2569,2570,2573,2574,2576,2577,2579],{},"A Derived block can name an original in another Document (",[461,2571,2572],{},"DerivationSource.document","). Whether it is stale is decided by comparing the ",[461,2575,2240],{}," its Derivation recorded with the ",[461,2578,658],{}," of the original's row, which the Block route also answers.",[599,2581,2583],{"id":2582},"document-routes-and-the-documentmutator","Document routes and the DocumentMutator",[418,2585,2586,2587,2590,2591,464,2594,464,2597,2600,2601,2603,2604,2606],{},"A documents port that implements the optional ",[461,2588,2589],{},"DocumentAdmin"," members (",[461,2592,2593],{},"listTree",[461,2595,2596],{},"createDocument",[461,2598,2599],{},"moveDocument",", next to ",[461,2602,1068],{},") serves the Document routes. Without those members they answer ",[461,2605,592],{},". The list and the tree hide the Documents the user may not read.",[431,2608,2609,2619],{},[434,2610,2611],{},[437,2612,2613,2616],{},[440,2614,2615],{},"Route",[440,2617,2618],{},"Does",[450,2620,2621,2631,2641,2662,2672,2682],{},[437,2622,2623,2628],{},[455,2624,2625],{},[461,2626,2627],{},"GET \u002Fapi\u002Feditor\u002Fdocuments",[455,2629,2630],{},"the Documents the user may read, newest first",[437,2632,2633,2638],{},[455,2634,2635],{},[461,2636,2637],{},"GET \u002Fapi\u002Feditor\u002Ftree",[455,2639,2640],{},"the workspace as a folder tree",[437,2642,2643,2648],{},[455,2644,2645],{},[461,2646,2647],{},"POST \u002Fapi\u002Feditor\u002Fdocuments",[455,2649,2650,2651,2654,2655,2658,2659,841],{},"creates a Document (",[461,2652,2653],{},"{ title?, folder?, templateId? }","; a ",[461,2656,2657],{},"templateId"," copies a Dokumentvorlage, see ",[1758,2660,2661],{"href":225},"Templates",[437,2663,2664,2669],{},[455,2665,2666],{},[461,2667,2668],{},"PUT \u002Fapi\u002Feditor\u002Fdocuments\u002F:id\u002Ffolder",[455,2670,2671],{},"moves a Document into a folder",[437,2673,2674,2679],{},[455,2675,2676],{},[461,2677,2678],{},"DELETE \u002Fapi\u002Feditor\u002Fdocuments\u002F:id",[455,2680,2681],{},"removes a Document with its Yjs state and its rows in the index",[437,2683,2684,2689],{},[455,2685,2686],{},[461,2687,2688],{},"POST \u002Fapi\u002Feditor\u002Fdocuments\u002F:id\u002Fops",[455,2690,2691,2692,2695],{},"applies block operations (",[461,2693,2694],{},"{ ops }","), all or none",[418,2697,2698,2699,2701,2702,464,2705,2707,2708,2711,2712,2715,2716,464,2719,464,2722,464,2725,2728,2729,2732,2733,2735,2736,2739,2740,2742,2743,2745,2746,2749,2750,2753],{},"The ",[461,2700,532],{}," behind the last route (",[461,2703,2704],{},"createDocumentMutator",[461,2706,614],{},"; ",[461,2709,2710],{},"EditorServer.mutator"," of the installed server) changes a Document nobody has to have open. The operations are those of the chat's ",[461,2713,2714],{},"edit_document"," tool (",[461,2717,2718],{},"update",[461,2720,2721],{},"add",[461,2723,2724],{},"delete",[461,2726,2727],{},"convert","). It checks each one against the block IDs of the Document, parses the Markdown without an editor (",[461,2730,2731],{},"createMarkdownBlocks","), writes an open Document inside its Y.Doc, so connected clients see an ordinary edit, and a stored Document through ",[461,2734,1062],{},"; the index is re-projected either way. Refusals are ",[461,2737,2738],{},"HttpError","s: ",[461,2741,2252],{}," for a user without write access, ",[461,2744,2244],{}," for an unknown Document, ",[461,2747,2748],{},"409"," when a block the operations name no longer exists, ",[461,2751,2752],{},"422"," for an invalid operation. Host code that changes a Document on a user's behalf calls the same method:",[671,2755,2758],{"className":673,"code":2756,"filename":2757,"language":676,"meta":677,"style":677},"\u002F\u002F server\u002Fapi\u002Fdocuments\u002F[id]\u002Freplace-block.post.ts\nimport { HttpError } from '@skaile\u002Fforge-editor\u002Fserver'\nimport { useEditorServer } from '@skaile\u002Fforge-editor\u002Fserver\u002Fnitro'\n\n\u002F\u002F A host route that changes a Document on a user's behalf. The mutator checks the user's right (`authorize`, 403), the block\n\u002F\u002F IDs (409 when a block is gone) and the operations (422), applies all or none, and keeps the Block index current.\nexport default defineEventHandler(async (event) => {\n  const id = getRouterParam(event, 'id')\n  const user = await currentUser(event)\n  if (!id || !user) throw new HttpError(400, 'Document and user needed')\n  const { blockId, markdown } = await readBody\u003C{ blockId: string, markdown: string }>(event)\n  const { mutator } = useEditorServer()\n  return mutator.apply(id, [{ type: 'update', id: blockId, markdown }], { user })\n})\n\n\u002F\u002F Nuxt auto-imports and the host's session helper, declared so the file typechecks on its own.\ndeclare function defineEventHandler(handler: (event: object) => Promise\u003Cunknown>): unknown\ndeclare function getRouterParam(event: object, name: string): string | undefined\ndeclare function readBody\u003CT>(event: object): Promise\u003CT>\ndeclare function currentUser(event: object): Promise\u003Cobject | null>\n","docs\u002Fsnippets\u002Fworkspace-mutator.ts",[461,2759,2760,2765,2784,2803,2807,2812,2817,2842,2867,2888,2933,2981,2998,3055,3061,3065,3070,3111,3146,3179],{"__ignoreMap":677},[681,2761,2762],{"class":683,"line":684},[681,2763,2764],{"class":756},"\u002F\u002F server\u002Fapi\u002Fdocuments\u002F[id]\u002Freplace-block.post.ts\n",[681,2766,2767,2769,2771,2774,2776,2778,2780,2782],{"class":683,"line":718},[681,2768,688],{"class":687},[681,2770,695],{"class":694},[681,2772,2773],{"class":698}," HttpError",[681,2775,702],{"class":694},[681,2777,705],{"class":687},[681,2779,708],{"class":694},[681,2781,614],{"class":711},[681,2783,715],{"class":694},[681,2785,2786,2788,2790,2793,2795,2797,2799,2801],{"class":683,"line":746},[681,2787,688],{"class":687},[681,2789,695],{"class":694},[681,2791,2792],{"class":698}," useEditorServer",[681,2794,702],{"class":694},[681,2796,705],{"class":687},[681,2798,708],{"class":694},[681,2800,1210],{"class":711},[681,2802,715],{"class":694},[681,2804,2805],{"class":683,"line":753},[681,2806,750],{"emptyLinePlaceholder":749},[681,2808,2809],{"class":683,"line":760},[681,2810,2811],{"class":756},"\u002F\u002F A host route that changes a Document on a user's behalf. The mutator checks the user's right (`authorize`, 403), the block\n",[681,2813,2814],{"class":683,"line":766},[681,2815,2816],{"class":756},"\u002F\u002F IDs (409 when a block is gone) and the operations (422), applies all or none, and keeps the Block index current.\n",[681,2818,2819,2821,2823,2826,2828,2831,2833,2836,2838,2840],{"class":683,"line":824},[681,2820,769],{"class":687},[681,2822,1223],{"class":687},[681,2824,2825],{"class":776}," defineEventHandler",[681,2827,780],{"class":698},[681,2829,2830],{"class":772},"async",[681,2832,608],{"class":694},[681,2834,2835],{"class":783},"event",[681,2837,841],{"class":694},[681,2839,855],{"class":772},[681,2841,1236],{"class":694},[681,2843,2844,2846,2848,2850,2853,2855,2857,2859,2861,2863,2865],{"class":683,"line":868},[681,2845,1241],{"class":772},[681,2847,1319],{"class":698},[681,2849,1247],{"class":694},[681,2851,2852],{"class":776}," getRouterParam",[681,2854,780],{"class":832},[681,2856,2835],{"class":698},[681,2858,730],{"class":694},[681,2860,708],{"class":694},[681,2862,1415],{"class":711},[681,2864,811],{"class":694},[681,2866,865],{"class":832},[681,2868,2869,2871,2874,2876,2879,2882,2884,2886],{"class":683,"line":874},[681,2870,1241],{"class":772},[681,2872,2873],{"class":698}," user",[681,2875,1247],{"class":694},[681,2877,2878],{"class":687}," await",[681,2880,2881],{"class":776}," currentUser",[681,2883,780],{"class":832},[681,2885,2835],{"class":698},[681,2887,865],{"class":832},[681,2889,2890,2893,2895,2897,2899,2902,2905,2908,2910,2913,2915,2917,2919,2922,2924,2926,2929,2931],{"class":683,"line":879},[681,2891,2892],{"class":687},"  if",[681,2894,608],{"class":832},[681,2896,2133],{"class":694},[681,2898,1415],{"class":698},[681,2900,2901],{"class":694}," ||",[681,2903,2904],{"class":694}," !",[681,2906,2907],{"class":698},"user",[681,2909,1418],{"class":832},[681,2911,2912],{"class":687},"throw",[681,2914,1250],{"class":694},[681,2916,2773],{"class":776},[681,2918,780],{"class":832},[681,2920,2268],{"class":2921},"sbssI",[681,2923,730],{"class":694},[681,2925,708],{"class":694},[681,2927,2928],{"class":711},"Document and user needed",[681,2930,811],{"class":694},[681,2932,865],{"class":832},[681,2934,2935,2937,2939,2941,2943,2946,2948,2950,2952,2955,2958,2960,2962,2964,2966,2968,2970,2972,2975,2977,2979],{"class":683,"line":885},[681,2936,1241],{"class":772},[681,2938,695],{"class":694},[681,2940,979],{"class":698},[681,2942,730],{"class":694},[681,2944,2945],{"class":698}," markdown",[681,2947,702],{"class":694},[681,2949,1247],{"class":694},[681,2951,2878],{"class":687},[681,2953,2954],{"class":776}," readBody",[681,2956,2957],{"class":694},"\u003C{",[681,2959,979],{"class":832},[681,2961,787],{"class":694},[681,2963,800],{"class":790},[681,2965,730],{"class":694},[681,2967,2945],{"class":832},[681,2969,787],{"class":694},[681,2971,800],{"class":790},[681,2973,2974],{"class":694}," }>",[681,2976,780],{"class":832},[681,2978,2835],{"class":698},[681,2980,865],{"class":832},[681,2982,2983,2985,2987,2990,2992,2994,2996],{"class":683,"line":920},[681,2984,1241],{"class":772},[681,2986,695],{"class":694},[681,2988,2989],{"class":698}," mutator",[681,2991,702],{"class":694},[681,2993,1247],{"class":694},[681,2995,2792],{"class":776},[681,2997,1269],{"class":832},[681,2999,3000,3002,3004,3006,3009,3011,3013,3015,3017,3019,3021,3023,3025,3027,3029,3031,3033,3035,3037,3039,3041,3043,3045,3047,3049,3051,3053],{"class":683,"line":937},[681,3001,827],{"class":687},[681,3003,2989],{"class":698},[681,3005,844],{"class":694},[681,3007,3008],{"class":776},"apply",[681,3010,780],{"class":832},[681,3012,1415],{"class":698},[681,3014,730],{"class":694},[681,3016,1486],{"class":832},[681,3018,1316],{"class":694},[681,3020,691],{"class":832},[681,3022,787],{"class":694},[681,3024,708],{"class":694},[681,3026,2718],{"class":711},[681,3028,811],{"class":694},[681,3030,730],{"class":694},[681,3032,1319],{"class":832},[681,3034,787],{"class":694},[681,3036,979],{"class":698},[681,3038,730],{"class":694},[681,3040,2945],{"class":698},[681,3042,702],{"class":694},[681,3044,1495],{"class":832},[681,3046,730],{"class":694},[681,3048,695],{"class":694},[681,3050,2873],{"class":698},[681,3052,702],{"class":694},[681,3054,865],{"class":832},[681,3056,3057,3059],{"class":683,"line":966},[681,3058,1002],{"class":694},[681,3060,865],{"class":698},[681,3062,3063],{"class":683,"line":1036},[681,3064,750],{"emptyLinePlaceholder":749},[681,3066,3067],{"class":683,"line":1383},[681,3068,3069],{"class":756},"\u002F\u002F Nuxt auto-imports and the host's session helper, declared so the file typechecks on its own.\n",[681,3071,3072,3074,3076,3078,3080,3083,3085,3087,3089,3091,3094,3096,3098,3101,3103,3106,3109],{"class":683,"line":1393},[681,3073,1615],{"class":772},[681,3075,773],{"class":772},[681,3077,2825],{"class":776},[681,3079,780],{"class":694},[681,3081,3082],{"class":776},"handler",[681,3084,787],{"class":694},[681,3086,608],{"class":694},[681,3088,2835],{"class":783},[681,3090,787],{"class":694},[681,3092,3093],{"class":790}," object",[681,3095,841],{"class":694},[681,3097,855],{"class":772},[681,3099,3100],{"class":790}," Promise",[681,3102,1256],{"class":694},[681,3104,3105],{"class":790},"unknown",[681,3107,3108],{"class":694},">):",[681,3110,1638],{"class":790},[681,3112,3113,3115,3117,3119,3121,3123,3125,3127,3129,3132,3134,3136,3138,3140,3143],{"class":683,"line":1427},[681,3114,1615],{"class":772},[681,3116,773],{"class":772},[681,3118,2852],{"class":776},[681,3120,780],{"class":694},[681,3122,2835],{"class":783},[681,3124,787],{"class":694},[681,3126,3093],{"class":790},[681,3128,730],{"class":694},[681,3130,3131],{"class":783}," name",[681,3133,787],{"class":694},[681,3135,800],{"class":790},[681,3137,803],{"class":694},[681,3139,800],{"class":790},[681,3141,3142],{"class":694}," |",[681,3144,3145],{"class":790}," undefined\n",[681,3147,3148,3150,3152,3154,3156,3159,3162,3164,3166,3168,3170,3172,3174,3176],{"class":683,"line":1472},[681,3149,1615],{"class":772},[681,3151,773],{"class":772},[681,3153,2954],{"class":776},[681,3155,1256],{"class":694},[681,3157,3158],{"class":790},"T",[681,3160,3161],{"class":694},">(",[681,3163,2835],{"class":783},[681,3165,787],{"class":694},[681,3167,3093],{"class":790},[681,3169,803],{"class":694},[681,3171,3100],{"class":790},[681,3173,1256],{"class":694},[681,3175,3158],{"class":790},[681,3177,3178],{"class":694},">\n",[681,3180,3181,3183,3185,3187,3189,3191,3193,3195,3197,3199,3201,3204,3206,3209],{"class":683,"line":1535},[681,3182,1615],{"class":772},[681,3184,773],{"class":772},[681,3186,2881],{"class":776},[681,3188,780],{"class":694},[681,3190,2835],{"class":783},[681,3192,787],{"class":694},[681,3194,3093],{"class":790},[681,3196,803],{"class":694},[681,3198,3100],{"class":790},[681,3200,1256],{"class":694},[681,3202,3203],{"class":790},"object",[681,3205,3142],{"class":694},[681,3207,3208],{"class":790}," null",[681,3210,3178],{"class":694},[599,3212,545],{"id":3213},"tool-providers",[418,3215,3216,3217,3219,3220,3223,3224,3226,3227,3229,3230,3233,3234,3236,3237,2308,3240,3243,3244,2261,3247,3250,3251,3254,3255,477,3258,3260,3261,3264,3265,3267],{},"The document chat has one tool of its own, ",[461,3218,2714],{},". A host can add read-only tools: ",[461,3221,3222],{},"tools"," of ",[461,3225,1044],{}," or ",[461,3228,1048],{}," takes a list of ",[461,3231,3232],{},"ToolProvider","s (",[461,3235,614],{},"). A provider is ",[461,3238,3239],{},"{ name, tool, handle(ctx, input) }",[461,3241,3242],{},"tool"," is the AI SDK definition the model sees (description and input schema, no ",[461,3245,3246],{},"execute",[461,3248,3249],{},"handle"," is what the server runs. The server feeds the returned text back to the model and lets it answer again. ",[461,3252,3253],{},"ToolContext"," holds the ports, the user behind the request, an abort signal, ",[461,3256,3257],{},"liveContent(id)",[461,3259,999],{}," (the Document open in the chat, absent for a chat without one); every read must stay inside what that user may read. ",[461,3262,3263],{},"toolRegistry(providers)"," indexes the list and throws for a missing or duplicate name and for ",[461,3266,2714],{},"; the install calls it, so a bad provider fails at start.",[418,3269,3270,3273],{},[461,3271,3272],{},"createWorkspaceTools(options?)"," provides two:",[431,3275,3276,3289],{},[434,3277,3278],{},[437,3279,3280,3283,3286],{},[440,3281,3282],{},"Tool",[440,3284,3285],{},"Input",[440,3287,3288],{},"Answers",[450,3290,3291,3308],{},[437,3292,3293,3297,3305],{},[455,3294,3295],{},[461,3296,551],{},[455,3298,3299,464,3301,3304],{},[461,3300,1925],{},[461,3302,3303],{},"limit?"," (10 by default)",[455,3306,3307],{},"the best hits over the Documents the user may read: address, Block type, title and the start of the text",[437,3309,3310,3314,3320],{},[455,3311,3312],{},[461,3313,554],{},[455,3315,3316,464,3318],{},[461,3317,999],{},[461,3319,1011],{},[455,3321,3322],{},"the Block as Markdown (6000 characters at most, cut with a note), read from the current content",[418,3324,3325,3326,3328,3329,3331],{},"An unreadable Document and an unknown Block get the same answer from ",[461,3327,554],{},", so the tool never confirms that a Block exists where the user may not look. Without the ",[461,3330,485],{}," port a call answers that the workspace has no index. A provider can be wrapped to add behaviour; the wrapper keeps the definition:",[671,3333,3336],{"className":673,"code":3334,"filename":3335,"language":676,"meta":677,"style":677},"import { createWorkspaceTools, type ToolProvider } from '@skaile\u002Fforge-editor\u002Fserver'\n\n\u002F\u002F A ToolProvider is a definition for the model (`name`, `tool`: description and input schema, no `execute`) and the handler\n\u002F\u002F the server runs. `handle` gets the model's raw input and answers the text the model reads. This wrapper keeps the\n\u002F\u002F definition and adds a log line to every call; the `ToolContext` holds the ports, the user, an abort signal, `liveContent` and the open `documentId`.\nfunction logged(provider: ToolProvider): ToolProvider {\n  return {\n    ...provider,\n    async handle(ctx, input) {\n      const answer = await provider.handle(ctx, input)\n      console.info(`[tool ${provider.name}] ${JSON.stringify(input)} -> ${answer.length} characters`)\n      return answer\n    },\n  }\n}\n\n\u002F\u002F Register the result next to your own providers: `provideEditorServer({ ports, tools })`.\nexport const tools: ToolProvider[] = createWorkspaceTools().map(logged)\n","docs\u002Fsnippets\u002Fworkspace-tool.ts",[461,3337,3338,3363,3367,3372,3377,3382,3405,3411,3419,3441,3470,3538,3546,3550,3554,3558,3562,3567],{"__ignoreMap":677},[681,3339,3340,3342,3344,3346,3348,3350,3353,3355,3357,3359,3361],{"class":683,"line":684},[681,3341,688],{"class":687},[681,3343,695],{"class":694},[681,3345,1182],{"class":698},[681,3347,730],{"class":694},[681,3349,691],{"class":687},[681,3351,3352],{"class":698}," ToolProvider",[681,3354,702],{"class":694},[681,3356,705],{"class":687},[681,3358,708],{"class":694},[681,3360,614],{"class":711},[681,3362,715],{"class":694},[681,3364,3365],{"class":683,"line":718},[681,3366,750],{"emptyLinePlaceholder":749},[681,3368,3369],{"class":683,"line":746},[681,3370,3371],{"class":756},"\u002F\u002F A ToolProvider is a definition for the model (`name`, `tool`: description and input schema, no `execute`) and the handler\n",[681,3373,3374],{"class":683,"line":753},[681,3375,3376],{"class":756},"\u002F\u002F the server runs. `handle` gets the model's raw input and answers the text the model reads. This wrapper keeps the\n",[681,3378,3379],{"class":683,"line":760},[681,3380,3381],{"class":756},"\u002F\u002F definition and adds a log line to every call; the `ToolContext` holds the ports, the user, an abort signal, `liveContent` and the open `documentId`.\n",[681,3383,3384,3387,3390,3392,3395,3397,3399,3401,3403],{"class":683,"line":766},[681,3385,3386],{"class":772},"function",[681,3388,3389],{"class":776}," logged",[681,3391,780],{"class":694},[681,3393,3394],{"class":783},"provider",[681,3396,787],{"class":694},[681,3398,3352],{"class":790},[681,3400,803],{"class":694},[681,3402,3352],{"class":790},[681,3404,1236],{"class":694},[681,3406,3407,3409],{"class":683,"line":824},[681,3408,827],{"class":687},[681,3410,1236],{"class":694},[681,3412,3413,3415,3417],{"class":683,"line":868},[681,3414,2083],{"class":694},[681,3416,3394],{"class":698},[681,3418,1348],{"class":694},[681,3420,3421,3424,3427,3429,3432,3434,3437,3439],{"class":683,"line":874},[681,3422,3423],{"class":772},"    async",[681,3425,3426],{"class":832}," handle",[681,3428,780],{"class":694},[681,3430,3431],{"class":783},"ctx",[681,3433,730],{"class":694},[681,3435,3436],{"class":783}," input",[681,3438,841],{"class":694},[681,3440,1236],{"class":694},[681,3442,3443,3446,3449,3451,3453,3456,3458,3460,3462,3464,3466,3468],{"class":683,"line":879},[681,3444,3445],{"class":772},"      const",[681,3447,3448],{"class":698}," answer",[681,3450,1247],{"class":694},[681,3452,2878],{"class":687},[681,3454,3455],{"class":698}," provider",[681,3457,844],{"class":694},[681,3459,3249],{"class":776},[681,3461,780],{"class":832},[681,3463,3431],{"class":698},[681,3465,730],{"class":694},[681,3467,3436],{"class":698},[681,3469,865],{"class":832},[681,3471,3472,3475,3477,3480,3482,3485,3488,3490,3492,3494,3497,3499,3501,3503,3506,3508,3511,3514,3516,3519,3521,3524,3526,3529,3531,3534,3536],{"class":683,"line":885},[681,3473,3474],{"class":698},"      console",[681,3476,844],{"class":694},[681,3478,3479],{"class":776},"info",[681,3481,780],{"class":832},[681,3483,3484],{"class":694},"`",[681,3486,3487],{"class":711},"[tool ",[681,3489,1008],{"class":694},[681,3491,3394],{"class":698},[681,3493,844],{"class":694},[681,3495,3496],{"class":698},"name",[681,3498,1002],{"class":694},[681,3500,818],{"class":711},[681,3502,1008],{"class":694},[681,3504,3505],{"class":698},"JSON",[681,3507,844],{"class":694},[681,3509,3510],{"class":776},"stringify",[681,3512,3513],{"class":698},"(input)",[681,3515,1002],{"class":694},[681,3517,3518],{"class":711}," -> ",[681,3520,1008],{"class":694},[681,3522,3523],{"class":698},"answer",[681,3525,844],{"class":694},[681,3527,3528],{"class":698},"length",[681,3530,1002],{"class":694},[681,3532,3533],{"class":711}," characters",[681,3535,3484],{"class":694},[681,3537,865],{"class":832},[681,3539,3540,3543],{"class":683,"line":920},[681,3541,3542],{"class":687},"      return",[681,3544,3545],{"class":698}," answer\n",[681,3547,3548],{"class":683,"line":937},[681,3549,1563],{"class":694},[681,3551,3552],{"class":683,"line":966},[681,3553,2212],{"class":694},[681,3555,3556],{"class":683,"line":1036},[681,3557,871],{"class":694},[681,3559,3560],{"class":683,"line":1383},[681,3561,750],{"emptyLinePlaceholder":749},[681,3563,3564],{"class":683,"line":1393},[681,3565,3566],{"class":756},"\u002F\u002F Register the result next to your own providers: `provideEditorServer({ ports, tools })`.\n",[681,3568,3569,3571,3574,3577,3579,3581,3583,3586,3588,3590,3592,3594],{"class":683,"line":1427},[681,3570,769],{"class":687},[681,3572,3573],{"class":772}," const",[681,3575,3576],{"class":698}," tools",[681,3578,787],{"class":694},[681,3580,3352],{"class":790},[681,3582,915],{"class":698},[681,3584,3585],{"class":694},"=",[681,3587,1182],{"class":776},[681,3589,1231],{"class":698},[681,3591,844],{"class":694},[681,3593,971],{"class":776},[681,3595,3596],{"class":698},"(logged)\n",[418,3598,3599],{},"The reference app registers the tools in its Nitro plugin:",[671,3601,3604],{"className":673,"code":3602,"filename":3603,"language":676,"meta":677,"style":677},"        listModels: async () => stubModelEnabled() ? [...publicEnvPresets(), stubModelProfile] : null,\n      },\n","server\u002Fplugins\u002Feditor-adapter.ts",[461,3605,3606,3646],{"__ignoreMap":677},[681,3607,3608,3611,3613,3615,3617,3619,3622,3625,3628,3630,3632,3635,3637,3639,3642,3644],{"class":683,"line":684},[681,3609,3610],{"class":790},"        listModels",[681,3612,787],{"class":694},[681,3614,1306],{"class":772},[681,3616,1309],{"class":694},[681,3618,855],{"class":772},[681,3620,3621],{"class":776}," stubModelEnabled",[681,3623,3624],{"class":698},"() ",[681,3626,3627],{"class":694},"?",[681,3629,1486],{"class":698},[681,3631,1489],{"class":694},[681,3633,3634],{"class":776},"publicEnvPresets",[681,3636,1231],{"class":698},[681,3638,730],{"class":694},[681,3640,3641],{"class":698}," stubModelProfile] ",[681,3643,787],{"class":694},[681,3645,1424],{"class":694},[681,3647,3648,3651],{"class":683,"line":718},[681,3649,3650],{"class":698},"      }",[681,3652,1348],{"class":694},[599,3654,3656],{"id":3655},"change-sets","Change sets",[418,3658,3659,3660,3662,3663,1059,3665,3668,3669,2707,3672,3675,3676,2261,3679,3681,3682,3684,3685,3688,3689,3692],{},"When the server has a ",[461,3661,532],{}," and the host provides the ",[461,3664,572],{},[461,3666,3667],{},"EditorPorts.changeSets",", a ",[461,3670,3671],{},"ChangeSetStore",[461,3673,3674],{},"createJsonAdapter({ dir }).changeSets"," keeps them in ",[461,3677,3678],{},".change-sets.json",[461,3680,2714],{}," accepts an optional ",[461,3683,999],{}," on an operation. Such an operation aims at another Document, which the user must be able to read and edit. The chat does not apply it. The server stores the operations of one answer as a ",[422,3686,3687],{},"change set"," for the asking user, and the user decides each ",[422,3690,3691],{},"hunk"," (one operation on one Block of one Document) on its own.",[418,3694,3695,3696,3699,3700,464,3703,464,3706,3709,3710,3712,3713,3716,3717,464,3719,464,3721,3723,3724,3726,3727,3730,3731,2367,3733,3736],{},"A hunk is ",[461,3697,3698],{},"pending"," until it is decided, then ",[461,3701,3702],{},"accepted",[461,3704,3705],{},"rejected",[461,3707,3708],{},"stale"," (a block it names is gone or changed underneath, ",[461,3711,2748],{},") or ",[461,3714,3715],{},"invalid"," (refused otherwise: ",[461,3718,2252],{},[461,3720,2244],{},[461,3722,2752],{},", or a failure of the mutator that is no HTTP refusal, so the hunk is never applied blindly again). Accepting a hunk applies it through the ",[461,3725,532],{},", so the checks above decide. Accepting claims the named hunks atomically, so two requests for the same hunk cannot both apply it. A decided hunk never changes again, and the model gets the outcomes as feedback with the next message. A change set belongs to the user whose chat proposed it (",[461,3728,3729],{},"changeSetOwner(user)"," is the key: ",[461,3732,1415],{},[461,3734,3735],{},"username",", else the serialized user); only that user lists and decides it.",[431,3738,3739,3747],{},[434,3740,3741],{},[437,3742,3743,3745],{},[440,3744,2615],{},[440,3746,2618],{},[450,3748,3749,3759,3773],{},[437,3750,3751,3756],{},[455,3752,3753],{},[461,3754,3755],{},"GET \u002Fapi\u002Feditor\u002Fchange-sets",[455,3757,3758],{},"the user's change sets that still have a pending hunk, oldest first",[437,3760,3761,3766],{},[455,3762,3763],{},[461,3764,3765],{},"POST \u002Fapi\u002Feditor\u002Fchange-sets\u002F:id\u002Faccept",[455,3767,3768,3769,3772],{},"accepts the named hunks (",[461,3770,3771],{},"{ hunks: ['op3', ...] }",") and answers the change set",[437,3774,3775,3780],{},[455,3776,3777],{},[461,3778,3779],{},"POST \u002Fapi\u002Feditor\u002Fchange-sets\u002F:id\u002Freject",[455,3781,3782],{},"rejects the named hunks; nothing is applied",[418,3784,3785,3786,608,3789,3792,3793,3796,3797,3226,3800,3803,3804,3806,3807,3810,3811,3814],{},"In the client, ",[461,3787,3788],{},"createChangeSetModel({ backend: backend.changeSets })",[461,3790,3791],{},"@skaile\u002Fforge-editor\u002Fchange-sets",") is the model, and ",[461,3794,3795],{},"ChangeSetPanel"," renders it grouped by Document (",[461,3798,3799],{},"@skaile\u002Fforge-editor\u002Fchange-sets\u002Fvue",[461,3801,3802],{},"\u002Freact","). The same model goes to the chat as the ",[461,3805,572],{}," property of ",[461,3808,3809],{},"AiSidebar",", so the chat can hand over a new change set and read the decisions. Edits to the open Document stay with the in-document Review. The reference app wires it once, in ",[461,3812,3813],{},"app\u002Fcomposables\u002FuseChangeSets.ts",", and places the panel next to the search:",[671,3816,3818],{"className":2415,"code":3817,"filename":2417,"language":2418,"meta":677,"style":677},"          icon=\"i-lucide-blocks\"\n",[461,3819,3820],{"__ignoreMap":677},[681,3821,3822],{"class":683,"line":684},[681,3823,3817],{"class":698},[599,3825,3827],{"id":3826},"embeds","Embeds",[418,3829,420,3830,3832,3833,3836,3837,3840,3841,464,3843,464,3845,608,3848,3226,3851,3854,3855,3857,3858,3860,3861,3864,3865,3868,3869,3872,3873,464,3876,3878,3879,3882,3883,477,3886,844],{},[461,3831,579],{}," shows a Block of any Document, read-only, inside another Document. The Block has one ",[422,3834,3835],{},"home",", the Document where it is stored and edited, and each embed is one ",[422,3838,3839],{},"mount"," of it. The embed stores no content: it is an atom node with ",[461,3842,999],{},[461,3844,1011],{},[461,3846,3847],{},"mode",[461,3849,3850],{},"block",[461,3852,3853],{},"children",", the Blocks nested in it) and a ",[461,3856,2532],{},", and its view reads the home Block. In the open Document itself the read is live and needs no request. For any other Document it goes through ",[461,3859,2528],{}," with the Block's content, and the client ",[422,3862,3863],{},"polls",": the server pushes nothing, so each mounted embed asks again every ",[461,3866,3867],{},"FOREIGN_CONTENT_REFRESH_MS"," (4 seconds), and an edit of the home Block shows in the other Document after at most about that long. A reference chip to another Document fetches only the summary, again when it is drawn after ",[461,3870,3871],{},"FOREIGN_BLOCK_REFRESH_MS"," (15 seconds) has passed since the last answer. An embed shows one of five states instead of content: ",[461,3874,3875],{},"loading",[461,3877,2233],{}," (the Block or its Document does not exist), ",[461,3880,3881],{},"unavailable"," (no index, a Document the user may not read, a failed read), ",[461,3884,3885],{},"cycle",[461,3887,3888],{},"limit",[418,3890,3891,3892,3894,3895,3897],{},"The cycle guard follows the chain of Blocks an embed is rendered inside, outermost first. An embed whose home Block is already in that chain would render itself again, so it shows ",[461,3893,3885],{},". Embeds nest, so an embed can show a Block that holds embeds of its own, up to three levels deep and 24 mounted embeds under one outermost embed; past either bound an embed shows ",[461,3896,3888],{}," and neither reads nor follows its Block.",[418,3899,3900,3901,3904,3905,477,3908,3911,3912,803],{},"The Markdown form is on a line of its own, ids percent-encoded. Typing ",[461,3902,3903],{},"![["," opens a popup that inserts one. ",[461,3906,3907],{},"blockEmbedMarkdown",[461,3909,3910],{},"referenceMarkdown"," write the two address forms (",[461,3913,3914],{},"@skaile\u002Fforge-editor\u002Fcomposites",[671,3916,3919],{"className":673,"code":3917,"filename":3918,"language":676,"meta":677,"style":677},"import { blockEmbedMarkdown, referenceMarkdown } from '@skaile\u002Fforge-editor\u002Fcomposites'\nimport { compositeFeature } from '@skaile\u002Fforge-editor\u002Fcomposites\u002Fvue'\n\nconst address = { documentId: 'minutes-2026-10', blockId: '0d7c9a0e-3f1a-4a4e-9c52-1b8a6d3f2e11' }\n\n\u002F\u002F Both forms name a Block by its address (Document ID and block ID); the Document ID is percent-encoded.\nexport const embed = blockEmbedMarkdown(address)\n\u002F\u002F ![[minutes-2026-10#0d7c9a0e-3f1a-4a4e-9c52-1b8a6d3f2e11]]\nexport const embedChildren = blockEmbedMarkdown({ ...address, mode: 'children' })\n\u002F\u002F ![[minutes-2026-10#0d7c9a0e-3f1a-4a4e-9c52-1b8a6d3f2e11|children]]\nexport const chip = referenceMarkdown({ documentId: address.documentId, target: address.blockId, locator: 'S. 12' })\n\u002F\u002F [@minutes-2026-10#0d7c9a0e-3f1a-4a4e-9c52-1b8a6d3f2e11, S. 12]\n\n\u002F\u002F „Am Ursprung bearbeiten“ on an embed: the host opens the home Document with the Block revealed (`revealBlock`).\nexport const composites = compositeFeature({\n  openBlock: ({ documentId, blockId }) => { console.info(`open \u002Fdocuments\u002F${documentId}?block=${blockId}`) },\n})\n","docs\u002Fsnippets\u002Fworkspace-embed.ts",[461,3920,3921,3945,3965,3969,4008,4012,4017,4033,4038,4077,4082,4141,4146,4150,4155,4172,4227],{"__ignoreMap":677},[681,3922,3923,3925,3927,3930,3932,3935,3937,3939,3941,3943],{"class":683,"line":684},[681,3924,688],{"class":687},[681,3926,695],{"class":694},[681,3928,3929],{"class":698}," blockEmbedMarkdown",[681,3931,730],{"class":694},[681,3933,3934],{"class":698}," referenceMarkdown",[681,3936,702],{"class":694},[681,3938,705],{"class":687},[681,3940,708],{"class":694},[681,3942,3914],{"class":711},[681,3944,715],{"class":694},[681,3946,3947,3949,3951,3954,3956,3958,3960,3963],{"class":683,"line":718},[681,3948,688],{"class":687},[681,3950,695],{"class":694},[681,3952,3953],{"class":698}," compositeFeature",[681,3955,702],{"class":694},[681,3957,705],{"class":687},[681,3959,708],{"class":694},[681,3961,3962],{"class":711},"@skaile\u002Fforge-editor\u002Fcomposites\u002Fvue",[681,3964,715],{"class":694},[681,3966,3967],{"class":683,"line":746},[681,3968,750],{"emptyLinePlaceholder":749},[681,3970,3971,3974,3977,3979,3981,3983,3985,3987,3990,3992,3994,3996,3998,4000,4003,4005],{"class":683,"line":753},[681,3972,3973],{"class":772},"const",[681,3975,3976],{"class":698}," address ",[681,3978,3585],{"class":694},[681,3980,695],{"class":694},[681,3982,795],{"class":832},[681,3984,787],{"class":694},[681,3986,708],{"class":694},[681,3988,3989],{"class":711},"minutes-2026-10",[681,3991,811],{"class":694},[681,3993,730],{"class":694},[681,3995,979],{"class":832},[681,3997,787],{"class":694},[681,3999,708],{"class":694},[681,4001,4002],{"class":711},"0d7c9a0e-3f1a-4a4e-9c52-1b8a6d3f2e11",[681,4004,811],{"class":694},[681,4006,4007],{"class":694}," }\n",[681,4009,4010],{"class":683,"line":760},[681,4011,750],{"emptyLinePlaceholder":749},[681,4013,4014],{"class":683,"line":766},[681,4015,4016],{"class":756},"\u002F\u002F Both forms name a Block by its address (Document ID and block ID); the Document ID is percent-encoded.\n",[681,4018,4019,4021,4023,4026,4028,4030],{"class":683,"line":824},[681,4020,769],{"class":687},[681,4022,3573],{"class":772},[681,4024,4025],{"class":698}," embed ",[681,4027,3585],{"class":694},[681,4029,3929],{"class":776},[681,4031,4032],{"class":698},"(address)\n",[681,4034,4035],{"class":683,"line":868},[681,4036,4037],{"class":756},"\u002F\u002F ![[minutes-2026-10#0d7c9a0e-3f1a-4a4e-9c52-1b8a6d3f2e11]]\n",[681,4039,4040,4042,4044,4047,4049,4051,4053,4055,4058,4060,4062,4065,4067,4069,4071,4073,4075],{"class":683,"line":874},[681,4041,769],{"class":687},[681,4043,3573],{"class":772},[681,4045,4046],{"class":698}," embedChildren ",[681,4048,3585],{"class":694},[681,4050,3929],{"class":776},[681,4052,780],{"class":698},[681,4054,1316],{"class":694},[681,4056,4057],{"class":694}," ...",[681,4059,1979],{"class":698},[681,4061,730],{"class":694},[681,4063,4064],{"class":832}," mode",[681,4066,787],{"class":694},[681,4068,708],{"class":694},[681,4070,3853],{"class":711},[681,4072,811],{"class":694},[681,4074,702],{"class":694},[681,4076,865],{"class":698},[681,4078,4079],{"class":683,"line":879},[681,4080,4081],{"class":756},"\u002F\u002F ![[minutes-2026-10#0d7c9a0e-3f1a-4a4e-9c52-1b8a6d3f2e11|children]]\n",[681,4083,4084,4086,4088,4091,4093,4095,4097,4099,4101,4103,4106,4108,4110,4112,4115,4117,4119,4121,4123,4125,4128,4130,4132,4135,4137,4139],{"class":683,"line":885},[681,4085,769],{"class":687},[681,4087,3573],{"class":772},[681,4089,4090],{"class":698}," chip ",[681,4092,3585],{"class":694},[681,4094,3934],{"class":776},[681,4096,780],{"class":698},[681,4098,1316],{"class":694},[681,4100,795],{"class":832},[681,4102,787],{"class":694},[681,4104,4105],{"class":698}," address",[681,4107,844],{"class":694},[681,4109,999],{"class":698},[681,4111,730],{"class":694},[681,4113,4114],{"class":832}," target",[681,4116,787],{"class":694},[681,4118,4105],{"class":698},[681,4120,844],{"class":694},[681,4122,1011],{"class":698},[681,4124,730],{"class":694},[681,4126,4127],{"class":832}," locator",[681,4129,787],{"class":694},[681,4131,708],{"class":694},[681,4133,4134],{"class":711},"S. 12",[681,4136,811],{"class":694},[681,4138,702],{"class":694},[681,4140,865],{"class":698},[681,4142,4143],{"class":683,"line":920},[681,4144,4145],{"class":756},"\u002F\u002F [@minutes-2026-10#0d7c9a0e-3f1a-4a4e-9c52-1b8a6d3f2e11, S. 12]\n",[681,4147,4148],{"class":683,"line":937},[681,4149,750],{"emptyLinePlaceholder":749},[681,4151,4152],{"class":683,"line":966},[681,4153,4154],{"class":756},"\u002F\u002F „Am Ursprung bearbeiten“ on an embed: the host opens the home Document with the Block revealed (`revealBlock`).\n",[681,4156,4157,4159,4161,4164,4166,4168,4170],{"class":683,"line":1036},[681,4158,769],{"class":687},[681,4160,3573],{"class":772},[681,4162,4163],{"class":698}," composites ",[681,4165,3585],{"class":694},[681,4167,3953],{"class":776},[681,4169,780],{"class":698},[681,4171,821],{"class":694},[681,4173,4174,4177,4179,4182,4184,4186,4188,4190,4192,4194,4197,4199,4201,4203,4205,4208,4210,4212,4214,4217,4219,4221,4223,4225],{"class":683,"line":1383},[681,4175,4176],{"class":776},"  openBlock",[681,4178,787],{"class":694},[681,4180,4181],{"class":694}," ({",[681,4183,795],{"class":783},[681,4185,730],{"class":694},[681,4187,979],{"class":783},[681,4189,991],{"class":694},[681,4191,855],{"class":772},[681,4193,695],{"class":694},[681,4195,4196],{"class":698}," console",[681,4198,844],{"class":694},[681,4200,3479],{"class":776},[681,4202,780],{"class":832},[681,4204,3484],{"class":694},[681,4206,4207],{"class":711},"open \u002Fdocuments\u002F",[681,4209,1008],{"class":694},[681,4211,999],{"class":698},[681,4213,1002],{"class":694},[681,4215,4216],{"class":711},"?block=",[681,4218,1008],{"class":694},[681,4220,1011],{"class":698},[681,4222,1031],{"class":694},[681,4224,1418],{"class":832},[681,4226,1469],{"class":694},[681,4228,4229,4231],{"class":683,"line":1393},[681,4230,1002],{"class":694},[681,4232,865],{"class":698},[418,4234,4235,4236,3223,4239,4242,4243,4246,4247,4250],{},"To edit an embedded Block, the user edits it at its home. ",[461,4237,4238],{},"openBlock",[461,4240,4241],{},"compositeFeature"," receives the address when the user picks „Am Ursprung bearbeiten“ on an embed; without it the embed has no such action. The host decides what that means. The reference app reveals the Block when its Document is the open one and otherwise opens the Document with ",[461,4244,4245],{},"?block=\u003Cid>",", whose page reveals the Block (",[461,4248,4249],{},"revealBlock",") once the editor has synced:",[671,4252,4255],{"className":2415,"code":4253,"filename":4254,"language":2418,"meta":677,"style":677},"\n\u002F\u002F „Am Ursprung bearbeiten“ on an embedded block: the open document reveals the block in place, any other opens its document with\n\u002F\u002F `?block=` (the reveal above).\nfunction openEmbedSource({ documentId, blockId }: BlockAddress) {\n  const instance = editor.value\n","app\u002Fpages\u002Fdocuments\u002F[id].vue",[461,4256,4257,4261,4266,4271,4276],{"__ignoreMap":677},[681,4258,4259],{"class":683,"line":684},[681,4260,750],{"emptyLinePlaceholder":749},[681,4262,4263],{"class":683,"line":718},[681,4264,4265],{"class":698},"\u002F\u002F „Am Ursprung bearbeiten“ on an embedded block: the open document reveals the block in place, any other opens its document with\n",[681,4267,4268],{"class":683,"line":746},[681,4269,4270],{"class":698},"\u002F\u002F `?block=` (the reveal above).\n",[681,4272,4273],{"class":683,"line":753},[681,4274,4275],{"class":698},"function openEmbedSource({ documentId, blockId }: BlockAddress) {\n",[681,4277,4278],{"class":683,"line":760},[681,4279,4280],{"class":698},"  const instance = editor.value\n",[599,4282,4284],{"id":4283},"what-the-host-supplies","What the host supplies",[418,4286,4287,4288,477,4290,1045,4292,4294,4295,464,4298,464,4300,4302,4303,464,4306,477,4309,844],{},"The library owns the ports, the routes, the panels' models, the references, the Embed and the tools. The host chooses the storage behind ",[461,4289,540],{},[461,4291,485],{},[461,4293,572],{},"), the access rule (",[461,4296,4297],{},"authenticate",[461,4299,2350],{},[461,4301,1117],{},"), where a hit or an Embed's source opens, and which Block types and tools an editor gets. See also the guides on the ",[1758,4304,4305],{"href":196},"editor ports",[1758,4307,4308],{"href":146},"storage adapters",[1758,4310,113],{"href":114},[4312,4313,4314],"style",{},"html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":677,"searchDepth":718,"depth":718,"links":4316},[4317,4320,4321,4322,4323,4324,4325,4326,4327],{"id":601,"depth":718,"text":602,"children":4318},[4319],{"id":1642,"depth":746,"text":1643},{"id":2215,"depth":718,"text":2216},{"id":2276,"depth":718,"text":502},{"id":2514,"depth":718,"text":2515},{"id":2582,"depth":718,"text":2583},{"id":3213,"depth":718,"text":545},{"id":3655,"depth":718,"text":3656},{"id":3826,"depth":718,"text":3827},{"id":4283,"depth":718,"text":4284},"The Block index, Workspace search, Block references and Embeds that name a Block address, Document routes with the DocumentMutator, Tool providers for the document chat, and change sets for edits to other Documents.",null,{},{"icon":193},{"title":190,"description":4328},"kAMWvMNReu6ss-YQm2XcYs-8PYGykdUhLNus46vAv60",[4335,4337],{"title":185,"path":186,"stem":187,"description":4336,"icon":188,"children":-1},"Record that a user checked a Typed block at one content state, derive the Freigabestatus, optionally lock checked blocks, and add your own chips and inspector sections.",{"title":195,"path":196,"stem":197,"description":4338,"icon":198,"children":-1},"The single seam between the package and the host: ports for identity, documents, Yjs state, assets, settings, themes and AI models.",1793219827617]