Skip to main content

document-editing

How to create, read, update, and delete documents. Covers the document scripts, markdown content model, parent-child hierarchy, and position ordering.

Source facts

Repository
BuilderIO/agent-native
Last source activity
October 10, 2026 at 19:24
Detected SKILL.md language
English
Stars
7,119
Forks
646

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
3 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
document-editing
description
How to create, read, update, and delete documents. Covers the document scripts, markdown content model, parent-child hierarchy, and position ordering.
# Document Editing Documents are stored in the SQL database via Drizzle ORM. Each document has a title, stable description, markdown content, optional parent (for nesting), and a position for ordering. The description explains why the page exists and what belongs there; it is not a changing synopsis of the current body. When operating Content through a connected MCP server, call these actions yourself; route work through `ask_app` only when delegating to Content's own agent is the explicit point of the request. Reaching tools you can call directly through `ask_app` adds a second interpretation of the request and hides real errors. ## Scripts Always use the dedicated scripts for document operations. Never use raw `db-exec` SQL. In dev, call actions with `pnpm action <name>`; in production, call native tools. Never use `curl`, raw HTTP requests, or `db-exec` with raw SQL for document operations. `.env` is loaded automatically — never manually set `DATABASE_URL` or other env vars. ### list-documents List document metadata in a tree structure. This intentionally does not return full document bodies; call `get-document` for the one document you need to read. ```bash pnpm action list-documents pnpm action list-documents --format json ``` ### search-documents Search documents by title and content. Results include snippets, not full document bodies; call `get-document` before editing or summarizing a specific result. ```bash pnpm action search-documents --query "meeting notes" pnpm action search-documents --query "project plan" --format json ``` ### get-document Get a single document by ID with full content and description. MCP callers read the complete values from `structuredContent`; the text preview may be shortened. ```bash pnpm action get-document --id abc123 pnpm action get-document --id abc123 --format json ``` ### create-document Create a new document. ```bash pnpm action create-document --title "Meeting Notes" --content "# Meeting Notes\n\nAttendees: ..." pnpm action create-document --title "Sub Page" --parentId parent123 pnpm action create-document --title "My Page" --icon "📝" pnpm action create-document --title "Research" --description "Evidence and source notes that support the current project" pnpm action create-document --title "Placeholder 1" --spaceName "Foobar" ``` If the result has `creativeContextProjectionStatus: "pending"`, the Page is already committed. Retry the same arguments with the returned `id` to repair its Creative Context projection; do not create another Page. When a user asks for a Page in an interactive Content conversation, creation is not a complete handoff. After `create-document` succeeds, call `navigate` with the returned document `id`, then call `view-screen` and compare its document ID with the create result. Navigation is asynchronous: if `view-screen` still reports the previous Page, repeat `navigate` and `view-screen` up to two more times. Say the Page is open only after the IDs match. If they never match, say the Page was created but navigation could not be verified, and provide the stable Page link from the create result. Do not navigate for background, batch, or API creation unless the caller explicitly asked to open the result. When the user names a workspace ("in my Foobar workspace"), pass `spaceName` (or a `spaceId` from `list-content-spaces`). A named workspace that does not resolve is an error, never a silent fall back. With no `parentId`, `spaceId`, or `spaceName` the page is created in the caller's Personal workspace, so read the returned `spaceId` before telling the user where the page landed. The Workspaces catalog is not a create target: its rows only list workspaces, and `add-database-item` against it is rejected. ### update-document Update a page's or database's metadata while preserving omitted fields. To replace its guidance, pass only `id` and `description`; an empty string clears the description. For a database, use its backing `documentId`, not its collection `databaseId`. The saved description is returned in MCP `structuredContent`. Agent responses contain metadata only; `get-document` returns the body. This does not change the Markdown body; body edits use `edit-document`. ### import-content Turn Markdown files the user hands over into new pages. Prefer it to `create-document` whenever the source is a file: it takes the title from frontmatter or the first heading, keeps the original file, uploads referenced images, records "Imported from <file>" in History, and names everything that did not come across. ```bash pnpm action import-content --dryRun true --parentId abc123 --files '[{"name":"guide.md","text":"# Guide\n..."},{"name":"logo.png"}]' pnpm action import-content --dryRun false --parentId abc123 --idempotencyKey guide-1 --files '[{"name":"guide.md","text":"..."},{"name":"logo.png","url":"https://..."}]' ``` Run the dry run first and tell the user each page's `status` and `notes` before applying. Applying fails with `IMPORT_IMAGE_NOT_UPLOADED` until every image named in `uploads` has a `url`; leave an image nobody can supply out of `files`, and its references become visible placeholders on the page. Other formats come back in `skipped` as not supported yet. Pass an `idempotencyKey` on every apply, and reuse it only to retry the same files, with the same image `url`s, into the same place. `IMPORT_INCOMPLETE` means some pages landed before a failure: its `details` name the `importId` and created page ids (with `documentIdsComplete: false`, more may exist), and the same call again finishes the import. `IMPORT_PAGE_TRASHED` means the import was undone or its pages trashed; restore them, or import with a new key. `undo-content-import --importId <id>` moves the import's pages to Trash. It needs only editor access, not the admin access `delete-document` needs, because it moves only pages the caller imported that nobody has changed since. It refuses with `IMPORT_PAGE_CHANGED` when a page was edited, moved, or given child pages since; ask before using `delete-document` instead. `IMPORT_IN_PROGRESS` means the import is still adding pages; undo it again once it finishes. While the Import dialog is open, `view-screen` returns `contentImport` with its status, destination, file names, and counts, never file contents. `import-content-source` is unrelated: it syncs a connected local folder. ### edit-document Surgically edit document content using search-and-replace. **Preferred over `update-document --content` for modifications** — sends only the changed text instead of regenerating the entire document. ```bash # Single edit pnpm action edit-document --id abc123 --find "old text" --replace "new text" # Delete text pnpm action edit-document --id abc123 --find "delete me" --replace "" # Batch edits pnpm action edit-document --id abc123 --edits '[{"find":"old","replace":"new"},{"find":"also old","replace":"also new"}]' ``` External MCP, WebMCP, tool, and A2A callers first read the document, then pass its `baseRevision` and one stable `idempotencyKey`. When the returned body is literally empty, pass non-whitespace `initializeContent` instead of `find` or `edits`. Initialization rejects whitespace-only and all other nonempty bodies, preserves the Markdown bytes exactly, and safely replays an identical retry. ### update-document Update an existing document's metadata or browser-owned content. External callers use the revisioned `edit-document` protocol for every body change. ```bash pnpm action update-document --id abc123 --title "New Title" pnpm action update-document --id abc123 --content "# Updated Content\n\nNew text here" pnpm action update-document --id abc123 --title "New Title" --content "New content" pnpm action update-document --id abc123 --description "Stable guidance for what belongs on this page" ``` ### Suggested edits When the user asks to **suggest**, **propose**, or **leave changes for review**, do not call `edit-document` or `update-document`. Read the current Page with `get-document`, then call `suggest-document-edit` with the Page's `id`, its `baseRevision`, a fresh `idempotencyKey`, and the exact `find` text plus the `replace` Markdown (omit `replace` to propose deleting the text). `find` must match the page's current text exactly once. Content builds the tracked change and anchor server-side; the page stays unchanged until a reviewer accepts. The action separates disjoint punctuation and word changes into independent review edits. Its result gives the first `suggestionId`, all `suggestionIds`, and a `proposalId`. To add another find/replace call to that proposal, pass its `proposalId`, the same `summary`, and a fresh `idempotencyKey`. Retry the same call with its original key. An unchanged replacement creates no suggestion and reports an error. Inside tables, callouts, toggles, and columns, suggest text: edit a cell, or edit or add a paragraph in a callout, toggle, or column. Keep each `find` within one cell or one frame, and make one call per cell. A `find`/`replace` that changes text on both sides of a cell or frame edge, adds or removes table rows or cells or a column, or changes a callout's icon, a toggle's title, or an image, fails with `suggestion_structure_unsupported`. Use `suggest-document-edit` for every suggested body edit. The generic `create-resource-suggestion` action remains for advanced proposals that build typed Page-body operations by hand: `resourceType: "document"`, adapter kind `content.document-markdown`, the Page's `revision` (or exact `updatedAt`) as `baseRevision`, a fresh `idempotencyKey`, and one operation carrying the complete current and proposed Markdown in `before.markdown` and `after.markdown`, the changed segment in `changedText`, and an `anchor` object. Use `list-resource-suggestions` to inspect pending and historical proposals. Only accept or reject when the user has asked for that decision and the caller has editor authority. A suggestion's author may instead withdraw their own pending suggestion with comment access (`decision: "withdrawn"`); withdrawn suggestions leave the Page unchanged and drop out of review. Call `decide-resource-suggestion` with a fresh idempotency key and the suggestion's `baseRevision` as `observedBase`. An accept that fails with `suggestion_stale` didn't land because the text around it changed; the Page is unchanged and the suggestion stays pending, so tell the user which suggestion it was instead of counting it as accepted. Suggested edits are unavailable for local-file, source-owned, externally linked, or trashed Pages, Collection Pages, Pages with inline databases, and collection-item Pages without an accessible primary Blocks field. ```bash pnpm action suggest-document-edit --id abc123 \ --baseRevision 'body:0:sha256:…' --idempotencyKey '<uuid>' \ --find 'Exact current sentence.' --replace 'Proposed replacement sentence.' ``` ### delete-document Move a page and all its sub-pages to Trash. IDs, bodies, hierarchy, and collection membership remain intact so the subtree can be restored. Agents pass the page's exact `updatedAt` from `<current-screen>` or a fresh read, plus an idempotency key. A conflict means the page changed: read it again before deciding to trash it. Collection pages use `delete-content-database` instead. ```bash pnpm action delete-document --id abc123 \ --expectedUpdatedAt '<updatedAt>' --idempotencyKey '<uuid>' ``` Restore the receipt's `trashRootId` with its `trashedAt` (also listed by `list-content-trash`), or permanently delete it only after it is in Trash: ```bash pnpm action restore-document --id abc123 \ --expectedTrashedAt '<trashedAt>' --idempotencyKey '<uuid>' pnpm action plan-content-trash-purge --mode selection --documentIds '["abc123"]' pnpm action permanently-delete-document --id abc123 --planId '<reviewed plan ID>' --scopeToken '<opaque plan token>' ``` Permanent deletion requires the exact `planId` and `scopeToken` returned by `plan-content-trash-purge`. Inspect the plan's affected and blocked items before executing it; if Trash changes after review, create and inspect a new plan. ## Comments Comments are Notion/Google-Docs-style **inline comments**. Selecting text and commenting leaves the passage **highlighted inline** via a ProseMirror decoration overlay — nothing is written into the markdown body, so the document round-trips unchanged. Each thread stores the quoted text plus surrounding context (`anchorPrefix`/`anchorSuffix`) and an approximate `anchorStartOffset`, so the highlight follows the text as the document is edited, disambiguates repeated text, and degrades gracefully (the thread stays in the sidebar) when its text is deleted. Resolving a thread clears its highlight and moves it to a collapsible **"Resolved (n)"** sidebar section, from which it can be **reopened**. Comments support **@mentions** of org members, stored as a `mentions` array of `{email, name}`. ```bash # List threads (returns anchor fields + parsed mentions) pnpm action list-comments --documentId abc123 # Plain comment on the document pnpm action add-comment --documentId abc123 --content "Looks good" # Inline-anchored comment with a mention pnpm action add-comment --documentId abc123 --content "@Sam check this" \ --quotedText "the second paragraph" --anchorPrefix "above " --anchorSuffix " here" \ --anchorStartOffset 120 --mentions '[{"email":"sam@x.com","name":"Sam"}]' # Reply to a thread pnpm action add-comment --documentId abc123 --threadId t123 --content "Agreed" # Resolve / reopen the whole thread pnpm action update-comment --id c123 --resolved true pnpm action update-comment --id c123 --resolved false ``` `--authorName` sets the comment's display name; it defaults to a name derived from the author's email. A new comment emails the document owner, earlier authors in the thread, and anyone mentioned, unless they turned those emails off. The current user's own switch is `get-content-notification-prefs` and `update-content-notification-prefs --emailNotifications=false`, the same one Settings shows on Notifications. Share invites always send. ### refresh-list Trigger the UI to refresh the document list. ```bash pnpm action refresh-list ``` Always run this after any document modification to update the sidebar. `create-document`, `update-document`, and `delete-document` already signal a UI refresh, so in practice only call `refresh-list` directly if you mutate documents another way and the UI doesn't update. ## Document Schema | Column | Type | Description | | ------------- | ------- | -------------------------------------- | | `id` | text | Primary key (12-char hex string) | | `parent_id` | text | Parent document ID (null for root) | | `title` | text | Document title (default: "Untitled") | | `description` | text | Stable semantic guidance for the page | | `content` | text | Markdown content | | `icon` | text | Emoji icon (optional) | | `position` | integer | Sort order within parent (0-based) | | `is_favorite` | integer | Whether document is favorited (0 or 1) | | `created_at` | text | ISO timestamp | | `updated_at` | text | ISO timestamp | ### Related Tables Documents live in the SQL `documents` table via Drizzle; the framework injects the live column schema separately, so this covers only semantics the schema can't convey: - `document_shares` holds per-user/per-org grants with a `viewer`, `editor`, or `admin` role. - `document_versions`, `document_comments`, and `document_sync_links` all carry `owner_email` so a workspace can upgrade from local mode to a real account without losing history, comments, or Notion links. - A collection is a normal document (`content_databases` + `document_property_definitions`) whose rows are also documents, linked through `content_database_items`. Row pages are omitted from the ordinary sidebar tree — they're reached through the collection view. Documents are **private by default**; use `share-resource` / `set-resource-visibility` (`resourceType document`) to change access. ## Content Format Documents use **markdown** for content. The editor renders markdown in real time. ### Math Content renders LaTeX with KaTeX while preserving the source in NFM. Use the canonical Content delimiters when an agent creates or edits math: ```md Inline math: The relationship is $`E = mc^2`$ in this example. Block math: $$ \int_0^1 x^2 dx = \frac{1}{3} $$ ``` - Inline math uses ``$`...`$``. Do not substitute ordinary `$...$`; dollar amounts in prose are intentionally not interpreted as equations. - Block equations use `$$` on their own lines before and after the LaTeX. - Keep the raw LaTeX intact when editing around an equation. Invalid or unsupported expressions remain visible as source so they can be repaired. - Markdown exports preserve these delimiters. HTML and PDF-ready exports render the equation. ## Parent-Child Hierarchy Documents form a tree via `parent_id`: - Root documents have `parent_id = null` - Child documents reference their parent's `id` - Deleting a parent recursively deletes all children - Position determines ordering within the same parent To reorganize the tree, move each subtree with `move-document` using ids from a prior action result. When the plan calls for a target page that does not exist yet — a new section, a grouping page — create it first with `create-document` and use the returned id as `parentId`. Descriptions are owned; context is inherited. `get-document` and `view-screen` return the focused page's own description plus a computed root-to-parent `contextPath`. Use that path to understand where the page lives, but never copy ancestor descriptions into the child. Collection, property, and option descriptions narrow the guidance further when working with structured values. ## Screen Context And IDs Screen context is auto-included as a `<current-screen>` block on every message — check it before acting instead of calling `view-screen` by default. Call `view-screen` explicitly only when that snapshot is truncated or doesn't yet reflect something that changed earlier in the same turn (for example, right
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub