[{"data":1,"prerenderedAt":2529},["ShallowReactive",2],{"navigation_docs":3,"-tutorial-backend-and-collaboration":412,"-tutorial-backend-and-collaboration-surround":2524},[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":70,"body":414,"description":2517,"extension":2518,"links":2519,"meta":2520,"navigation":2521,"path":71,"seo":2522,"stem":72,"__hash__":2523},"docs\u002F2.tutorial\u002F6.backend-and-collaboration.md",{"type":415,"value":416,"toc":2508},"minimark",[417,433,464,471,475,490,698,712,718,744,2045,2073,2077,2096,2180,2187,2250,2268,2358,2368,2372,2381,2475,2484,2488,2491,2495,2504],[418,419,420,421,425,426,432],"p",{},"Until now the editor kept its data in the page. Meeting notes belong to a team, so this chapter moves them behind a server and lets several people edit one document. Two pieces cooperate: the ",[422,423,424],"strong",{},"Nuxt module"," (routes, Hocuspocus websocket server, rate limiting) and the ",[422,427,428],{},[429,430,431],"code",{},"EditorPorts"," you give it (who the user is, where documents live). On the client, collaboration is a feature, not a mode.",[418,434,435,436,439,440,443,444,447,448,451,452,455,456,459,460,463],{},"This chapter has no live demo: it needs a server. The repository's Nuxt app is the runnable reference. Start it with ",[429,437,438],{},"bun run dev"," (port 3056); it needs ",[429,441,442],{},"NUXT_SESSION_PASSWORD"," (at least 32 characters, for the session cookie) in ",[429,445,446],{},".env",". The app has no login: a visitor acts as one of the demo users ",[429,449,450],{},"anna",", ",[429,453,454],{},"ben"," and ",[429,457,458],{},"bernd"," and switches in the user menu top right. Open ",[429,461,462],{},"\u002Fkitchen-sink\u002Fadvanced"," for the advanced editor on a sample document:",[418,465,466],{},[467,468],"img",{"alt":469,"src":470},"The advanced editor of the repository's Nuxt app: toolbar, Block outline groups, Typed blocks and the JSON and Markdown panes","\u002Fimg\u002Ftutorial-advanced.png",[472,473,140],"h2",{"id":474},"register-the-nuxt-module",[418,476,477,478,481,482,485,486,489],{},"The module goes into ",[429,479,480],{},"nuxt.config.ts"," after ",[429,483,484],{},"@nuxt\u002Fui",", because it patches Nuxt UI's generated stylesheet. The repository's own config registers it by path, so that editing the library needs no build step; in your app the entry is ",[429,487,488],{},"@skaile\u002Fforge-editor\u002Fnuxt",":",[491,492,497],"pre",{"className":493,"code":494,"filename":480,"language":495,"meta":496,"style":496},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","  extends: ['nuxt-layer-annotation-inbox'],\n  compatibilityDate: '2026-09-01',\n  devtools: { enabled: true },\n  devServer: { port: 3056 }, \u002F\u002F assigned by dev-server skill (~\u002F.skills\u002Fdevskill\u002Fports.tsv)\n  ssr: false, \u002F\u002F TipTap is client-only\n  \u002F\u002F Nuxt does not honour custom export conditions when it resolves a module name, so the source module is loaded by path.\n  modules: ['@nuxt\u002Fui', '@skaile\u002Fforge-chat\u002Fnuxt', 'nuxt-auth-utils', '.\u002Fpackages\u002Feditor\u002Fsrc\u002Fnuxt\u002Fmodule.ts'],\n  ui: { fonts: false },\n  \u002F\u002F `EDITOR_LOCALE=en` renders the editor chrome in English (default: German). Speech-to-text (“Create readable rendition” on\n  \u002F\u002F audio\u002Fvideo) runs on Deepgram only when `NUXT_TIPTAP_EDITOR_AI_TRANSCRIPTION_API_KEY` is set (the key itself is read at runtime).\n","ts","",[429,498,499,530,548,571,597,614,620,667,686,692],{"__ignoreMap":496},[500,501,504,508,511,515,518,522,524,527],"span",{"class":502,"line":503},"line",1,[500,505,507],{"class":506},"sBMFI","  extends",[500,509,489],{"class":510},"sMK4o",[500,512,514],{"class":513},"sTEyZ"," [",[500,516,517],{"class":510},"'",[500,519,521],{"class":520},"sfazB","nuxt-layer-annotation-inbox",[500,523,517],{"class":510},[500,525,526],{"class":513},"]",[500,528,529],{"class":510},",\n",[500,531,533,536,538,541,544,546],{"class":502,"line":532},2,[500,534,535],{"class":506},"  compatibilityDate",[500,537,489],{"class":510},[500,539,540],{"class":510}," '",[500,542,543],{"class":520},"2026-09-01",[500,545,517],{"class":510},[500,547,529],{"class":510},[500,549,551,554,556,559,562,564,568],{"class":502,"line":550},3,[500,552,553],{"class":506},"  devtools",[500,555,489],{"class":510},[500,557,558],{"class":510}," {",[500,560,561],{"class":506}," enabled",[500,563,489],{"class":510},[500,565,567],{"class":566},"sfNiH"," true",[500,569,570],{"class":510}," },\n",[500,572,574,577,579,581,584,586,590,593],{"class":502,"line":573},4,[500,575,576],{"class":506},"  devServer",[500,578,489],{"class":510},[500,580,558],{"class":510},[500,582,583],{"class":506}," port",[500,585,489],{"class":510},[500,587,589],{"class":588},"sbssI"," 3056",[500,591,592],{"class":510}," },",[500,594,596],{"class":595},"sHwdD"," \u002F\u002F assigned by dev-server skill (~\u002F.skills\u002Fdevskill\u002Fports.tsv)\n",[500,598,600,603,605,608,611],{"class":502,"line":599},5,[500,601,602],{"class":506},"  ssr",[500,604,489],{"class":510},[500,606,607],{"class":566}," false",[500,609,610],{"class":510},",",[500,612,613],{"class":595}," \u002F\u002F TipTap is client-only\n",[500,615,617],{"class":502,"line":616},6,[500,618,619],{"class":595},"  \u002F\u002F Nuxt does not honour custom export conditions when it resolves a module name, so the source module is loaded by path.\n",[500,621,623,626,628,630,632,634,636,638,640,643,645,647,649,652,654,656,658,661,663,665],{"class":502,"line":622},7,[500,624,625],{"class":506},"  modules",[500,627,489],{"class":510},[500,629,514],{"class":513},[500,631,517],{"class":510},[500,633,484],{"class":520},[500,635,517],{"class":510},[500,637,610],{"class":510},[500,639,540],{"class":510},[500,641,642],{"class":520},"@skaile\u002Fforge-chat\u002Fnuxt",[500,644,517],{"class":510},[500,646,610],{"class":510},[500,648,540],{"class":510},[500,650,651],{"class":520},"nuxt-auth-utils",[500,653,517],{"class":510},[500,655,610],{"class":510},[500,657,540],{"class":510},[500,659,660],{"class":520},".\u002Fpackages\u002Feditor\u002Fsrc\u002Fnuxt\u002Fmodule.ts",[500,662,517],{"class":510},[500,664,526],{"class":513},[500,666,529],{"class":510},[500,668,670,673,675,677,680,682,684],{"class":502,"line":669},8,[500,671,672],{"class":506},"  ui",[500,674,489],{"class":510},[500,676,558],{"class":510},[500,678,679],{"class":506}," fonts",[500,681,489],{"class":510},[500,683,607],{"class":566},[500,685,570],{"class":510},[500,687,689],{"class":502,"line":688},9,[500,690,691],{"class":595},"  \u002F\u002F `EDITOR_LOCALE=en` renders the editor chrome in English (default: German). Speech-to-text (“Create readable rendition” on\n",[500,693,695],{"class":502,"line":694},10,[500,696,697],{"class":595},"  \u002F\u002F audio\u002Fvideo) runs on Deepgram only when `NUXT_TIPTAP_EDITOR_AI_TRANSCRIPTION_API_KEY` is set (the key itself is read at runtime).\n",[418,699,700,701,704,705,708,709,711],{},"The module registers one client plugin, which installs the editor context (",[429,702,703],{},"createEditor",") with account-synced settings and the public AI configuration; the context then talks to the module's HTTP routes. It auto-registers no component or composable: import everything from the package entries. ",[706,707,424],"a",{"href":141}," has the details, and ",[706,710,21],{"href":22}," has the full setup including the minimal in-memory ports.",[472,713,715,716],{"id":714},"give-it-editorports","Give it ",[429,717,431],{},[418,719,720,721,724,725,728,729,731,732,735,736,739,740,743],{},"The package owns the routes, the websocket server, rate limiting and the wire formats. ",[422,722,723],{},"You own identity"," (sessions, sign-in, which user may open which document) ",[422,726,727],{},"and storage"," (where documents, Yjs state, assets, settings and themes live). Both enter through one object, ",[429,730,431],{},", which you provide in a Nitro plugin with ",[429,733,734],{},"provideEditorServer({ ports })",". The package never reads cookies or touches files itself: ",[429,737,738],{},"authenticate"," returns the user or ",[429,741,742],{},"null",", and the storage methods persist what they are given. The repository's plugin wires the ports to session cookies and the file system:",[491,745,748],{"className":493,"code":746,"filename":747,"language":495,"meta":496,"style":496},"import type { User } from '#auth-utils'\nimport { createJsonAdapter } from '@skaile\u002Fforge-editor\u002Fadapters\u002Fjson'\nimport { provideEditorServer } from '@skaile\u002Fforge-editor\u002Fserver\u002Fnitro'\nimport { createWorkspaceTools } from '@skaile\u002Fforge-editor\u002Fserver'\n\n\u002F\u002F Connects the editor package's Nuxt integration to this app's sessions, JSON document store and per-user settings.\n\u002F\u002F Synchronous on purpose: Nitro does not await a plugin, so registering after an `await` would leave the first requests without an editor server.\nexport default defineNitroPlugin(() => {\n  \u002F\u002F The add-ons' ports (render targets, speech, knowledge graph) and Block types; each add-on is on behind its flag, `runtimeConfig.public.addons.\u003Cname>` (`server\u002Futils\u002Faddons.ts`).\n  const addons = createAddonServer({ storeAsset, loadAsset }, addonFlagsOf(useRuntimeConfig().public.addons))\n  provideEditorServer({\n    ports: {\n      identity: {\n        \u002F\u002F Without a session cookie h3 throws (\"Cannot initialize a new session\") outside a main handler.\n        authenticate: request => getUserSession({ headers: request.headers, context: {} }).then(s => s.user ?? null, () => null),\n      },\n      documents: {\n        loadContent: async id => (await getDocument(id))?.content ?? null,\n        async storeContent(id, content) {\n          const existing = await getDocument(id)\n          if (!existing) return \u002F\u002F deleted while a store was pending\n          await putDocument({ ...existing, content, updatedAt: new Date().toISOString() })\n        },\n        listContents: async () => (await listAllDocuments()).map(({ id, content }) => ({ id, content })),\n        \u002F\u002F Document management over the package store: folders, create, move, delete (`\u002Fapi\u002Feditor\u002F{documents,tree}`).\n        listDocumentIds: async () => indexedIds(),\n        listTree,\n        async createDocument(input) {\n          const { id, title, updatedAt } = await createDocument(input)\n          return { id, title, updatedAt, folder: input.folder ?? '' }\n        },\n        moveDocument,\n        deleteDocument: removeDocument,\n      },\n      directory: { listUsers: async () => (await listUsers()).map(toSessionUser) },\n      collabState: { loadState, storeState, removeState },\n      assets: { storeAsset, loadAsset },\n      \u002F\u002F `user` is what `authenticate` returned: the session user.\n      settings: {\n        loadSettings: user => getUserSettings((user as User).id),\n        storeSettings: (user, settings) => putUserSettings((user as User).id, settings),\n      },\n      themes: { listThemes, loadTheme, storeTheme, removeTheme },\n      \u002F\u002F Block index (`index` capability): a projection of the documents above, rebuilt on start and kept current by the editor server; in memory (memory adapter, PGlite with `NUXT_BLOCK_INDEX=pglite`, or PGlite with trigram search and vectors with `NUXT_BLOCK_INDEX=pg-vector`), so nothing to migrate.\n      index: createHostBlockIndex(addons.ports.embeddings, addons.embeddingsError),\n      \u002F\u002F Prüfung records (`pruefung` capability): the editor's JSON store on `\u003Cdocuments dir>\u002F.pruefung.json`, a dot-named file the document store never lists.\n      pruefung: createJsonAdapter({ dir: storeRoot() }).pruefung,\n      \u002F\u002F Change sets of the document chat (`changeSets` capability): what the model proposed for other Documents, decided per Document; `\u003Cdocuments dir>\u002F.change-sets.json`, likewise dot-named.\n      changeSets: createJsonAdapter({ dir: storeRoot() }).changeSets,\n      \u002F\u002F Demo of the model port (`DEMO_STUB_MODEL=1`): the stub answers `{ preset: 'stub' }`; `null` for anything else falls through to the environment presets.\n      models: {\n        languageModel: async ref => stubModelEnabled() && 'preset' in ref && ref.preset === STUB_MODEL_ID ? stubLanguageModel : null,\n        listModels: async () => stubModelEnabled() ? [...publicEnvPresets(), stubModelProfile] : null,\n      },\n      ...addons.ports,\n    },\n    ...(addons.blockTypes && { blockTypes: addons.blockTypes }),\n    \u002F\u002F The document chat's read-only workspace tools (`find_blocks`, `read_block`): the model finds and reads Blocks of the user's other Documents through the block index above.\n    tools: createWorkspaceTools(addons.blockTypes && { blockTypes: addons.blockTypes }),\n  })\n})\n","server\u002Fplugins\u002Feditor-adapter.ts",[429,749,750,778,798,818,838,844,849,854,879,884,938,949,959,969,975,1061,1067,1077,1121,1143,1166,1189,1239,1245,1306,1312,1333,1341,1358,1390,1431,1436,1444,1457,1462,1505,1530,1548,1554,1564,1600,1646,1651,1681,1687,1725,1731,1766,1772,1805,1811,1821,1882,1925,1930,1944,1950,1986,1992,2030,2038],{"__ignoreMap":496},[500,751,752,756,759,761,764,767,770,772,775],{"class":502,"line":503},[500,753,755],{"class":754},"s7zQu","import",[500,757,758],{"class":754}," type",[500,760,558],{"class":510},[500,762,763],{"class":513}," User",[500,765,766],{"class":510}," }",[500,768,769],{"class":754}," from",[500,771,540],{"class":510},[500,773,774],{"class":520},"#auth-utils",[500,776,777],{"class":510},"'\n",[500,779,780,782,784,787,789,791,793,796],{"class":502,"line":532},[500,781,755],{"class":754},[500,783,558],{"class":510},[500,785,786],{"class":513}," createJsonAdapter",[500,788,766],{"class":510},[500,790,769],{"class":754},[500,792,540],{"class":510},[500,794,795],{"class":520},"@skaile\u002Fforge-editor\u002Fadapters\u002Fjson",[500,797,777],{"class":510},[500,799,800,802,804,807,809,811,813,816],{"class":502,"line":550},[500,801,755],{"class":754},[500,803,558],{"class":510},[500,805,806],{"class":513}," provideEditorServer",[500,808,766],{"class":510},[500,810,769],{"class":754},[500,812,540],{"class":510},[500,814,815],{"class":520},"@skaile\u002Fforge-editor\u002Fserver\u002Fnitro",[500,817,777],{"class":510},[500,819,820,822,824,827,829,831,833,836],{"class":502,"line":573},[500,821,755],{"class":754},[500,823,558],{"class":510},[500,825,826],{"class":513}," createWorkspaceTools",[500,828,766],{"class":510},[500,830,769],{"class":754},[500,832,540],{"class":510},[500,834,835],{"class":520},"@skaile\u002Fforge-editor\u002Fserver",[500,837,777],{"class":510},[500,839,840],{"class":502,"line":599},[500,841,843],{"emptyLinePlaceholder":842},true,"\n",[500,845,846],{"class":502,"line":616},[500,847,848],{"class":595},"\u002F\u002F Connects the editor package's Nuxt integration to this app's sessions, JSON document store and per-user settings.\n",[500,850,851],{"class":502,"line":622},[500,852,853],{"class":595},"\u002F\u002F Synchronous on purpose: Nitro does not await a plugin, so registering after an `await` would leave the first requests without an editor server.\n",[500,855,856,859,862,866,869,872,876],{"class":502,"line":669},[500,857,858],{"class":754},"export",[500,860,861],{"class":754}," default",[500,863,865],{"class":864},"s2Zo4"," defineNitroPlugin",[500,867,868],{"class":513},"(",[500,870,871],{"class":510},"()",[500,873,875],{"class":874},"spNyl"," =>",[500,877,878],{"class":510}," {\n",[500,880,881],{"class":502,"line":688},[500,882,883],{"class":595},"  \u002F\u002F The add-ons' ports (render targets, speech, knowledge graph) and Block types; each add-on is on behind its flag, `runtimeConfig.public.addons.\u003Cname>` (`server\u002Futils\u002Faddons.ts`).\n",[500,885,886,889,892,895,898,901,904,907,909,912,914,917,919,922,924,927,930,932,935],{"class":502,"line":694},[500,887,888],{"class":874},"  const",[500,890,891],{"class":513}," addons",[500,893,894],{"class":510}," =",[500,896,897],{"class":864}," createAddonServer",[500,899,868],{"class":900},"swJcz",[500,902,903],{"class":510},"{",[500,905,906],{"class":513}," storeAsset",[500,908,610],{"class":510},[500,910,911],{"class":513}," loadAsset",[500,913,592],{"class":510},[500,915,916],{"class":864}," addonFlagsOf",[500,918,868],{"class":900},[500,920,921],{"class":864},"useRuntimeConfig",[500,923,871],{"class":900},[500,925,926],{"class":510},".",[500,928,929],{"class":513},"public",[500,931,926],{"class":510},[500,933,934],{"class":513},"addons",[500,936,937],{"class":900},"))\n",[500,939,941,944,946],{"class":502,"line":940},11,[500,942,943],{"class":864},"  provideEditorServer",[500,945,868],{"class":900},[500,947,948],{"class":510},"{\n",[500,950,952,955,957],{"class":502,"line":951},12,[500,953,954],{"class":900},"    ports",[500,956,489],{"class":510},[500,958,878],{"class":510},[500,960,962,965,967],{"class":502,"line":961},13,[500,963,964],{"class":900},"      identity",[500,966,489],{"class":510},[500,968,878],{"class":510},[500,970,972],{"class":502,"line":971},14,[500,973,974],{"class":595},"        \u002F\u002F Without a session cookie h3 throws (\"Cannot initialize a new session\") outside a main handler.\n",[500,976,978,981,983,987,989,992,994,996,999,1001,1003,1005,1008,1010,1013,1015,1018,1020,1023,1025,1028,1030,1033,1035,1038,1040,1043,1046,1049,1052,1054,1057,1059],{"class":502,"line":977},15,[500,979,980],{"class":864},"        authenticate",[500,982,489],{"class":510},[500,984,986],{"class":985},"sHdIc"," request",[500,988,875],{"class":874},[500,990,991],{"class":864}," getUserSession",[500,993,868],{"class":900},[500,995,903],{"class":510},[500,997,998],{"class":900}," headers",[500,1000,489],{"class":510},[500,1002,986],{"class":513},[500,1004,926],{"class":510},[500,1006,1007],{"class":513},"headers",[500,1009,610],{"class":510},[500,1011,1012],{"class":900}," context",[500,1014,489],{"class":510},[500,1016,1017],{"class":510}," {}",[500,1019,766],{"class":510},[500,1021,1022],{"class":900},")",[500,1024,926],{"class":510},[500,1026,1027],{"class":864},"then",[500,1029,868],{"class":900},[500,1031,1032],{"class":985},"s",[500,1034,875],{"class":874},[500,1036,1037],{"class":513}," s",[500,1039,926],{"class":510},[500,1041,1042],{"class":513},"user",[500,1044,1045],{"class":510}," ??",[500,1047,1048],{"class":510}," null,",[500,1050,1051],{"class":510}," ()",[500,1053,875],{"class":874},[500,1055,1056],{"class":510}," null",[500,1058,1022],{"class":900},[500,1060,529],{"class":510},[500,1062,1064],{"class":502,"line":1063},16,[500,1065,1066],{"class":510},"      },\n",[500,1068,1070,1073,1075],{"class":502,"line":1069},17,[500,1071,1072],{"class":900},"      documents",[500,1074,489],{"class":510},[500,1076,878],{"class":510},[500,1078,1080,1083,1085,1088,1091,1093,1096,1099,1102,1104,1107,1110,1113,1116,1118],{"class":502,"line":1079},18,[500,1081,1082],{"class":864},"        loadContent",[500,1084,489],{"class":510},[500,1086,1087],{"class":874}," async",[500,1089,1090],{"class":985}," id",[500,1092,875],{"class":874},[500,1094,1095],{"class":900}," (",[500,1097,1098],{"class":754},"await",[500,1100,1101],{"class":864}," getDocument",[500,1103,868],{"class":900},[500,1105,1106],{"class":513},"id",[500,1108,1109],{"class":900},"))",[500,1111,1112],{"class":510},"?.",[500,1114,1115],{"class":513},"content",[500,1117,1045],{"class":510},[500,1119,1120],{"class":510}," null,\n",[500,1122,1124,1127,1130,1132,1134,1136,1139,1141],{"class":502,"line":1123},19,[500,1125,1126],{"class":874},"        async",[500,1128,1129],{"class":900}," storeContent",[500,1131,868],{"class":510},[500,1133,1106],{"class":985},[500,1135,610],{"class":510},[500,1137,1138],{"class":985}," content",[500,1140,1022],{"class":510},[500,1142,878],{"class":510},[500,1144,1146,1149,1152,1154,1157,1159,1161,1163],{"class":502,"line":1145},20,[500,1147,1148],{"class":874},"          const",[500,1150,1151],{"class":513}," existing",[500,1153,894],{"class":510},[500,1155,1156],{"class":754}," await",[500,1158,1101],{"class":864},[500,1160,868],{"class":900},[500,1162,1106],{"class":513},[500,1164,1165],{"class":900},")\n",[500,1167,1169,1172,1174,1177,1180,1183,1186],{"class":502,"line":1168},21,[500,1170,1171],{"class":754},"          if",[500,1173,1095],{"class":900},[500,1175,1176],{"class":510},"!",[500,1178,1179],{"class":513},"existing",[500,1181,1182],{"class":900},") ",[500,1184,1185],{"class":754},"return",[500,1187,1188],{"class":595}," \u002F\u002F deleted while a store was pending\n",[500,1190,1192,1195,1198,1200,1202,1205,1207,1209,1211,1213,1216,1218,1221,1224,1226,1228,1231,1234,1237],{"class":502,"line":1191},22,[500,1193,1194],{"class":754},"          await",[500,1196,1197],{"class":864}," putDocument",[500,1199,868],{"class":900},[500,1201,903],{"class":510},[500,1203,1204],{"class":510}," ...",[500,1206,1179],{"class":513},[500,1208,610],{"class":510},[500,1210,1138],{"class":513},[500,1212,610],{"class":510},[500,1214,1215],{"class":900}," updatedAt",[500,1217,489],{"class":510},[500,1219,1220],{"class":510}," new",[500,1222,1223],{"class":864}," Date",[500,1225,871],{"class":900},[500,1227,926],{"class":510},[500,1229,1230],{"class":864},"toISOString",[500,1232,1233],{"class":900},"() ",[500,1235,1236],{"class":510},"}",[500,1238,1165],{"class":900},[500,1240,1242],{"class":502,"line":1241},23,[500,1243,1244],{"class":510},"        },\n",[500,1246,1248,1251,1253,1255,1257,1259,1261,1263,1266,1269,1271,1274,1276,1279,1281,1283,1285,1288,1290,1292,1294,1296,1298,1300,1302,1304],{"class":502,"line":1247},24,[500,1249,1250],{"class":864},"        listContents",[500,1252,489],{"class":510},[500,1254,1087],{"class":874},[500,1256,1051],{"class":510},[500,1258,875],{"class":874},[500,1260,1095],{"class":900},[500,1262,1098],{"class":754},[500,1264,1265],{"class":864}," listAllDocuments",[500,1267,1268],{"class":900},"())",[500,1270,926],{"class":510},[500,1272,1273],{"class":864},"map",[500,1275,868],{"class":900},[500,1277,1278],{"class":510},"({",[500,1280,1090],{"class":985},[500,1282,610],{"class":510},[500,1284,1138],{"class":985},[500,1286,1287],{"class":510}," })",[500,1289,875],{"class":874},[500,1291,1095],{"class":900},[500,1293,903],{"class":510},[500,1295,1090],{"class":513},[500,1297,610],{"class":510},[500,1299,1138],{"class":513},[500,1301,766],{"class":510},[500,1303,1109],{"class":900},[500,1305,529],{"class":510},[500,1307,1309],{"class":502,"line":1308},25,[500,1310,1311],{"class":595},"        \u002F\u002F Document management over the package store: folders, create, move, delete (`\u002Fapi\u002Feditor\u002F{documents,tree}`).\n",[500,1313,1315,1318,1320,1322,1324,1326,1329,1331],{"class":502,"line":1314},26,[500,1316,1317],{"class":864},"        listDocumentIds",[500,1319,489],{"class":510},[500,1321,1087],{"class":874},[500,1323,1051],{"class":510},[500,1325,875],{"class":874},[500,1327,1328],{"class":864}," indexedIds",[500,1330,871],{"class":900},[500,1332,529],{"class":510},[500,1334,1336,1339],{"class":502,"line":1335},27,[500,1337,1338],{"class":513},"        listTree",[500,1340,529],{"class":510},[500,1342,1344,1346,1349,1351,1354,1356],{"class":502,"line":1343},28,[500,1345,1126],{"class":874},[500,1347,1348],{"class":900}," createDocument",[500,1350,868],{"class":510},[500,1352,1353],{"class":985},"input",[500,1355,1022],{"class":510},[500,1357,878],{"class":510},[500,1359,1361,1363,1365,1367,1369,1372,1374,1376,1378,1380,1382,1384,1386,1388],{"class":502,"line":1360},29,[500,1362,1148],{"class":874},[500,1364,558],{"class":510},[500,1366,1090],{"class":513},[500,1368,610],{"class":510},[500,1370,1371],{"class":513}," title",[500,1373,610],{"class":510},[500,1375,1215],{"class":513},[500,1377,766],{"class":510},[500,1379,894],{"class":510},[500,1381,1156],{"class":754},[500,1383,1348],{"class":864},[500,1385,868],{"class":900},[500,1387,1353],{"class":513},[500,1389,1165],{"class":900},[500,1391,1393,1396,1398,1400,1402,1404,1406,1408,1410,1413,1415,1418,1420,1423,1425,1428],{"class":502,"line":1392},30,[500,1394,1395],{"class":754},"          return",[500,1397,558],{"class":510},[500,1399,1090],{"class":513},[500,1401,610],{"class":510},[500,1403,1371],{"class":513},[500,1405,610],{"class":510},[500,1407,1215],{"class":513},[500,1409,610],{"class":510},[500,1411,1412],{"class":900}," folder",[500,1414,489],{"class":510},[500,1416,1417],{"class":513}," input",[500,1419,926],{"class":510},[500,1421,1422],{"class":513},"folder",[500,1424,1045],{"class":510},[500,1426,1427],{"class":510}," ''",[500,1429,1430],{"class":510}," }\n",[500,1432,1434],{"class":502,"line":1433},31,[500,1435,1244],{"class":510},[500,1437,1439,1442],{"class":502,"line":1438},32,[500,1440,1441],{"class":513},"        moveDocument",[500,1443,529],{"class":510},[500,1445,1447,1450,1452,1455],{"class":502,"line":1446},33,[500,1448,1449],{"class":900},"        deleteDocument",[500,1451,489],{"class":510},[500,1453,1454],{"class":513}," removeDocument",[500,1456,529],{"class":510},[500,1458,1460],{"class":502,"line":1459},34,[500,1461,1066],{"class":510},[500,1463,1465,1468,1470,1472,1475,1477,1479,1481,1483,1485,1487,1489,1491,1493,1495,1497,1500,1502],{"class":502,"line":1464},35,[500,1466,1467],{"class":900},"      directory",[500,1469,489],{"class":510},[500,1471,558],{"class":510},[500,1473,1474],{"class":864}," listUsers",[500,1476,489],{"class":510},[500,1478,1087],{"class":874},[500,1480,1051],{"class":510},[500,1482,875],{"class":874},[500,1484,1095],{"class":900},[500,1486,1098],{"class":754},[500,1488,1474],{"class":864},[500,1490,1268],{"class":900},[500,1492,926],{"class":510},[500,1494,1273],{"class":864},[500,1496,868],{"class":900},[500,1498,1499],{"class":513},"toSessionUser",[500,1501,1182],{"class":900},[500,1503,1504],{"class":510},"},\n",[500,1506,1508,1511,1513,1515,1518,1520,1523,1525,1528],{"class":502,"line":1507},36,[500,1509,1510],{"class":900},"      collabState",[500,1512,489],{"class":510},[500,1514,558],{"class":510},[500,1516,1517],{"class":513}," loadState",[500,1519,610],{"class":510},[500,1521,1522],{"class":513}," storeState",[500,1524,610],{"class":510},[500,1526,1527],{"class":513}," removeState",[500,1529,570],{"class":510},[500,1531,1533,1536,1538,1540,1542,1544,1546],{"class":502,"line":1532},37,[500,1534,1535],{"class":900},"      assets",[500,1537,489],{"class":510},[500,1539,558],{"class":510},[500,1541,906],{"class":513},[500,1543,610],{"class":510},[500,1545,911],{"class":513},[500,1547,570],{"class":510},[500,1549,1551],{"class":502,"line":1550},38,[500,1552,1553],{"class":595},"      \u002F\u002F `user` is what `authenticate` returned: the session user.\n",[500,1555,1557,1560,1562],{"class":502,"line":1556},39,[500,1558,1559],{"class":900},"      settings",[500,1561,489],{"class":510},[500,1563,878],{"class":510},[500,1565,1567,1570,1572,1575,1577,1580,1583,1585,1588,1590,1592,1594,1596,1598],{"class":502,"line":1566},40,[500,1568,1569],{"class":864},"        loadSettings",[500,1571,489],{"class":510},[500,1573,1574],{"class":985}," user",[500,1576,875],{"class":874},[500,1578,1579],{"class":864}," getUserSettings",[500,1581,1582],{"class":900},"((",[500,1584,1042],{"class":513},[500,1586,1587],{"class":754}," as",[500,1589,763],{"class":506},[500,1591,1022],{"class":900},[500,1593,926],{"class":510},[500,1595,1106],{"class":513},[500,1597,1022],{"class":900},[500,1599,529],{"class":510},[500,1601,1603,1606,1608,1610,1612,1614,1617,1619,1621,1624,1626,1628,1630,1632,1634,1636,1638,1640,1642,1644],{"class":502,"line":1602},41,[500,1604,1605],{"class":864},"        storeSettings",[500,1607,489],{"class":510},[500,1609,1095],{"class":510},[500,1611,1042],{"class":985},[500,1613,610],{"class":510},[500,1615,1616],{"class":985}," settings",[500,1618,1022],{"class":510},[500,1620,875],{"class":874},[500,1622,1623],{"class":864}," putUserSettings",[500,1625,1582],{"class":900},[500,1627,1042],{"class":513},[500,1629,1587],{"class":754},[500,1631,763],{"class":506},[500,1633,1022],{"class":900},[500,1635,926],{"class":510},[500,1637,1106],{"class":513},[500,1639,610],{"class":510},[500,1641,1616],{"class":513},[500,1643,1022],{"class":900},[500,1645,529],{"class":510},[500,1647,1649],{"class":502,"line":1648},42,[500,1650,1066],{"class":510},[500,1652,1654,1657,1659,1661,1664,1666,1669,1671,1674,1676,1679],{"class":502,"line":1653},43,[500,1655,1656],{"class":900},"      themes",[500,1658,489],{"class":510},[500,1660,558],{"class":510},[500,1662,1663],{"class":513}," listThemes",[500,1665,610],{"class":510},[500,1667,1668],{"class":513}," loadTheme",[500,1670,610],{"class":510},[500,1672,1673],{"class":513}," storeTheme",[500,1675,610],{"class":510},[500,1677,1678],{"class":513}," removeTheme",[500,1680,570],{"class":510},[500,1682,1684],{"class":502,"line":1683},44,[500,1685,1686],{"class":595},"      \u002F\u002F Block index (`index` capability): a projection of the documents above, rebuilt on start and kept current by the editor server; in memory (memory adapter, PGlite with `NUXT_BLOCK_INDEX=pglite`, or PGlite with trigram search and vectors with `NUXT_BLOCK_INDEX=pg-vector`), so nothing to migrate.\n",[500,1688,1690,1693,1695,1698,1700,1702,1704,1707,1709,1712,1714,1716,1718,1721,1723],{"class":502,"line":1689},45,[500,1691,1692],{"class":900},"      index",[500,1694,489],{"class":510},[500,1696,1697],{"class":864}," createHostBlockIndex",[500,1699,868],{"class":900},[500,1701,934],{"class":513},[500,1703,926],{"class":510},[500,1705,1706],{"class":513},"ports",[500,1708,926],{"class":510},[500,1710,1711],{"class":513},"embeddings",[500,1713,610],{"class":510},[500,1715,891],{"class":513},[500,1717,926],{"class":510},[500,1719,1720],{"class":513},"embeddingsError",[500,1722,1022],{"class":900},[500,1724,529],{"class":510},[500,1726,1728],{"class":502,"line":1727},46,[500,1729,1730],{"class":595},"      \u002F\u002F Prüfung records (`pruefung` capability): the editor's JSON store on `\u003Cdocuments dir>\u002F.pruefung.json`, a dot-named file the document store never lists.\n",[500,1732,1734,1737,1739,1741,1743,1745,1748,1750,1753,1755,1757,1759,1761,1764],{"class":502,"line":1733},47,[500,1735,1736],{"class":900},"      pruefung",[500,1738,489],{"class":510},[500,1740,786],{"class":864},[500,1742,868],{"class":900},[500,1744,903],{"class":510},[500,1746,1747],{"class":900}," dir",[500,1749,489],{"class":510},[500,1751,1752],{"class":864}," storeRoot",[500,1754,1233],{"class":900},[500,1756,1236],{"class":510},[500,1758,1022],{"class":900},[500,1760,926],{"class":510},[500,1762,1763],{"class":513},"pruefung",[500,1765,529],{"class":510},[500,1767,1769],{"class":502,"line":1768},48,[500,1770,1771],{"class":595},"      \u002F\u002F Change sets of the document chat (`changeSets` capability): what the model proposed for other Documents, decided per Document; `\u003Cdocuments dir>\u002F.change-sets.json`, likewise dot-named.\n",[500,1773,1775,1778,1780,1782,1784,1786,1788,1790,1792,1794,1796,1798,1800,1803],{"class":502,"line":1774},49,[500,1776,1777],{"class":900},"      changeSets",[500,1779,489],{"class":510},[500,1781,786],{"class":864},[500,1783,868],{"class":900},[500,1785,903],{"class":510},[500,1787,1747],{"class":900},[500,1789,489],{"class":510},[500,1791,1752],{"class":864},[500,1793,1233],{"class":900},[500,1795,1236],{"class":510},[500,1797,1022],{"class":900},[500,1799,926],{"class":510},[500,1801,1802],{"class":513},"changeSets",[500,1804,529],{"class":510},[500,1806,1808],{"class":502,"line":1807},50,[500,1809,1810],{"class":595},"      \u002F\u002F Demo of the model port (`DEMO_STUB_MODEL=1`): the stub answers `{ preset: 'stub' }`; `null` for anything else falls through to the environment presets.\n",[500,1812,1814,1817,1819],{"class":502,"line":1813},51,[500,1815,1816],{"class":900},"      models",[500,1818,489],{"class":510},[500,1820,878],{"class":510},[500,1822,1824,1827,1829,1831,1834,1836,1839,1841,1844,1846,1849,1851,1854,1856,1859,1861,1863,1865,1868,1871,1874,1877,1880],{"class":502,"line":1823},52,[500,1825,1826],{"class":864},"        languageModel",[500,1828,489],{"class":510},[500,1830,1087],{"class":874},[500,1832,1833],{"class":985}," ref",[500,1835,875],{"class":874},[500,1837,1838],{"class":864}," stubModelEnabled",[500,1840,1233],{"class":900},[500,1842,1843],{"class":510},"&&",[500,1845,540],{"class":510},[500,1847,1848],{"class":520},"preset",[500,1850,517],{"class":510},[500,1852,1853],{"class":510}," in",[500,1855,1833],{"class":513},[500,1857,1858],{"class":510}," &&",[500,1860,1833],{"class":513},[500,1862,926],{"class":510},[500,1864,1848],{"class":513},[500,1866,1867],{"class":510}," ===",[500,1869,1870],{"class":513}," STUB_MODEL_ID",[500,1872,1873],{"class":510}," ?",[500,1875,1876],{"class":513}," stubLanguageModel",[500,1878,1879],{"class":510}," :",[500,1881,1120],{"class":510},[500,1883,1885,1888,1890,1892,1894,1896,1898,1900,1903,1905,1908,1911,1913,1915,1918,1921,1923],{"class":502,"line":1884},53,[500,1886,1887],{"class":864},"        listModels",[500,1889,489],{"class":510},[500,1891,1087],{"class":874},[500,1893,1051],{"class":510},[500,1895,875],{"class":874},[500,1897,1838],{"class":864},[500,1899,1233],{"class":900},[500,1901,1902],{"class":510},"?",[500,1904,514],{"class":900},[500,1906,1907],{"class":510},"...",[500,1909,1910],{"class":864},"publicEnvPresets",[500,1912,871],{"class":900},[500,1914,610],{"class":510},[500,1916,1917],{"class":513}," stubModelProfile",[500,1919,1920],{"class":900},"] ",[500,1922,489],{"class":510},[500,1924,1120],{"class":510},[500,1926,1928],{"class":502,"line":1927},54,[500,1929,1066],{"class":510},[500,1931,1933,1936,1938,1940,1942],{"class":502,"line":1932},55,[500,1934,1935],{"class":510},"      ...",[500,1937,934],{"class":513},[500,1939,926],{"class":510},[500,1941,1706],{"class":513},[500,1943,529],{"class":510},[500,1945,1947],{"class":502,"line":1946},56,[500,1948,1949],{"class":510},"    },\n",[500,1951,1953,1956,1958,1960,1962,1965,1967,1969,1972,1974,1976,1978,1980,1982,1984],{"class":502,"line":1952},57,[500,1954,1955],{"class":510},"    ...",[500,1957,868],{"class":900},[500,1959,934],{"class":513},[500,1961,926],{"class":510},[500,1963,1964],{"class":513},"blockTypes",[500,1966,1858],{"class":510},[500,1968,558],{"class":510},[500,1970,1971],{"class":900}," blockTypes",[500,1973,489],{"class":510},[500,1975,891],{"class":513},[500,1977,926],{"class":510},[500,1979,1964],{"class":513},[500,1981,766],{"class":510},[500,1983,1022],{"class":900},[500,1985,529],{"class":510},[500,1987,1989],{"class":502,"line":1988},58,[500,1990,1991],{"class":595},"    \u002F\u002F The document chat's read-only workspace tools (`find_blocks`, `read_block`): the model finds and reads Blocks of the user's other Documents through the block index above.\n",[500,1993,1995,1998,2000,2002,2004,2006,2008,2010,2012,2014,2016,2018,2020,2022,2024,2026,2028],{"class":502,"line":1994},59,[500,1996,1997],{"class":900},"    tools",[500,1999,489],{"class":510},[500,2001,826],{"class":864},[500,2003,868],{"class":900},[500,2005,934],{"class":513},[500,2007,926],{"class":510},[500,2009,1964],{"class":513},[500,2011,1858],{"class":510},[500,2013,558],{"class":510},[500,2015,1971],{"class":900},[500,2017,489],{"class":510},[500,2019,891],{"class":513},[500,2021,926],{"class":510},[500,2023,1964],{"class":513},[500,2025,766],{"class":510},[500,2027,1022],{"class":900},[500,2029,529],{"class":510},[500,2031,2033,2036],{"class":502,"line":2032},60,[500,2034,2035],{"class":510},"  }",[500,2037,1165],{"class":900},[500,2039,2041,2043],{"class":502,"line":2040},61,[500,2042,1236],{"class":510},[500,2044,1165],{"class":513},[418,2046,2047,2048,2050,2051,2054,2055,2058,2059,451,2062,451,2065,2068,2069,2072],{},"You implement only the ports your product needs. A route whose port you did not register is not mounted (the path answers 404), and the client's probe omits the features that depend on it. ",[706,2049,431],{"href":196}," lists every port, ",[706,2052,2053],{"href":249},"Routes and auth"," the routes and how each authenticates, and ",[706,2056,2057],{"href":146},"Storage adapters"," the ready-made bundles (",[429,2060,2061],{},"createJsonAdapter",[429,2063,2064],{},"createDrizzleAdapter",[429,2066,2067],{},"createForgeAdapter",") that save you from writing ",[429,2070,2071],{},"documents"," yourself.",[472,2074,2076],{"id":2075},"open-a-collaborative-document","Open a collaborative document",[418,2078,2079,2080,2083,2084,2087,2088,2091,2092,2095],{},"The preset contains ",[429,2081,2082],{},"collabFeature()",", and the surface gets a session through the ",[429,2085,2086],{},"collaboration"," prop: a Yjs document, a Hocuspocus provider and the current user. The Yjs document ",[422,2089,2090],{},"is"," the document, so there is no ",[429,2093,2094],{},"v-model",". The repository's document page creates the session once per document:",[491,2097,2102],{"className":2098,"code":2099,"filename":2100,"language":2101,"meta":496,"style":496},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const commentsOpen = useState('tiptap-editor-comments', () => false)\n\u002F\u002F The editor's scroll container: CommentsMargin aligns its cards with the commented text inside it.\nconst scrollEl = useTemplateRef\u003CHTMLElement>('scrollEl')\n\n\u002F\u002F Demo of the opt-in `pruefung` feature: the built-in Block types plus „Prüfabschnitt“ (`shared\u002Futils\u002Fhost-block-types.ts`); the user\n\u002F\u002F holds `pruefung.record` (not part of ROOT_ABILITIES).\n\u002F\u002F The optional add-ons (flags `runtimeConfig.public.addons`, `app\u002Feditor\u002Faddons.ts`) add their Block types (slides, quotes) and features, loaded here\n\u002F\u002F before the editor mounts and only for the flags that are on; Explore is always on.\nconst { t, locale } = useEditorI18n()\nconst exploreTarget = useExploreTarget()\n\u002F\u002F Choosing a Block in the Explore panel opens it like an embed's source; „Verknüpfungen anzeigen“ shows the panel in the sidebar.\nconst addons = await loadAddons({\n  addons: addonFlagsOf(useRuntimeConfig().public.addons),\n","app\u002Fpages\u002Fdocuments\u002F[id].vue","vue",[429,2103,2104,2109,2114,2131,2135,2140,2145,2150,2155,2160,2165,2170,2175],{"__ignoreMap":496},[500,2105,2106],{"class":502,"line":503},[500,2107,2108],{"class":513},"const commentsOpen = useState('tiptap-editor-comments', () => false)\n",[500,2110,2111],{"class":502,"line":532},[500,2112,2113],{"class":513},"\u002F\u002F The editor's scroll container: CommentsMargin aligns its cards with the commented text inside it.\n",[500,2115,2116,2119,2122,2125,2128],{"class":502,"line":550},[500,2117,2118],{"class":513},"const scrollEl = useTemplateRef",[500,2120,2121],{"class":510},"\u003C",[500,2123,2124],{"class":900},"HTMLElement",[500,2126,2127],{"class":510},">",[500,2129,2130],{"class":513},"('scrollEl')\n",[500,2132,2133],{"class":502,"line":573},[500,2134,843],{"emptyLinePlaceholder":842},[500,2136,2137],{"class":502,"line":599},[500,2138,2139],{"class":513},"\u002F\u002F Demo of the opt-in `pruefung` feature: the built-in Block types plus „Prüfabschnitt“ (`shared\u002Futils\u002Fhost-block-types.ts`); the user\n",[500,2141,2142],{"class":502,"line":616},[500,2143,2144],{"class":513},"\u002F\u002F holds `pruefung.record` (not part of ROOT_ABILITIES).\n",[500,2146,2147],{"class":502,"line":622},[500,2148,2149],{"class":513},"\u002F\u002F The optional add-ons (flags `runtimeConfig.public.addons`, `app\u002Feditor\u002Faddons.ts`) add their Block types (slides, quotes) and features, loaded here\n",[500,2151,2152],{"class":502,"line":669},[500,2153,2154],{"class":513},"\u002F\u002F before the editor mounts and only for the flags that are on; Explore is always on.\n",[500,2156,2157],{"class":502,"line":688},[500,2158,2159],{"class":513},"const { t, locale } = useEditorI18n()\n",[500,2161,2162],{"class":502,"line":694},[500,2163,2164],{"class":513},"const exploreTarget = useExploreTarget()\n",[500,2166,2167],{"class":502,"line":940},[500,2168,2169],{"class":513},"\u002F\u002F Choosing a Block in the Explore panel opens it like an embed's source; „Verknüpfungen anzeigen“ shows the panel in the sidebar.\n",[500,2171,2172],{"class":502,"line":951},[500,2173,2174],{"class":513},"const addons = await loadAddons({\n",[500,2176,2177],{"class":502,"line":961},[500,2178,2179],{"class":513},"  addons: addonFlagsOf(useRuntimeConfig().public.addons),\n",[418,2181,2182,2183,2186],{},"and hands it to ",[429,2184,2185],{},"AdvancedEditor",", the ready-made composition of every catalog feature (media, transcript, Typed blocks, mentions, authors, comments, collab, block UI, themes and, optionally, AI):",[491,2188,2190],{"className":2098,"code":2189,"filename":2100,"language":2101,"meta":496,"style":496},"              >\n                {{ titleError }}\n              \u003C\u002Fp>\n            \u003C\u002Fdiv>\n            \u003CAdvancedEditor\n              v-model:sidebar-open=\"sidebarOpen\"\n              v-model:comments-open=\"commentsOpen\"\n              :document-id=\"id\"\n              :collaboration=\"{ ydoc, provider, user: { id: user.id, name: user.username, color: user.color } }\"\n              :block-types=\"blockTypes\"\n",[429,2191,2192,2197,2202,2215,2220,2225,2230,2235,2240,2245],{"__ignoreMap":496},[500,2193,2194],{"class":502,"line":503},[500,2195,2196],{"class":513},"              >\n",[500,2198,2199],{"class":502,"line":532},[500,2200,2201],{"class":513},"                {{ titleError }}\n",[500,2203,2204,2207,2210,2212],{"class":502,"line":550},[500,2205,2206],{"class":510},"              \u003C",[500,2208,2209],{"class":513},"\u002F",[500,2211,418],{"class":900},[500,2213,2214],{"class":510},">\n",[500,2216,2217],{"class":502,"line":573},[500,2218,2219],{"class":513},"            \u003C\u002Fdiv>\n",[500,2221,2222],{"class":502,"line":599},[500,2223,2224],{"class":513},"            \u003CAdvancedEditor\n",[500,2226,2227],{"class":502,"line":616},[500,2228,2229],{"class":513},"              v-model:sidebar-open=\"sidebarOpen\"\n",[500,2231,2232],{"class":502,"line":622},[500,2233,2234],{"class":513},"              v-model:comments-open=\"commentsOpen\"\n",[500,2236,2237],{"class":502,"line":669},[500,2238,2239],{"class":513},"              :document-id=\"id\"\n",[500,2241,2242],{"class":502,"line":688},[500,2243,2244],{"class":513},"              :collaboration=\"{ ydoc, provider, user: { id: user.id, name: user.username, color: user.color } }\"\n",[500,2246,2247],{"class":502,"line":694},[500,2248,2249],{"class":513},"              :block-types=\"blockTypes\"\n",[418,2251,2252,2253,2256,2257,2260,2261,2263,2264,2267],{},"With your own preset the same wiring reads ",[429,2254,2255],{},"\u003CEditorSurface :preset=\"preset\" :collaboration=\"{ ydoc, provider, user }\" \u002F>"," over a ",[429,2258,2259],{},"defineEditor"," list that contains ",[429,2262,2082],{}," (from ",[429,2265,2266],{},"@skaile\u002Fforge-editor\u002Fcollab\u002Fvue","). What to know about the session:",[2269,2270,2271,2288,2318,2328,2348],"ul",{},[2272,2273,2274,2277,2278,451,2281,451,2284,2287],"li",{},[422,2275,2276],{},"Undo is per client."," Core turns its own undo history off; the Collaboration extension brings a per-client undo and redo (",[429,2279,2280],{},"Mod-z",[429,2282,2283],{},"Mod-y",[429,2285,2286],{},"Shift-Mod-z","), so undo never reverts another user's edits.",[2272,2289,2290,2293,2294,2297,2298,2300,2301,2303,2304,1095,2307,2310,2311,455,2314,2317],{},[422,2291,2292],{},"Carets."," ",[429,2295,2296],{},"provider"," adds remote carets labelled with ",[429,2299,1042],{},"; without a provider the Yjs document is edited locally only. ",[429,2302,1042],{}," is an ",[429,2305,2306],{},"AuthorIdentity",[429,2308,2309],{},"{ id, name, color }",", your stable user id). The optional ",[429,2312,2313],{},"authors",[429,2315,2316],{},"comments"," features attribute text and comments to it.",[2272,2319,2320,2323,2324,2327],{},[422,2321,2322],{},"Fixed at mount."," Remount with a new ",[429,2325,2326],{},"key"," when the document changes, so Yjs document, provider and editor never bleed between documents.",[2272,2329,2330,2333,2334,2336,2337,2339,2340,2342,2343,2345,2346,926],{},[422,2331,2332],{},"Setup errors."," Supplying ",[429,2335,2086],{}," to a preset without ",[429,2338,2082],{}," throws „",[429,2341,2086],{}," needs a preset with the collaboration feature“. Without a ",[429,2344,2086],{}," prop the feature adds no extension and the editor behaves like a standalone one with a JSON ",[429,2347,2094],{},[2272,2349,2350,2353,2354,2357],{},[422,2351,2352],{},"CSS."," Include ",[429,2355,2356],{},"@skaile\u002Fforge-editor\u002Fstyles\u002Fcollab.css"," (the Nuxt module adds it) for the caret styles.",[418,2359,2360,2361,2363,2364,2367],{},"To try it: open the same document URL in a second browser tab, or as another user in a private window (switch to ",[429,2362,454],{}," in the user menu). Text and carets appear in both within a moment, and the header badge reads „Synchronisiert“. ",[706,2365,2366],{"href":309},"Collaboration"," covers authors, comments and positions that survive concurrent edits.",[472,2369,2371],{"id":2370},"teach-the-server-your-block","Teach the server your block",[418,2373,2374,2375,2377,2378,489],{},"A Yjs document is valid only if every participant knows the same schema. The standalone editors of chapters 4 and 5 use a JSON ",[429,2376,2094],{}," and need nothing here. With a collaboration session the ",[422,2379,2380],{},"server must know the callout, too",[2269,2382,2383,2409,2428,2447,2457],{},[2272,2384,2385,2386,2389,2390,2393,2394,455,2397,2400,2401,2404,2405,2408],{},"The module's collab route builds its schema from the advanced entry plus the ",[429,2387,2388],{},"schema"," option you give the server: ",[429,2391,2392],{},"provideEditorServer({ ports, schema: () => [...advancedSchemaExtensions(), Callout] })"," (the option also exists on ",[429,2395,2396],{},"createCollabServer",[429,2398,2399],{},"installEditorServer","; the default is ",[429,2402,2403],{},"advancedSchemaExtensions()","). Without it, a document that contains a ",[429,2406,2407],{},"callout"," is outside what that route can load: seeding from JSON and the load-time block-ID backfill both go through a schema without the node.",[2272,2410,2411,2412,2415,2416,2419,2420,2423,2424,2427],{},"Pass the ",[422,2413,2414],{},"Vue-free"," node file, ",[429,2417,2418],{},"Callout"," from ",[429,2421,2422],{},"callout\u002Fnode.ts",", never ",[429,2425,2426],{},"CalloutBlock",", so no Vue component loads on the server. This is why chapter 4 split the node into two files.",[2272,2429,2430,2431,2434,2435,2438,2439,2442,2443,2446],{},"A plain Vue or React app with ",[422,2432,2433],{},"its own Hocuspocus server"," builds the schema itself: ",[429,2436,2437],{},"getSchema([...advancedSchemaExtensions(), Callout])",", with the same Collaboration ",[429,2440,2441],{},"field"," as the client (",[429,2444,2445],{},"default",").",[2272,2448,2449,2452,2453,2456],{},[429,2450,2451],{},"editorBlock: true"," works on the server too: ",[429,2454,2455],{},"assignBlockIds"," reads the flag from the schema, so the server backfills IDs for callouts exactly like for paragraphs.",[2272,2458,2459,2460,2463,2464,2467,2468,2470,2471,2474],{},"The client mounts a preset that contributes the same node: ",[429,2461,2462],{},"advancedEditor().features"," plus ",[429,2465,2466],{},"calloutFeature()",", together with ",[429,2469,2082],{},"'s session, which ",[429,2472,2473],{},"advancedEditor()"," already has.",[418,2476,2477,2480,2481,2483],{},[706,2478,2479],{"href":366},"Advanced schema"," shows the Vue-free schema entries, and ",[706,2482,135],{"href":136}," the build and the process-wide registry.",[472,2485,2487],{"id":2486},"what-you-have-now","What you have now",[418,2489,2490],{},"A server side for the editor: the Nuxt module with ports for identity and storage, a collaboration session that two browsers share, and a callout the server can load. You also know where to look when a route is missing (the port is not registered) or a document will not load (the server schema lacks a node).",[472,2492,2494],{"id":2493},"next","Next",[418,2496,2497,2499,2500,2503],{},[706,2498,75],{"href":76}," teaches the editor, and through ",[429,2501,2502],{},"aiContext"," the model, about callouts.",[2505,2506,2507],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .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 .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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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}",{"title":496,"searchDepth":532,"depth":532,"links":2509},[2510,2511,2513,2514,2515,2516],{"id":474,"depth":532,"text":140},{"id":714,"depth":532,"text":2512},"Give it EditorPorts",{"id":2075,"depth":532,"text":2076},{"id":2370,"depth":532,"text":2371},{"id":2486,"depth":532,"text":2487},{"id":2493,"depth":532,"text":2494},"Register the Nuxt module, hand it EditorPorts for identity and storage, and open one document in two browsers with the collaboration session.","md",null,{},{"icon":73},{"title":70,"description":2517},"6UWp2zOQYApsvTGcFc_HmqjWANuhXgNKGvS6RHFvVAQ",[2525,2527],{"title":65,"path":66,"stem":67,"description":2526,"icon":68,"children":-1},"Turn the callout into an EditorFeature with a toolbar button, a hover menu for the tone, „Umwandeln in …“ and its own ability.",{"title":75,"path":76,"stem":77,"description":2528,"icon":78,"children":-1},"Add aiFeature() to the meeting-notes editor, provide the \u002Fapi\u002Fai endpoints and keys, and describe the callout to the model with aiContext.",1793178230172]