{"owner":"siyuan-note","repo":"siyuan","hasSkills":true,"totalSkillsCount":5,"totalTokensCount":27408,"categories":["anthropic-skill","root-instruction","project-spec"],"hasMcp":false,"mcpConfig":null,"found":["kernel/util/builtin_skills/siyuan-plugin-development/SKILL.md","AGENTS.md","kernel/util/builtin_skills/siyuan-plugin-development/references/verification.md","kernel/util/builtin_skills/siyuan-plugin-development/references/development.md","docs/API.md"],"skills":{"kernel/util/builtin_skills/siyuan-plugin-development/SKILL.md":"---\nname: siyuan-plugin-development\ndescription: Official frontend plugin development workflow. After the user explicitly opts in, clarify missing requirements, confirm a short plan, implement in a managed copy, verify, and package. Installation and activation require separate authorization.\n---\n\n# SiYuan Frontend Plugin Development\n\nThis is a read-only official skill shipped with SiYuan. Its current scope is creating and modifying frontend plugins, using CommonJS JavaScript without third-party runtime dependencies by default. Full development, verification, and recovery of kernel.js are outside this workflow's scope.\n\nReplace example placeholders such as ${PLUGIN_NAME} according to the confirmed plan. Do not treat user-defined variable settings as official content.\n\n## Obtain an Explicit Choice, Then Confirm the Plan\n\n- Use this skill only when the user explicitly asks to develop or modify a plugin. Do not automatically turn ordinary template, document, or theme requests into plugins\n- Initiate the official choice with workflow.action=\"choose\" in the question tool; questions may be an empty array. The server displays a fixed, localized yes/no question. Do not treat ordinary questions, chat text, the model's own consent, or the user's previous answers as permission for this task\n- After the user chooses yes, load the body through skill with action=\"load\", source=\"builtin\", and name=\"siyuan-plugin-development\". Continue only after the server confirms successful loading. Do not read the official body if the user declines or cancels, or if the skill is disabled; user-requested custom development may still proceed under its original permissions\n- Extract known requirements from the current conversation, and ask only about gaps that would change the implementation: functionality, entry points, triggers, target frontend, data-read and data-write effects, and deliverables. Do not repeat questions already answered, guess a notebook, or expand the scope to every platform\n- Submit a short plan through question with workflow.action=\"plan\", bound to the taskId returned by the choice. The full plan must clearly state the plugin name, frontend, functionality and entry points, data changes, delivery scope, import source, and files allowed in the package. The server freezes and displays the entire plan; do not write source code before actual acceptance is confirmed\n- Update the plan and obtain new confirmation when changing a confirmed goal, source-code origin, data effects, or packaging scope. Do not fabricate a taskId, plan digest, or confirmed state; previous questions and versions do not authorize a new task\n\nExample choice call: {\"questions\":[],\"workflow\":{\"action\":\"choose\"}}. Use newTask=true only when the user explicitly requests another plugin; reconsider=true may be used when the user explicitly changes the current workflow choice. Ordinary repeated calls reuse the current choice without asking again.\n\nPlan fields: taskId, proposal (at most 2000 characters), packageName, frontend (one of desktop, desktop-window, browser-desktop, mobile, or browser-mobile), dataEffects and deliverables (at most 1000 characters each), and files (at most 200 exact relative file paths). Before importing an existing plugin, call bazaar's project_status with taskId, sourcePath, and exact sourceFiles; sourceFiles must match the workflow.files awaiting confirmation. The host returns a sourceRevision for the selected existing safe files; include that digest and sourcePath in the plan. Do not implicitly read or hash the entire repository. List excluded paths such as .git, node_modules, and config without reading their contents. Omit source fields when creating a new project. The technical file list is also shown to the user; do not add unapproved files after confirmation. The allowlist may contain optional files that do not yet exist, but this does not permit omitting the entry point, manifest, or declared resources.\n\n## Verify Tools and APIs First\n\nRead references/development.md first. Read every resource through skill with source=\"builtin\" and name=\"siyuan-plugin-development/relative-path\". Check the tools currently available and the target SiYuan version; consult the official plugin-sample, petal, and source code for that version as needed. Do not treat type declarations, outdated README versions, or unreleased APIs as evidence of support in the current host.\n\nUse CommonJS JavaScript by default. Use TypeScript only when the required dependencies and build tools actually exist and are authorized for use; source code is not equivalent to a runnable index.js. Native agents are not guaranteed to have a shell, dependency-installation tools, or arbitrary code execution. If a tool is missing, report the exact blocked step. Do not invent execution results or bypass a tool denial. If the user specifies TypeScript, confirm an alternative approach before switching to JavaScript.\n\n## Implement in a Managed Copy\n\n1. After plan confirmation, call bazaar's prepare_project with taskId so the host creates a source-code copy and baseline checkpoint; do not specify an absolute write directory yourself. For imports, first perform a read-only inspection of the exact sourceFiles, then import a copy after approval of the source and the selected files' baseline digest; leave the original directory unchanged. After reconfirming a plan for an existing managed project, obtain the current source revision with project_status(taskId), then call prepare_project(taskId, expectedSourceRevision) to establish a new checkpoint. This preserves the current source code without reimporting or overwriting it\n2. Use file.write with ifAbsent=true for new files. Before editing existing text, use file.read with withMetadata=true; continue reading with the same expectedRevision and nextOffsetByte until you have fully read the original content that needs changing. truncated=true does not represent a complete file\n3. Prefer file.edit for partial changes, providing expectedRevision and unique, nonoverlapping oldText/newText replacements. Use file.write with expectedRevision only when full replacement is necessary, and only after reading the entire file at that same revision. On a conflict, reread and verify first. Do not blindly retry writes or fall back to overwriting the entire file when a match is missing or ambiguous\n4. Use the paths and project revisions returned by the host; verify the current source code, plan, and checkpoint through bazaar's project_status. Stop writing if a checkpoint cannot be established, and report partial failures accurately. When recovery is needed, use restore_project and respect the current revision and tool confirmation requirements\n5. Do not bypass conditional writes for managed source code through copy, unzip, directory renaming, or HTTP file APIs. Do not touch host-controlled, backup, or artifact directories, or directly overwrite the actual plugin installation directory\n\nCanceling the workflow does not automatically roll back changes. When cancellation, an unknown write result, or partial preparation prevents the ordinary plan from continuing, request recovery-only authorization through question with questions=[], workflow.action=\"recover\", and taskId. The server displays and binds the most recently actually approved plan and its digest.\nAfter confirmation, you may call restore_project, or retry prepare_project only for the same frozen, interrupted preparation. Recovery does not roll back notes, runtime data, or other configuration, and does not authorize further source-code changes; subsequent ordinary source changes still require normal plan confirmation.\n\nUse the minimal entry-point pattern in examples/index.js as a reference and adapt it to the actual functionality. Create entry points through the public Plugin API and modify notes through kernel APIs; never use fs or Electron to modify data files directly. Put UI text in i18n. Handle return codes, missing targets, closed notebooks, repeated clicks, and late responses. onunload must handle partial initialization and clean up the plugin's own listeners, timers, observers, and requests; unloading cannot undo write requests that have already been sent.\n\n## Verify, Package, and Deliver\n\nRead references/verification.md first. Declare only frontends that are actually implemented and verified; do not declare kernels without kernel.js. Do not copy the example's author, repository, funding information, or data permissions, or invent a publishing identity for a local prototype.\n\nRequest a deterministic local ZIP through bazaar's package_local with taskId and the latest expectedSourceRevision. The host freezes only existing safe files on the allowlist. Optional paths may be absent, but plugin.json, index.js, and resources declared by the manifest must exist. It does not execute project scripts or download dependencies. If the entry point or declared resources are missing, or a secret file is detected, first correct the source code or reconfirm the scope. Retain the returned packageHash, sourceRevision, and validation results. Successful packaging does not mean JavaScript syntax, actual installation, frontend loading, or functional execution has passed verification.\n\nReport separate results and evidence for source code, static checks, builds, ZIP packaging, simulated tests, actual installation, activation configuration, frontend loading, and functional execution. Mark builds for plain JavaScript as \"not applicable\"; mark steps that were not run as \"unverified\" and explain why. Tests using SDK substitutes or a standalone Electron DOM environment are not SiYuan integration tests; verify desktop Electron and the desktop browser separately.\n\nInstallation and activation each require the user's explicit authorization for the corresponding action and target. Overwriting an enabled plugin may execute code immediately; a disable notification does not confirm unloading in every frontend, so do not claim it automatically guarantees execution has stopped. Once authorized, use only the native installation interface, bound to the package digest and target revision returned by the host; do not install through generic file writes. Rolling back code does not roll back the user's notes or configuration.\n","AGENTS.md":"# AGENTS.md\n\nSiYuan repository guide. Module path `github.com/siyuan-note/siyuan`, license AGPL-3.0.\n\n---\n\n## 1. Non-negotiable constraints\n\n### Verify the code baseline before repository tasks\n\n- Before each new repository task, verify the repository, current commit, branch, and working-tree state. For work targeting the latest branch, select the baseline source branch using section 2's Git rules and fetch the relevant remote refs before analysis or editing. Respect explicitly requested commits, tags, or pull requests; for a fixed-revision review or historical investigation, inspect that revision without advancing it or synchronizing the working branch. Fetch only if needed to resolve the requested revision. If fetching is unavailable, report the known commit and freshness limit; read-only analysis may continue, but do not claim to have verified the latest remote baseline\n- Follow the repository-specific Git rules for working branches and authorized push destinations. If the user explicitly requests a task branch or the repository's own rules require one, create it from the verified task baseline. Using an isolated worktree does not change the default working branch or authorize pushing to a task branch instead. When synchronizing a development branch, automatically fast-forward it only when its working tree is clean and it is merely behind. Rebase is permitted when synchronizing divergent development branches, provided existing work is preserved; this does not authorize force-pushing rewritten history. Read-only analysis does not itself authorize branch synchronization. If synchronization is blocked, preserve existing work and report the limitation; do not discard changes, automatically stash, create merge commits, or silently substitute another push destination\n- Reuse the verified baseline for follow-ups in the same task; recheck after a substantial pause, a repository or branch change, or when newer commits could affect the task. Unrelated conversation needs no fetch\n\n### Requirement evaluation\n\nBefore implementing a requirement, evaluate its premise and approach with effort proportional to its complexity and risk\n\n1. **Check compliance first:** For major features, review applicable compliance requirements, licensing terms, privacy risks, and costs before technical design\n2. **Check the premise and existing capabilities:** Inspect the relevant implementation, settings, shortcuts, APIs, or plugin capabilities, and reproduce reported behavior when feasible. Check applicable repository rules, encrypted notebook compatibility, the `.sy` format, and API contracts. Distinguish the user's goal and acceptance criteria from a suggested implementation; evaluate whether that implementation addresses the underlying problem. State any limits to verification\n3. **Choose an appropriate approach:** Prefer the smallest sufficient change consistent with existing practices and compatibility requirements. Consider whether an existing capability or a documentation correction already satisfies the need, including the option of making no code change\n4. **Briefly report before editing:** State the conclusion, key evidence, and remaining uncertainty in a progress update before the first change. Discuss alternatives only when they involve meaningful tradeoffs; simple tasks need only a short explanation\n5. **Ask only when a decision is needed:** If the premise is false or implementation is unwarranted, explain the evidence and ask how to proceed unless the user's existing instructions already resolve the next step. Ask before changing the agreed goal or scope, or making an unresolved product, priority, or compatibility tradeoff. Do not silently narrow or broaden the task\n6. **Otherwise proceed directly:** When the requirement is reasonable, implement it without another confirmation round. Choosing a different implementation that meets the agreed goal and acceptance criteria does not itself require approval\n\nUnambiguous mechanical edits with no behavioral or compatibility impact need no separate evaluation round. Continue to follow applicable repository checks; a rename, version bump, or documentation update is not automatically exempt\n\n### Do not hand-edit\n\n- `app/stage/protyle/js/lute/lute.min.js` (built from upstream `88250/lute`)\n- `app/stage/build/**`, `app/src/types/dist/**`\n- `app/changelogs/**` (generated by separate tooling)\n- `app/kernel/SiYuan-Kernel*`, `*.syso`, `kernel/kernel.aar`\n- `app/pandoc/*`\n- Other files marked as generated: edit their source and regenerate them instead. API declarations and schemas come from `kernel/apicontract/`; attribute-view capability declarations come from `kernel/av/capabilities.go`. Before changing either, read the generation instructions and output inventory in [docs/API-CONTRACTS.md](docs/API-CONTRACTS.md#generation-and-verification), including Petal synchronization\n\n### Verification and prohibited operations\n\n1. **Frontend verification:** Do not use `npx webpack` or `pnpm dev` to verify changes. After frontend code changes, run `pnpm run lint` from `app/`; it includes type checks, ESLint automatic fixes, and CSS, menu, and startup-language checks, but not behavioral tests. Review the resulting diff and keep fixes within the task scope without overwriting existing work. In read-only tasks, use applicable non-mutating checks instead, such as `pnpm run typecheck` and ESLint without `--fix` or `--cache`; do not run the auto-fixing lint command\n2. **Frontend build:** Do NOT run `pnpm build` — the developer runs `pnpm dev` manually, and `pnpm build` will conflict with it, producing broken bundles\n3. **Kernel development:** After modifying Go code, run `gofmt`, but do not compile the kernel binary or restart a running kernel; the developer handles both manually. Isolated regression tests and contract generation are allowed; they must not replace the product binary or restart the running instance\n4. **Git:** **NEVER** run `git commit` / `git push` unless explicitly asked — no exceptions\n5. **UI verification in a browser:** Open `/stage/build/desktop/` directly instead of `/`. The kernel selects the frontend bundle by User-Agent in `kernel/server/serve.go`, and a UA containing `Electron` is redirected to the Electron-only `/stage/build/app/`, which fails in a plain browser with `require is not defined`; VSCode's built-in browser sends such a UA. The Electron main window does not expose a remote debugging port by default\n6. **Verification scope:** Run the checks and regression tests applicable to the changed behavior, including non-API Go code, frontend interactions, and scripts. Select scope according to impact and risk; a full suite is not required for every task, and lint or formatting alone does not establish behavioral correctness. Use `app/scripts/test.mjs` and the relevant maintenance guides to identify test discovery and commands. For documentation-only changes, check accuracy, links, language alignment, and the diff rather than running auto-fixing code checks. Report passed, failed, and unrun checks with their limits; do not infer that tests passed solely from a successful CI job or release\n7. **Test synchronization:** Whenever code or files change, handle the corresponding tests and fixtures in the same change rather than leaving them for later\n   - Rename, move, merge, split, or delete a test file together with its source, and keep the file name reflecting the code it covers; a test file whose source no longer exists or whose name no longer matches is stale and must be repaired or removed\n   - When behavior changes intentionally, update the affected assertions, case names, and comments to the new contract instead of dropping the coverage; when behavior is removed, delete only the cases that cover the removed behavior and keep adjacent assertions\n   - Keep test doubles aligned with the code under test: remove mocks of exports, constants, flags, and helpers that no longer exist, and do not preserve assertions that only pass because a stale stub keeps them alive\n   - Do not use a test that already fails for unrelated reasons as permission to leave a new mismatch; state such failures separately and verify whether the task changed their outcome\n   - Merge test files only when they cover the same subject and merging removes duplication without losing cases or discoverability; otherwise keep the current split\n   - Run the affected tests after the synchronization, and name the files and cases that were updated, merged, or deleted\n\n### Encrypted notebook compatibility\n\n- Encrypted notebook development is complete. Maintain it as a released feature\n- Existing encrypted data is the compatibility baseline. Upgrades must preserve access and recovery; do not treat it as disposable development data or require users to delete or recreate it\n- Changes to document, asset, attribute-view, key-envelope, backup, history, or sync formats must retain authenticated reads of supported existing formats or provide a recoverable migration before removing a reader\n- Changes to envelopes, AAD semantics, or key derivation must use an explicit format version and a compatibility plan. Migration must authenticate the source, preserve recovery material, and remain recoverable after failure or interruption; never regenerate MasterSalt or discard keys to work around incompatibility\n- Unknown formats, corruption, and authentication failures must preserve the original data and return an error; compatibility must not bypass authentication or fall back to plaintext. Derived indexes may be rebuilt after the source ciphertext authenticates\n- Format changes require regression coverage using fixtures from the supported previous format, including the affected read, export, history, backup, and recovery paths. See `docs/ENCRYPTED-NOTEBOOK.md` and `docs/ENCRYPTED-NOTEBOOK.zh-CN.md`; keep their compatibility policies aligned\n\n---\n\n## 2. Project-specific rules\n\n1. **i18n:**\n   - New keys go at the **top** of each `langs/*.json` object; add to every language file (reference `en.json`)\n   - Indent `langs/*.json` with tabs, using one tab per nesting level; do not use spaces for indentation\n   - Exception: inside the `_kernel` object, append new entries at the **end** using the next incremental numeric key\n   - Each language must be properly translated — do NOT copy the same text across all language files\n   - Use three ASCII periods (`...`) for ellipses in all localized strings; do not use Unicode ellipsis characters (`…` or `……`)\n   - Setting description tip strings must not end with a period or equivalent sentence-ending mark (for example `.`, `。`, or `।`)\n   - User-facing error messages must not end with a period or its language-specific equivalent (for example `.`, `。`, or `।`); preserve punctuation between sentences and apply this rule consistently across all languages, including `_kernel` messages\n   - Domains: `ld246.com` only in `zh-CN.json`; use `liuyun.io` in all other languages\n   - In `zh-TW` localization and the Traditional Chinese user guide, consistently translate the content-model term Block as `區塊`, including compounds (e.g. `子區塊`, `程式碼區塊`, `區塊 ID`); never abbreviate it as `塊`, and count blocks with `個區塊`\n   - Translate Block Reference as `區塊引用` and Blockquote as `引述區塊`; preserve non-content-block terms such as `分塊` (data chunks) and `覈取方塊` (Checkbox)\n   - Preserve each `_kernel` message's Go format argument positions and verbs from `en.json`; when translation requires a different order, use explicit argument indexes such as `%[4]s` and `%[3]d`. Preserve `%%` for literal percent signs\n   - After modifying i18n files, run `python scripts/check-lang-keys.py` to verify key completeness and kernel format argument compatibility across all language files\n2. **Cross-platform scripting:**\n   - Do not assume the current shell is Bash, zsh, or PowerShell. Confirm the shell before using shell-specific syntax; otherwise avoid constructs such as `&&`, heredocs, and `/dev/null`\n   - For simple sequences, use separate command calls and set the command working directory instead of chaining `cd` with another command\n   - For multi-step logic, write and run a temporary Node.js or Python script. On Windows, avoid PowerShell unless necessary\n   - Do not pass non-ASCII text through shell pipelines, PowerShell here-strings, `python -c`, or `node -e`; write the text to a UTF-8 file with a file-editing tool and consume that file instead\n3. **Icons:** Do not hand-write SVG; use existing icons from `app/appearance/icons/litheness/icon.js` when possible\n   - If no existing icon is suitable, source one from the official [Lucide icon library](https://lucide.dev/icons/) and adapt only attributes such as stroke width to match the established icon style; preserve the upstream path data\n   - When adding an icon to `app/appearance/icons/litheness/icon.js`, add its preview entry to `app/appearance/icons/index.html` in the same change and keep the order aligned\n4. **User guide:** When editing the user guide, follow `docs/SY-FORMAT.md`\n   - User-guide changes must be synchronized across all four bundled languages: English (`app/guide/20210808180117-6v0mkxr`), Simplified Chinese (`app/guide/20210808180117-czj9bvb`), Traditional Chinese (`app/guide/20211226090932-5lcq56f`), and Japanese (`app/guide/20240530133126-axarxgx`). Do not omit Japanese or discover corresponding pages only by Chinese/English title matching\n   - Before finishing a guide change, enumerate the language directories under `app/guide/` and verify the corresponding section in each language. Translate the content, preserve existing document/block IDs, generate fresh IDs for new blocks, and validate the changed `.sy` JSON. `docs/` developer documentation does not replace the bundled user guide\n   - When a feature adds or changes shortcuts, update the shortcut documentation in the user guide in the same change; if the appropriate section is unclear, ask the user where it should be placed\n   - Outside list items, complete prose paragraphs must end with appropriate punctuation for their language (for example `.`, `。`, `?`, or `！`); use a colon when introducing following content. Headings, navigation/reference directories, short labels, standalone links, and code, formulas, or technical syntax are exceptions and must not receive punctuation mechanically\n   - List item text must not end with a period or equivalent sentence-ending mark (for example `.`, `。`, or `।`)\n   - Represent in-app UI navigation paths as segmented `kbd` text marks: use one `NodeTextMark` with `TextMarkType: \"kbd\"` per navigation level, and place a plain `NodeText` containing ` - ` between adjacent levels\n   - In every language, separate a `kbd` path from adjacent prose with exactly one ASCII space on each side, except at block boundaries, after full-width punctuation, or before any punctuation. Half-width punctuation before the path still requires a space; keep internal ` - ` separators unchanged\n5. **Git:**\n   - Branch policy is repository-specific: in `siyuan-note/siyuan`, use `dev` as the baseline source branch and default working branch. If `dev` is unavailable, use the repository's verified default branch. Do not commit on or push to `master` while `dev` is available; when `dev` is unavailable and `master` is the verified default branch, it is permitted\n   - Select the working branch from the user's requested outcome. For direct commits and explicitly authorized pushes, use the selected baseline as the working branch, commit branch, and push destination unless the user explicitly requests a task branch. When the user explicitly requests an ordinary development pull request, create a task branch from the verified baseline before the first change and use it as the PR head, including when the authorized push destination is a fork; a shared baseline branch such as `dev`, or a fork mirror of it, must not be used as the PR head except for the official release workflow below. GitHub does not support changing an existing PR's head branch, so verify the head before creating the PR. Creating a branch does not authorize committing or pushing without the user's authorization. Do not create or push a task branch merely to isolate changes or because the selected working branch has uncommitted work or is behind the remote\n   - **Official releases:** Before release work, read and follow [docs/RELEASE-VERIFICATION.zh-CN.md](docs/RELEASE-VERIFICATION.zh-CN.md) for preparation, build verification, integration, and history synchronization. Build from a verified `dev` commit and keep it fixed until the release PR is merged; if it advances, stop and reconcile the build inputs. With authorized release integration, use `dev` as the PR head and `master` as the base, the sole exception to the shared-branch PR rule. Use a merge commit; if repository settings prohibit it, report the limitation instead of substituting squash or rebase. Verify the resulting `master` tree against the package build commit before creating a release tag\n   - **Release history and authorization:** Before further development, merge the released `master` commit back into `dev`, preserving both histories and verifying an unchanged released code tree when no subsequent code changes have occurred. Perform this synchronization only with authorization for the merge and explicit authorization for its push. The release exception does not authorize direct commits or pushes to `master`, or unrequested PR merges, tags, uploads, or publication\n   - In other repositories, follow their own branch rules for baseline selection and work; otherwise prefer `dev` when it exists locally or on the remote, and use the repository's actual default branch when `dev` does not exist. Do not extend SiYuan's `master` prohibition to other repositories or assume their default branch is named `master` or `main`\n   - If the intended branch cannot be determined confidently from the user's instructions, the repository's rules, and local or remote branch information, ask the user which branch to use and wait for their answer before editing, committing, or pushing; do not guess or silently choose a fallback\n   - Before editing, verify the repository and current branch. Before every explicitly authorized commit or push, verify the current branch again and, for a push, the destination remote and branch; never rely only on the upstream configuration. If SiYuan is checked out on a branch other than the selected working branch, switch to the selected working branch before making changes, or create and switch to a task branch based on the selected baseline when the user explicitly requests a pull request or task branch; preserve existing work and do not reset or discard it to switch branches\n   - When explicitly asked to commit, follow the style of recent commits (gitmoji prefix + subject, in English)\n   - Append the full issue/PR URL to the end of the commit title (e.g. `https://github.com/siyuan-note/siyuan/issues/<NNN>`, not the `#NNN` short form — it is clickable) only when a related issue exists; never put the URL in the commit body, and do not fabricate one\n   - When creating a pull request, write an English title without a gitmoji shortcode or emoji prefix. Describe the final change in the PR title instead of copying a commit title with its gitmoji prefix or appended issue URL\n6. **GitHub:** Prefer the GitHub CLI (`gh`) for all GitHub operations, including reading issues, comments, pull requests, commits, statuses, and metadata. If `gh` is unavailable or does not support the operation, fall back to the GitHub API or web interface\n   - When creating an issue in this repository, use an English title and a Chinese body whose first paragraph is the corresponding Chinese title; do not use this repository's own issue templates or reproduce their form fields, and write a task-specific body directly. Preserve the relevant details from the discussion, including the problem, reproduction steps, expected and actual behavior, evidence, agreed approach, constraints, compatibility concerns, alternatives and tradeoffs, acceptance criteria, and unresolved questions, as applicable. Remove repetition and unrelated material, but do not omit information needed to understand, evaluate, or implement the issue merely to make it shorter\n   - Before filing an issue or pull request in any other repository, read that repository's own templates first and follow the one matching the task: check `.github/ISSUE_TEMPLATE/` (issue forms and Markdown templates) and the PR template, then fill every section it marks as required. The conventions above — an English title, a Chinese body, and bypassing the templates — apply only to this repository and must not be carried over. Confirm separately whether and where the project accepts issues and pull requests; do not infer whether pull requests are accepted from mirror status or disabled issues. Identify the correct issue tracker (for example, `golang/go` carries gopls issues, not `golang/tools`) and code contribution destination before submitting\n   - When changing the title of an existing issue, keep the replaced title as the first paragraph of the body, separated from the remaining content by a blank line, and keep the rest of the body unchanged\n   - When replying to an issue or pull request, explain the conclusion and its basis with detail proportional to the task. Include relevant implementation decisions, behavior changes, compatibility concerns, and unresolved issues. Report only verification and tests actually performed and the conclusions they support; do not list unperformed tests. Briefly disclose any verification gap that materially limits the conclusion, and never imply broader coverage than was verified. Avoid routine logs and unrelated commands. For implemented changes, invite the user to test the new version and provide feedback. If the issue or pull request body is in Chinese, reply in Chinese; otherwise, reply bilingually in the language of the body and Chinese.\n   - When the selected endpoint supports labels, they may be included in the same create or update payload. Verify the issue or pull request identity and the requested non-label changes, including title, body, state, or base branch as applicable. The exception applies only to labels: do not check whether they were applied or send a follow-up request solely to apply them, and do not claim they were verified; GitHub may silently drop label changes when the actor lacks push access\n   - For GitHub write operations containing non-ASCII text on Windows or when shell encoding is uncertain, use this file-based workflow. Do not use this workflow for ASCII-only requests:\n     1. Create the request payload as UTF-8 JSON with a file-editing tool, not an inline shell command\n     2. Store it in the operating system's temporary directory with a unique name such as `siyuan-gh-<operation>-<timestamp>.json`; do not leave temporary payloads in the repository\n     3. Call the appropriate endpoint with `gh api --method <method> \"<endpoint>\" --input \"<absolute-json-path>\"`\n     4. Inspect the returned resource and read it back with `gh api`. Prefer parsing JSON and comparing its fields with the submitted payload to verify the published text exactly, including line breaks and non-ASCII characters. Normalize CRLF to LF if needed, but preserve meaningful leading/trailing whitespace and blank lines. For `--jq` text output, query one field per call (for example `.title` and `.body`) and remove only the single output line terminator added by the command\n     5. Keep the submitted payload until comparison passes; then delete temporary payload and read-back files and confirm their removal\n   - Prefer capturing `gh` stdout directly in a Node.js or Python script and decoding it as UTF-8. When using PowerShell to save output, explicitly use `Out-File -Encoding utf8`; Windows PowerShell 5.1 defaults to UTF-16LE for `>` and `Out-File`, and its UTF-8 output includes a BOM, so readers must handle that BOM. Explicit file encoding cannot repair text already corrupted by pipeline decoding; use direct process capture in that case\n   - `gh api` applies `--jq` locally after sending the request, so an invalid or failing expression can cause a non-zero exit even when the write succeeded. Omit `--jq` on write commands and use separate read-back calls to verify individual fields. When a write command exits non-zero, confirm the remote state before deciding whether to retry; the write may already have succeeded, and retrying can create duplicate issues or comments or repeat other side effects\n7. **Issue titles:** Whenever the user asks to generate an issue title, provide it in English regardless of the wording of the request, and do not start it with `Fix`. These rules choose title wording from the issue's nature; they are not an instruction to apply GitHub labels\n   - For a bug, objectively describe the problem or symptom instead of writing from a bug-fix perspective\n   - For an improvement to existing functionality, write the title from an improvement perspective and prefer `Improve ...`\n   - For a capability that did not previously exist, write the title from a support perspective and prefer `Support ...`\n   - If the nature is unclear, infer the perspective from the issue content\n8. **LD246:**\n   - When accessing `ld246.com`, set the HTTP `User-Agent` header to `SiYuan-Coding-Agent`\n   - Read post details with `GET https://ld246.com/api/v2/article/{articleId}?p=1`; `data.article` contains the post, and `p` paginates replies. For Markdown, use `GET https://ld246.com/article/raw/{articleId}`\n   - Authenticate both endpoints with `Authorization: token <LD_TOKEN>` using the environment variable; on Windows, check process, user, then machine scope. Do not expose the Token or rely on UA exemptions\n   - Check HTTP status and JSON `code` (`0` means success); report missing credentials or authentication errors\n9. **Configurable entries:**\n   - Treat configurable desktop menu `data-id` values and stable dock entry keys as persisted configuration identifiers. Resolve dock keys through `getDockEntryKey` in `app/src/config/entryVisibility/catalog.ts`: it prefers `data-entry-id`, maps plugin runtime types to stable keys derived from plugin name and dock ID, and falls back to the built-in `data-type`. Do not rename or reuse persisted keys unless the same change migrates existing visibility and order configuration\n   - When adding, removing, renaming, or moving a configurable desktop menu item or dock entry, or changing its identifier mapping, update `app/src/config/entryVisibility/catalog.ts` in the same change, including its type, hierarchy, label, Simple profile default, and default position, and update the related tests\n   - Give every configurable desktop menu separator a stable `data-id` and register it in the catalog as a `separator`. Keep the catalog order aligned with the actual menu declaration order because it defines the built-in order and where new entries are merged into existing custom profiles\n   - Keep parent and child paths aligned with the actual menu hierarchy. Dock entries support visibility, ordering, and movement across the six scopes in `dockOrder.ts`; use that mechanism rather than treating dock entries as ordinary menu siblings. Preserve existing placement, hidden entries, unavailable plugin slots, and compatibility with profiles that have no dock orders\n   - Cover catalog consistency, separator placement, order migration, and plugin-slot preservation in the related tests. Configured menus must not produce leading, trailing, or consecutive separators\n   - The menu `ignore` option controls conditional rendering and must not be used to opt an entry out of visibility or order configuration\n10. **API contracts:**\n    - Follow [docs/API-CONTRACTS.md](docs/API-CONTRACTS.md) when adding or changing kernel HTTP APIs. Define new endpoints in `kernel/apicontract/` and bind their handlers through `contractHandler`; keep contracts synchronized when changing existing endpoints\n    - Treat `docs/API-CONTRACTS.md` and its localized versions as maintenance guides. For routine endpoint or feature changes, do not append endpoint-specific behavior, feature summaries, verification results, or focused test commands. Update these guides only when the contract mechanism, compatibility policy, generation workflow, or test discovery/verification rules change, or when the user explicitly requests a documentation update; keep localized versions aligned\n    - Document endpoint behavior, defaults, constraints, and compatibility in comments on the corresponding contract source declarations. Synchronize plugin-facing explanations in the corresponding maintained declarations in `petal`, following the Petal documentation rule below. Put user-facing feature instructions in the bundled user guide\n    - Do not automatically add newly implemented endpoints to `docs/API.md` or its localized versions (`docs/API.zh-CN.md`, `docs/API.ja.md`); add such documentation only when explicitly requested by the user. Continue maintaining API contracts, generated declarations, and required regression tests. These documentation scope rules take precedence over broader documentation instructions in the maintenance guides\n    - After changing an existing endpoint's request, response, or documented behavior, check its existing reference entries in `docs/API.md`, `docs/API.zh-CN.md`, and `docs/API.ja.md`. Update affected entries together; do not assume the language versions cover identical endpoints. Newly added endpoints remain documented only on request\n    - Preserve existing input compatibility, response variants, authorization, and encrypted notebook lease behavior; cover affected behavior with regression tests\n    - Every route must have a typed contract; remove both the registration and contract when deleting a route, and never bypass contract checks with `any` or type assertions\n    - After contract changes, run `pnpm run api:generate --petal ../../petal` and `pnpm run api:check --petal ../../petal` from `app/`; verify the Petal checkout first using the cross-repo rules below. The example path is relative to the generator's `kernel/` working directory and assumes a sibling `petal` repository; substitute the verified path when different. Synchronize related public declarations in `petal` and do not hand-edit generated declarations or schemas\n    - Run `pnpm run lint` from `app/`, `go test ./apicontract/...` from `kernel/`, and the applicable API compatibility and route coverage tests described in the maintenance document. Verify that existing CI selections and documented commands discover new regression cases; automatic discovery or coverage by an existing full-suite command satisfies this requirement without a documentation edit. Update CI selections and documented commands only when they would otherwise miss the new tests. Report task-specific verification commands and results in the task response or PR description\n\n11. **Desktop and mobile parity:**\n    - When changing functionality shared by desktop and mobile, inspect the corresponding mobile implementation under `app/src/mobile/` and update it in the same task. This includes menus, toolbars, dialogs, interactions, and their event handlers; do not assume that desktop changes automatically apply to mobile\n    - Before finishing, verify both implementations and their shared dependencies, preserving platform-appropriate interactions. If no corresponding mobile feature exists or the change is desktop-only, state why mobile changes are not needed in the task response\n\n12. **UI copy and user guides:** Change wording only when needed, following nearby style and keeping necessary guidance and warnings. Put specific instructions beside the relevant control or in the corresponding guide section; do not overload general descriptions with feature lists or implementation details\n\n13. **Configuration defaults:** Put a new setting's default value in its constructor (`NewAppearance`, `NewEditor`, `NewFileTree`, etc.) and let the shared normalization functions fill missing containers and values. `InitConf` only projects runtime state and runs versioned migrations; do not add a startup function that patches a single default\n    - `conf.json` written by an older version has no key for a new field, and configuration sections unmarshal into zero values, so a plain `bool` is only correct when the default is `false`\n    - A default of `true` requires `*bool` (and a defaulted non-zero enumeration requires `*int`): initialize it in the constructor, keep the `nil` branch in normalization, and preserve an explicitly saved `false`. See `FileTree.CreateDocAtTop`, `Editor.CheckBlockRef`, and `Search.HanSensitive`\n    - Keep a default-`true` plain `bool` only when existing workspaces must stay `false` while new workspaces start `true`, as with `Appearance.MenuMouseScroll`: the constructor supplies the new-workspace value and the zero value supplies the existing-workspace one\n    - `POST /api/setting/set<Section>` merges the request into the current section: omitted fields keep their current value and `null` restores the default; declare `Merge` on `SettingWrite` instead of adding per-field preservation branches. Decoding starts from the section constructor defaults so `null` never falls back to a zero value\n    - Keep `*bool` on the API request and response declaration when the wire format must distinguish `null`, even if the persisted configuration uses a plain `bool`; see `apicontract.SettingFileTree`\n    - When an existing workspace must retain a legacy default that is not the zero value (for example `OCR.Auto`), resolve it in the load path that knows whether `conf.json` existed; do not read that fact from a global inside a constructor or normalization helper. Resolve a segment that exists but lacks the decisive field the same way as a missing segment\n\n---\n\n## 3. Coding conventions\n\n1. **Comments:** Write in Chinese, wrap at 120 characters, and describe what the code does without referencing the old implementation\n2. **Punctuation:** Use language-appropriate punctuation in UI text, comments, user guides, and documentation (e.g. Chinese punctuation ，。：；！？「」 for Chinese). Keep UI text and its punctuation in i18n language files rather than hard-coding them in code\n3. **UI paths:** In all contexts, including code comments, UI text, i18n, user guides, documentation, issue/PR content, and responses, separate navigation levels with a hyphen surrounded by spaces (for example, `设置 - 快捷键 - 通用`); do not use arrow symbols such as `→`\n4. **Markdown:** Do not hand-wrap; keep each line (paragraphs, table rows, list items, etc.) on a single line\n5. **TypeScript/JavaScript:** Semicolons required, use double quotes, indent with spaces\n   - When moving or extracting a symbol into another module, update all affected imports to reference its defining module directly. Do not leave forwarding re-exports in the original module merely to avoid updating callers\n6. **CSS:** Do not use the `:has()` selector because of its performance impact, and prefer a comma-separated selector list over `:is()`, whose extra matching layer adds measurable cost\n   - Expand selector groups instead of grouping them with `:is()` or `:where()`; use `:where()` only to drop specificity from a single selector\n   - Before adding or styling a basic control, inspect and reuse the existing component, markup pattern, and shared styles in `app/src/assets/scss/component/` (for example, `b3-button`, `b3-select`, and `b3-text-field`), including existing modifiers; use `block__icon` for established icon-button patterns\n   - Apply the same reuse-first rule to menus, dialogs, tooltips, and drag interactions: inspect existing implementations and APIs before adding a feature-specific replacement\n   - For single-choice enumerations in menus, use submenu items with the current option checked rather than embedding a select control. Show a right-arrow indicator on the parent item, open its submenu on hover on desktop, and support tapping on mobile. Reuse the existing menu implementation and positioning behavior\n   - Feature styles should describe layout (such as placement, width constraints, gaps, and wrapping), rather than duplicate or override basic control appearance (such as height, padding, typography, colors, borders, shadows, and hover/focus/disabled states). Do not use deeper selectors, inline styles, or `!important` merely to restyle a shared control\n   - If existing controls cannot meet a requirement, explain the concrete need and prefer extending a shared component or modifier when the need is reusable. Keep necessary feature-specific exceptions narrowly scoped; use theme variables for appearance and retain keyboard focus and disabled feedback\n   - Reuse components according to their purpose; do not borrow an unrelated component (for example, menu items for a form) and then cancel its styles. Preserve useful feature classes as theme hooks when switching to shared controls\n   - When reviewing control-style changes, check consistency with shared controls, theme overrides through shared classes, light/dark themes, narrow layouts, and large editor fonts. Preserve necessary layout and touch-target constraints; do not remove all feature styles indiscriminately\n7. **CSS positioning and scrolling:** When changing `position`, `transform`, `contain`, or `overflow` on a shared container, check the effects on descendant positioning reference frames, overlay coverage, and clipping. Prefer a dedicated container when a local control needs a positioning reference. For settings dialog changes, verify detail overlays, the top drag area, and scrollbar placement at different window widths\n\n8. **Custom attributes:**\n   - HTML custom attributes used only for DOM/UI state must use the `data-` prefix without an extra `sy` namespace, for example `data-ui-density`\n   - Custom attributes owned by built-in features and persisted in notebook, document, or block data must use the `custom-sy-` prefix. Preserve existing persisted attribute names and their compatibility; DOM/UI naming changes must not rename these data attributes\n   - Define custom attribute name constants in `app/src/constants.ts`, alongside similar constants in `Constants`, rather than in individual feature modules\n\n9. **Frontend preference storage:** Do not use browser `localStorage` directly, including `window.localStorage` and `globalThis.localStorage`\n   - New user-facing preferences, including settings switches and explicit reader or panel preferences, default to `conf.json`; define them in the corresponding configuration namespace, normalize them in the kernel, and write through `createConfigNamespaceApi().patch`\n   - Reserve `local.json` for growing mappings keyed by document, view, or transaction IDs; transient or session state such as scroll positions, dialog geometry, and restored tabs; usage history and reading positions; and plugin runtime state\n   - Writing frequency does not determine storage: `uiLayout` is already a frequently updated configuration namespace with silent notifications. Neither `conf.json` nor `local.json` participates in data sync; configuration export includes `conf.json`\n   - Read local state from `window.siyuan.storage`; when changing it, update its in-memory value and persist it with `setStorageVal` from `app/src/protyle/util/compatibility.ts`. SiYuan persists these values in the workspace's `data/storage/local.json`; do not use browser `storage` events to observe changes to this store\n\n10. **Frontend platform identification:**\n   - Use the mutually exclusive `body` class names written by `applyPlatformClasses()` in `app/src/protyle/util/compatibility.ts`: `body--mac`, `body--windows`, `body--linux`, `body--ios`, `body--android`, and `body--harmony`\n   - `body--win32` and `body--win32-browser` are retained only for existing third-party themes; use neither in new code, and do not add consumers when changing related styles\n   - Do not derive the frontend platform from the kernel reported container or from `data-backend`: a remote kernel reports its own host, so both differ from the platform of the current frontend\n   - `data-frontend` on the root element reports the frontend form (`desktop`, `desktop-window`, `mobile`, `browser-desktop`, `browser-mobile`) and may be used by built-in styles and third-party theme CSS; `data-backend` on the root element is for third-party JavaScript only and must not appear in built-in CSS\n\n---\n\n## 4. Required toolchain\n\n| Tool | Version | Source of truth |\n|---|---|---|\n| Go | see `go` directive | `kernel/go.mod` |\n| Node (+ pnpm) | see CI matrix | `.github/workflows/cd.yml`, `app/package.json` (`packageManager` field) |\n\n---\n\n## 5. Repository layout\n\n**Architecture:** Go kernel (`kernel/`) + TypeScript frontend (`app/`). Read versions from `kernel/go.mod`, `app/package.json`, `kernel/util/working.go`.\n\nTop level (repo root):\n\n| Path | Contents |\n|---|---|\n| `kernel/` | Go backend — server, data engine, API, all domain logic |\n| `app/` | TypeScript frontend (Electron/web), built by webpack into `app/stage/build/` |\n| `app/appearance/` | Themes, icons, **i18n** (`appearance/langs/*.json`) |\n| `app/stage/` | Build output served by the kernel |\n| `app/changelogs/` | Per-version changelog markdown |\n| `.github/` | Contribution and security policies, issue/PR templates, CI workflows |\n| `scripts/` | Release packaging, changelog generation, language-key checks |\n\n### Major `kernel/` packages (under `kernel/`)\n\n| Package | Responsibility |\n|---|---|\n| `main.go` (`//go:build !mobile`) | Desktop entry point, delegates to `cli/cmd` |\n| `cli/cmd/` | Cobra CLI subcommands |\n| `model/` | Core domain: blocks, transactions, indexing, search, attribute views, export, history, sync, flashcards, AI, CalDAV/CardDAV, auth |\n| `treenode/` | In-memory tree over the Lute AST + `blocktree.db` |\n| `av/` | **Attribute View** (database) engine: values, filters, sorts, layouts (table/kanban/gallery) |\n| `sql/` | **Embedded SQLite** (`siyuan.db`, `history.db`, `asset_content.db`) + FTS5; async index queues |\n| `search/` | FTS tokenizer helpers, CJK conversion (`hanconv.go`) |\n| `bazaar/` | Marketplace: plugins/widgets/themes/icons/templates |\n| `filesys/` | Read/write `.sy` files on disk (via `filelock`) |\n| `server/` | Gin server bootstrap (`serve.go`): middleware, TLS/cmux, WebDAV/CalDAV/CardDAV, WebSocket, MCP |\n| `api/` | HTTP route registration (`router.go::ServeAPI`) + per-area handlers |\n| `conf/` | Configuration structs |\n| `util/` | Cross-cutting: `working.go` (workspace, `Boot()`), `lute.go`, `i18n.go`, `websocket.go` (melody push), `result.go` (API envelope) |\n| `plugin/` | Plugin subsystem (kernel side) |\n| `mcp/` | MCP (Model Context Protocol) server |\n| `agent/` | AI agent runtime |\n| `mobile/`, `harmony/` | `//go:build mobile` gomobile bindings for Android/iOS/HarmonyOS |\n\n### Frontend (`app/src/`) highlights\n\n| Dir | Purpose |\n|---|---|\n| `index.ts` | Main `App` class — boots SPA, opens main WebSocket, handles WS push events |\n| `window/` | Detached Electron window variant |\n| `protyle/` | Block editor; rich-content rendering in `render/`, attribute-view rendering in `render/av/` |\n| `editor/`, `layout/`, `menus/`, `dialog/`, `config/`, `mobile/`, `ai/`, `sync/`, `history/`, `search/`, `card/` | Feature modules |\n| `util/fetch.ts` | `fetchGet`/`fetchPost` — all kernel calls |\n| `layout/Model.ts` | WebSocket client all UI binds to |\n| `constants.ts` | Global constants (version, IDs, storage keys) |\n\nFour webpack configs emit bundles to `app/stage/build/{app,desktop,mobile,export}/`; the kernel's `serveAppearance` selects the app bundle by User-Agent. The `export` bundle is a rendering library (global `Protyle`, entry `app/src/protyle/method.ts`) for code, math, and diagrams. Pages assembled by `app/src/protyle/export/index.ts` load it for desktop PDF preview and standalone exported HTML.\n\n---\n\n## 6. Related repositories (navigation)\n\n| Repo | Role / what to know |\n|---|---|\n| `siyuan-android` / `siyuan-ios` / `siyuan-harmony` | Native apps wrapping the gomobile kernel; read each project's README for platform-specific build, kernel-binding, and integration steps |\n| `siyuan-chrome` | Independent TypeScript browser extension (web clipper); interacts with the running kernel only through the HTTP API in `docs/API.md` |\n| `siyuan-testing` | Playwright end-to-end tests for a running SiYuan instance; test data belongs in the `SiYuan Testing` notebook — see that repository's `AGENTS.md` |\n| `petal` | SiYuan Plugin API declaration (the plugin system is named \"petal\"); consumed by plugins, not a kernel Go dependency |\n| `lute` | Markdown/Kramdown AST engine for the editor and `.sy` format; source of `lute.min.js`. The maintainer's default checkout is `$GOPATH/src/github.com/88250/lute`, not a sibling repo; verify the actual checkout before use |\n| `dejavu` | Data repo / sync engine (encrypted snapshots) |\n| `riff` | Spaced-repetition (SRS) flashcard scheduler |\n| `gulu` / `eventbus` / `filelock` / `httpclient` / `logging` | General utilities, in-process events, file locking, HTTP client, and logging |\n| `go-sqlite3` / `pdfcpu` | Maintainer's forks, pulled in via permanent `replace` in `kernel/go.mod` (keep those) |\n| `epub` / `clipboard` / `go-humanize` / `vitess-sqlparser` / `dataparser` / `encryption` | Smaller Go libraries (export / clipboard / formatting / SQL parse / data parse / crypto) |\n\nAll Go libraries above are dependencies in `kernel/go.mod`. GitHub org: `siyuan-note/*` for the `siyuan-` apps and most libs; `88250/*` for lute, gulu, and the forks (go-sqlite3 / pdfcpu).\n\n### Cross-repo notes\n\n- **Checkout availability:** Before cross-repo work, verify each checkout's path, identity, branch, baseline, and existing changes under its own rules. If a required checkout is unavailable, complete independent work where possible and report the exact synchronization or verification blocker; do not claim the task is fully synchronized. These rules do not authorize automatically creating or downloading another repository\n- **Editing Go dependencies:** To test a local change, add a temporary `replace` in `kernel/go.mod` pointing at your local checkout; **never commit that temporary `replace`**.\n- **Rebuilding `lute.min.js`:** Change `lute`, rebuild with GopherJS, and copy the artifact into `app/stage/protyle/js/lute/`.\n- **Type declarations:** when changing files under `app/src/types/` or other TypeScript declarations and constants exposed to plugins, synchronize the corresponding declarations and constants in the `petal` repository in the same task.\n- **Petal changelog:** Whenever changing files in the `petal` repository, update `petal/CHANGELOG.md` in the same task. Add a concise entry under the current unreleased version before committing the Petal change.\n- **Petal documentation:** Keep `petal/README.md` limited to the project title and the `plugin-sample` link. Do not add feature descriptions, API usage explanations, or code examples to the README. Document API behavior, constraints, and lifecycle in comments on the corresponding API declarations in `petal`; put executable usage examples in the actual source code of `plugin-sample`.\n\n---\n\n## 7. Response style\n\n1. **Language:** Match the user's language; do not mix languages mid-sentence (keep proper nouns / identifiers in their original form)\n","kernel/util/builtin_skills/siyuan-plugin-development/references/verification.md":"# Verification Record\n\nReport only actual evidence for each item: passed, failed, unverified, or not applicable. Completed source code and ZIP files do not replace runtime evidence.\n\n- Source code: implementation matches the confirmed plan, frontend and resource declarations are accurate, and there are no unapproved data effects\n- Static checks: syntax, type, or lint checks actually run and their results; mark as unverified if no parser is available\n- Build: the actual TypeScript command, exit status, and artifacts; not applicable for plain JavaScript\n- ZIP: package path, hash, source revision, file list, and validation results returned by package_local\n- Simulated tests: identify SDK substitutes, DOM environments, or standalone Electron environments; do not describe them as an actual SiYuan instance\n- Installation: authorized target, package hash, revisions before and after installation, and actual native installation results\n- Activation: actual configuration state; successful installation does not imply activation\n- Frontend loading: check entry points and errors separately in desktop Electron and the desktop browser; neither substitutes for the other\n- Functional execution: main operation, error feedback, repeated clicks, unloading after partial initialization, and late asynchronous responses\n- Data writes: target, count, and actual returned status; verify unknown results read-only first to avoid duplicate submissions\n\nIf an actual SiYuan instance, target frontend, or necessary permission is unavailable, explicitly leave the corresponding stages unverified. Do not compensate for a missing test environment by installing into the user's actual environment. Rolling back code does not roll back user data.\n\nRecovery is not guaranteed to handle every process crash automatically. If a temporary file is only partially written, or a conditional create has published the target but has not yet cleaned up the temporary hard link, the tool preserves both names, their bytes, and the logs, and returns result_unknown; manual inspection may be necessary. Do not delete files whose state is uncertain, weaken hard-link checks, or blindly retry to claim that recovery is complete.\n","kernel/util/builtin_skills/siyuan-plugin-development/references/development.md":"# Development References and Boundaries\n\n## Official References\n\n- Plugin sample: https://github.com/siyuan-note/plugin-sample\n- Plugin API declarations: https://github.com/siyuan-note/petal\n- Implementation of the target SiYuan version: https://github.com/siyuan-note/siyuan\n\nVerify the currently running version and target frontend, then use the public APIs for that version. The host loads the frontend plugin entry point index.js; CommonJS uses require(\"siyuan\") and module.exports. Do not assume the host exports an API simply because its type exists in an npm package. Take target frontend values from the current manifest specification instead of blindly reusing an all-platform array.\n\n## Minimum Files and Builds\n\nA minimal frontend package requires plugin.json and a real index.js, together with any i18n, README, index.css, and other resources actually used. The manifest's name, version, minimum host version, and frontend declarations must comply with the current native installer's parsing rules. Publishing metadata requires a real identity; do not invent an author or repository for a local prototype.\n\nexamples/index.js and examples/en_US.json are original, small, dependency-free examples that demonstrate only public entry points and lifecycle handling. They are not a completed plugin for the user; do not treat example text as user requirements. Verify addTopBar, showMessage, and language-file keys against the target version. Register layout-related entry points in onLayoutReady and avoid duplicate registration.\n\nTypeScript produces an installable index.js only through an actually available build toolchain; changing a .ts extension is not a build. package_local only freezes and packages files; it does not build or execute arbitrary project scripts. If build capabilities are unavailable, accurately deliver the source code and blocker details, or switch to plain JavaScript with the user's agreement.\n\n## Imports and Reconfirmation\n\nBefore importing, inspect the candidate source with project_status(taskId, sourcePath, sourceFiles). sourceFiles is an exact allowlist of relative file paths and must match the plan's workflow.files; sourceRevision binds the selected existing safe files, not the entire repository. The host only lists excluded paths without reading contents such as .git, node_modules, or config. An optional path on the allowlist that does not yet exist may be created later; do not treat it as an imported file.\n\nAfter reconfirming a plan for an existing managed project, obtain the current sourceRevision through project_status(taskId), then establish a new checkpoint with prepare_project(taskId, expectedSourceRevision). Do not overwrite the current source code or reimport the previous source. If revisions do not match, first inspect the changes read-only, then handle their actual effects.\n\nPackaging freezes only existing safe files on the confirmed allowlist. Missing optional files may be omitted from the package, but plugin.json, index.js, and resources declared by the manifest must be complete. Do not use optional allowlist entries to bypass entry-point or resource validation.\n\n## Data and Lifecycle\n\nRead and modify notes through public kernel APIs, and check code before reporting success. Do not directly modify .sy files, databases, or the actual data directory. Define the target and scope of effects clearly, access encrypted notebooks under existing permissions, and do not bypass locks or denials.\n\nPrevent duplicate clicks during long-running writes, and retain request and instance state. In onunload, clean up the plugin's own events, timers, and observers, and cancel reads that can be canceled; after an asynchronous response, check that the instance is still valid. Canceling a request or unloading does not guarantee that a server-side write has not already executed. If the result is unknown, verify it read-only first.\n\n## Installation\n\nGenerating a local ZIP does not grant permission to install or activate it. Install through the native local-package interface, bound to the package hash and expected installation-target revision; keep the immutable installation snapshot separate from development source code. Explain the effects of overwriting an enabled plugin before obtaining authorization for an action that may execute code. If the user approves only file delivery, stop at source code and artifacts.\n\nRecover installation data through the existing repo snapshot and recovery tools, respecting the user's sync-ignore rules. Snapshots cover non-ignored code under data/plugins and data under data/storage/petal; they do not include the workspace conf directory or managed-project source code. Managed source code is still restored through restore_project. The agent creates a snapshot before the first local write in each conversation round, rather than creating a separate recovery point for every installation; the installer does not keep a separate plugin-code backup either. A full-repository checkout rolls back all data included in the snapshot, disables sync, and reloads the interface. Single-file recovery overwrites only the selected files and does not delete files added later. Restoring petals.json affects the entire plugin configuration. Verify snapshot contents and recovery scope first, follow the existing tool confirmation requirements, do not automatically check out the entire repository, and do not promise to stop plugin code that is already running.\n","docs/API.md":"**English**\n| [中文](API.zh-CN.md)\n| [日本語](API.ja.md)\n\nPlugin resource declarations, data authorization, and publishing APIs are documented in [Plugin publishing](PLUGIN-PUBLISH.md).\n\n* [Specification](#Specification)\n    * [Parameters and return values](#Parameters-and-return-values)\n    * [TypeScript contracts](#TypeScript-contracts)\n    * [Behavior semantics](#Behavior-semantics)\n    * [Authentication](#Authentication)\n* [Notebooks](#Notebooks)\n    * [List notebooks](#List-notebooks)\n    * [Open a notebook](#Open-a-notebook)\n    * [Close a notebook](#Close-a-notebook)\n    * [Rename a notebook](#Rename-a-notebook)\n    * [Create a notebook](#Create-a-notebook)\n    * [Remove a notebook](#Remove-a-notebook)\n    * [Get notebook configuration](#Get-notebook-configuration)\n    * [Save notebook configuration](#Save-notebook-configuration)\n* [Documents](#Documents)\n    * [Create a document with Markdown](#Create-a-document-with-Markdown)\n    * [Rename a document](#Rename-a-document)\n    * [Remove a document](#Remove-a-document)\n    * [Move documents](#Move-documents)\n    * [Set notebook and document sort values](#Set-notebook-and-document-sort-values)\n    * [Set a document's child document sort mode](#Set-a-documents-child-document-sort-mode)\n    * [Get human-readable path based on path](#Get-human-readable-path-based-on-path)\n    * [Get human-readable path based on ID](#Get-human-readable-path-based-on-ID)\n    * [Get storage path based on ID](#Get-storage-path-based-on-ID)\n    * [Get IDs based on human-readable path](#Get-IDs-based-on-human-readable-path)\n* [Assets](#Assets)\n    * [Upload assets](#Upload-assets)\n* [Blocks](#Blocks)\n    * [Insert blocks](#Insert-blocks)\n    * [Prepend blocks](#Prepend-blocks)\n    * [Append blocks](#Append-blocks)\n    * [Update a block](#Update-a-block)\n    * [Delete a block](#Delete-a-block)\n    * [Move a block](#Move-a-block)\n    * [Fold a block](#Fold-a-block)\n    * [Unfold a block](#Unfold-a-block)\n    * [Get a block kramdown](#Get-a-block-kramdown)\n    * [Get child blocks](#get-child-blocks)\n    * [Transfer block ref](#transfer-block-ref)\n* [Attributes](#Attributes)\n    * [Set block attributes](#Set-block-attributes)\n    * [Get block attributes](#Get-block-attributes)\n* [Database](#Database)\n    * [Render](#Render)\n    * [Get](#Get)\n    * [Get primary key values](#Get-primary-key-values)\n    * [Search](#Search)\n    * [Set a cell value](#Set-a-cell-value)\n    * [Add items](#Add-items)\n    * [Remove items](#Remove-items)\n    * [Change layout](#Change-layout)\n    * [Set grouping](#Set-grouping)\n    * [Get filter and sort](#Get-filter-and-sort)\n    * [Set filter](#Set-filter)\n    * [Set sort](#Set-sort)\n    * [Add a field](#Add-a-field)\n    * [Remove a field](#Remove-a-field)\n    * [Set global field sort](#Set-global-field-sort)\n    * [Set per-view field sort](#Set-per-view-field-sort)\n* [Search](#Search)\n    * [Get saved search criteria](#Get-saved-search-criteria)\n    * [Save a search criterion](#Save-a-search-criterion)\n    * [Remove a search criterion](#Remove-a-search-criterion)\n* [SQL](#SQL)\n    * [Execute SQL query](#Execute-SQL-query)\n    * [Flush transaction](#Flush-transaction)\n* [Templates](#Templates)\n    * [Render a template](#Render-a-template)\n    * [Render Sprig](#Render-Sprig)\n* [File](#File)\n    * [Get file](#Get-file)\n    * [Put file](#Put-file)\n    * [Remove file](#Remove-file)\n    * [Rename file](#Rename-file)\n    * [List files](#List-files)\n* [Export](#Export)\n    * [Export Markdown](#Export-Markdown)\n    * [Export Files and Folders](#Export-files-and-folders)\n* [Conversion](#Conversion)\n    * [Pandoc](#Pandoc)\n* [Notification](#Notification)\n    * [Push message](#Push-message)\n    * [Push error message](#Push-error-message)\n* [Network](#Network)\n    * [Forward proxy](#Forward-proxy)\n        * [JSON forward proxy](#JSON-forward-proxy)\n        * [HTTP forward proxy](#HTTP-forward-proxy)\n        * [WebSocket forward proxy](#WebSocket-forward-proxy)\n        * [EventSource forward proxy](#EventSource-forward-proxy)\n* [System](#System)\n    * [Get boot progress](#Get-boot-progress)\n    * [Get system version](#Get-system-version)\n    * [Get the current time of the system](#Get-the-current-time-of-the-system)\n\n---\n\n## Specification\n\n### Parameters and return values\n\n* Endpoint: `http://127.0.0.1:6806`\n* Unless otherwise stated, API interfaces use the POST method\n* For interfaces that take JSON parameters, the parameter is a JSON string placed in the body, and the header Content-Type is `application/json`\n* Return value\n\n   ````json\n   {\n     \"code\": 0,\n     \"msg\": \"\",\n     \"data\": {}\n   }\n   ````\n\n    * `code`: non-zero for exceptions\n    * `msg`: an empty string under normal circumstances, an error text will be returned under abnormal conditions\n    * `data`: may be `{}`, `[]` or `NULL`, depending on the interface\n\n### TypeScript contracts\n\nThe plugin `fetchPost`, `fetchSyncPost`, and `fetchGet` declarations infer request and response types for migrated API paths from generated kernel contracts. Coverage is expanding and includes system utilities, batch block attributes, tag and bookmark operations, selected block queries, notebook listing, history search, and snapshot operations. Existing untyped endpoints and dynamic URLs remain supported. Check the response code before reading successful data from asynchronous calls, and handle nullable fields explicitly.\n\n```typescript\nimport {fetchSyncPost} from \"siyuan\";\n\nconst response = await fetchSyncPost(\"/api/attr/getBlockAttrs\", {id: blockID});\nif (response.code === 0 && response.data) {\n    const value = response.data[\"custom-value\"];\n}\n```\n\nSee the [generated route declarations](../app/src/types/api/index.d.ts) for exact coverage and the [contract maintenance guide](API-CONTRACTS.md) for generation and compatibility rules. Type declarations do not validate JSON at runtime.\n\n### Behavior semantics\n\n* Only interfaces with dedicated interface sections in this document are public APIs. Other kernel routes and `/api/transactions` operations are internal implementations and provide no compatibility or behavioral stability guarantees unless otherwise stated\n* `code: 0` means the interface reported no error while handling the request. It guarantees only the outcome explicitly documented for that interface; it does not mean that related indexes, caches, WebSocket broadcasts, or sync state have all been updated\n* The meanings of omitted fields, `null`, empty objects, and empty arrays are interface-specific. Whether an object or array replaces, merges with, or partially updates existing state, and whether its order is significant, are also defined by each interface\n* An interface may trim, ignore, complete, or transform input. When its documentation states that a normalized result is returned, callers should use the returned `data` as the actual accepted result\n* Do not infer that an operation is read-only from its name. Persistent side effects and their scope are described by each interface when applicable\n* Repeating the same request is idempotent or safe to retry only when explicitly documented. If a response is interrupted or otherwise indeterminate, read the current state before retrying whenever possible\n\n### Authentication\n\nView the API token in <kbd>Settings - Authentication - API token</kbd>. Use it in the request header: `Authorization: Token xxx`\n\n## Notebooks\n\n### List notebooks\n\n* `/api/notebook/lsNotebooks`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"notebooks\": [\n        {\n          \"id\": \"20210817205410-2kvfpfn\", \n          \"name\": \"Test Notebook\",\n          \"icon\": \"1f41b\",\n          \"sort\": 0,\n          \"closed\": false\n        },\n        {\n          \"id\": \"20210808180117-czj9bvb\",\n          \"name\": \"SiYuan User Guide\",\n          \"icon\": \"1f4d4\",\n          \"sort\": 1,\n          \"closed\": false\n        }\n      ]\n    }\n  }\n  ```\n\n### Open a notebook\n\n* `/api/notebook/openNotebook`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Close a notebook\n\n* `/api/notebook/closeNotebook`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nThe close endpoint validates the notebook ID without trimming whitespace. Typed request and response declarations are generated in `app/src/types/api/index.d.ts` and synchronized to `petal`.\n\n### Rename a notebook\n\n* `/api/notebook/renameNotebook`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\",\n    \"name\": \"New name for notebook\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Create a notebook\n\n* `/api/notebook/createNotebook`\n* Parameters\n\n  ```json\n  {\n    \"name\": \"Notebook name\"\n  }\n  ```\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"notebook\": {\n        \"id\": \"20220126215949-r1wvoch\",\n        \"name\": \"Notebook name\",\n        \"icon\": \"\",\n        \"sort\": 0,\n        \"closed\": false\n      }\n    }\n  }\n  ```\n\n### Remove a notebook\n\n* `/api/notebook/removeNotebook`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Get notebook configuration\n\n* `/api/notebook/getNotebookConf`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210817205410-2kvfpfn\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"box\": \"20210817205410-2kvfpfn\",\n      \"conf\": {\n        \"name\": \"Test Notebook\",\n        \"closed\": false,\n        \"refCreateSavePath\": \"\",\n        \"createDocNameTemplate\": \"\",\n        \"dailyNoteSavePath\": \"/daily note/{{now | date \\\"2006/01\\\"}}/{{now | date \\\"2006-01-02\\\"}}\",\n        \"dailyNoteTemplatePath\": \"\"\n      },\n      \"name\": \"Test Notebook\"\n    }\n  }\n  ```\n\n### Save notebook configuration\n\n* `/api/notebook/setNotebookConf`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210817205410-2kvfpfn\",\n    \"conf\": {\n        \"name\": \"Test Notebook\",\n        \"closed\": false,\n        \"refCreateSavePath\": \"\",\n        \"createDocNameTemplate\": \"\",\n        \"dailyNoteSavePath\": \"/daily note/{{now | date \\\"2006/01\\\"}}/{{now | date \\\"2006-01-02\\\"}}\",\n        \"dailyNoteTemplatePath\": \"\"\n      }\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"name\": \"Test Notebook\",\n      \"closed\": false,\n      \"refCreateSavePath\": \"\",\n      \"createDocNameTemplate\": \"\",\n      \"dailyNoteSavePath\": \"/daily note/{{now | date \\\"2006/01\\\"}}/{{now | date \\\"2006-01-02\\\"}}\",\n      \"dailyNoteTemplatePath\": \"\"\n    }\n  }\n  ```\n\n## Documents\n\n### Create a document with Markdown\n\n* `/api/filetree/createDocWithMd`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210817205410-2kvfpfn\",\n    \"path\": \"/foo/bar\",\n    \"markdown\": \"\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n    * `path`: Document path, which needs to start with `/` and separate levels with `/` (corresponds to the database `hpath` field)\n        * `/` is a hierarchy separator and cannot represent a literal slash in a document title; missing parent documents are created automatically\n        * For example, `/Notes/Programming in C/C++` creates a document titled `C++` under `Programming in C` under `Notes`\n        * Importers should sanitize each title before joining titles into a path, for example by replacing ASCII `/` with full-width `／` (U+FF0F): `/Notes/Programming in C／C++` creates a document titled `Programming in C／C++` under `Notes`. This replacement changes the title text\n    * `markdown`: GFM Markdown content\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"20210914223645-oj2vnx2\"\n  }\n  ```\n\n    * `data`: Created document ID\n    * If you use the same `path` to call this interface repeatedly, the existing document will not be overwritten\n\n### Rename a document\n\n* `/api/filetree/renameDoc`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\",\n    \"path\": \"/20210902210113-0avi12f.sy\",\n    \"title\": \"New document title\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n    * `path`: Document path\n    * `title`: New document title\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nRename a document by `id`:\n\n* `/api/filetree/renameDocByID`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210902210113-0avi12f\",\n    \"title\": \"New document title\"\n  }\n  ```\n\n    * `id`: Document ID\n    * `title`: New document title\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Remove a document\n\n* `/api/filetree/removeDoc`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\",\n    \"path\": \"/20210902210113-0avi12f.sy\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n    * `path`: Document path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nRemove a document by `id`:\n\n* `/api/filetree/removeDocByID`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210902210113-0avi12f\"\n  }\n  ```\n\n    * `id`: Document ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Move documents\n\n* `/api/filetree/moveDocs`\n* Parameters\n\n  ```json\n  {\n    \"fromPaths\": [\"/20210917220056-yxtyl7i.sy\"],\n    \"toNotebook\": \"20210817205410-2kvfpfn\",\n    \"toPath\": \"/\"\n  }\n  ```\n\n    * `fromPaths`: Source paths\n    * `toNotebook`: Target notebook ID\n    * `toPath`: Target path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nMove documents by `id`:\n\n* `/api/filetree/moveDocsByID`\n* Parameters\n\n  ```json\n  {\n    \"fromIDs\": [\"20210917220056-yxtyl7i\"],\n    \"toID\": \"20210817205410-2kvfpfn\"\n  }\n  ```\n\n    * `fromIDs`: Source docs' IDs\n    * `toID`: Target parent doc's ID or notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Reorder documents relative to a sibling\n\n* `/api/filetree/reorderDocs`\n* Parameters\n\n  ```json\n  {\n    \"sourceIDs\": [\"20210917220056-yxtyl7i\"],\n    \"targetID\": \"20210917220057-abcdefg\",\n    \"position\": \"before\"\n  }\n  ```\n\n    * `sourceIDs`: Source document IDs inserted in array order\n    * `targetID`: Target sibling document ID\n    * `position`: `before` or `after`\n    * After moving, every source document must have the same notebook and parent as the target; sorting uses the complete sibling list, including hidden and unlisted documents\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"changed\": true,\n      \"notebook\": \"20210817205410-2kvfpfn\",\n      \"parentPath\": \"/\"\n    }\n  }\n  ```\n\n### Reorder notebooks relative to another notebook\n\n* `/api/notebook/reorder`\n* Parameters\n\n  ```json\n  {\n    \"sourceIDs\": [\"20210817205410-2kvfpfn\"],\n    \"targetID\": \"20210817205411-abcdefg\",\n    \"position\": \"after\"\n  }\n  ```\n\n    * `sourceIDs`: Source notebook IDs inserted in array order\n    * `targetID`: Target notebook ID\n    * `position`: `before` or `after`\n    * Sorting uses the complete notebook list, including closed notebooks\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"changed\": true\n    }\n  }\n  ```\n\n### Set notebook and document sort values\n\n* `/api/filetree/setSort`\n* Parameters\n\n  ```json\n  {\n    \"notebookSorts\": [\n      {\n        \"id\": \"20210817205410-2kvfpfn\",\n        \"sort\": -10\n      }\n    ],\n    \"docSorts\": [\n      {\n        \"id\": \"20210917220056-yxtyl7i\",\n        \"sort\": -8\n      }\n    ]\n  }\n  ```\n\n    * `notebookSorts`: Notebook IDs and their sort values, optional\n    * `docSorts`: Document IDs and their sort values, optional\n    * Documents in `docSorts` must belong to opened and unlocked notebooks; notebook root document IDs are not accepted\n    * At least one of `notebookSorts` and `docSorts` must be non-empty. Array order does not affect sorting; each `sort` value is stored directly\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"notebookIDs\": [\"20210817205410-2kvfpfn\"],\n      \"docIDs\": [\"20210917220056-yxtyl7i\"]\n    }\n  }\n  ```\n\n### Set a document's child document sort mode\n\n* `/api/filetree/setDocSortMode`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210917220056-yxtyl7i\",\n    \"sortMode\": 4\n  }\n  ```\n\n    * `id`: ID of the regular document whose child documents use this sort mode; notebook root document IDs are not accepted\n    * `sortMode`: Integer from `0` through `14`; `null` clears the document's explicit setting and inherits the nearest parent document, notebook, or global document tree sort rule, in that order\n    * Values: `0`/`1` file name ascending/descending; `2`/`3` update time ascending/descending; `4`/`5` natural file name ascending/descending; `6` custom; `7`/`8` reference count ascending/descending; `9`/`10` creation time ascending/descending; `11`/`12` size ascending/descending; `13`/`14` child document count ascending/descending\n    * The declared sort mode is inherited by deeper descendants until another document declares its own sort mode\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"box\": \"20210817205410-2kvfpfn\",\n      \"id\": \"20210917220056-yxtyl7i\",\n      \"path\": \"/20210917220056-yxtyl7i.sy\",\n      \"sortMode\": 4,\n      \"effectiveSortMode\": 4\n    }\n  }\n  ```\n\n    * `sortMode` is the explicit setting (`null` when inheriting), while `effectiveSortMode` is the actual sort mode after inheritance is resolved\n\n### Get human-readable path based on path\n\n* `/api/filetree/getHPathByPath`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\",\n    \"path\": \"/20210917220500-sz588nq/20210917220056-yxtyl7i.sy\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n    * `path`: Document path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"/foo/bar\"\n  }\n  ```\n\n### Get human-readable path based on ID\n\n* `/api/filetree/getHPathByID`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210917220056-yxtyl7i\"\n  }\n  ```\n\n    * `id`: Block ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"/foo/bar\"\n  }\n  ```\n\n### Get storage path based on ID\n\n* `/api/filetree/getPathByID`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210808180320-fqgskfj\"\n  }\n  ```\n\n    * `id`: Block ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n    \"notebook\": \"20210808180117-czj9bvb\",\n    \"path\": \"/20200812220555-lj3enxa/20210808180320-fqgskfj.sy\"\n    }\n  }\n  ```\n\n### Get IDs based on human-readable path\n\n* `/api/filetree/getIDsByHPath`\n* Parameters\n\n  ```json\n  {\n    \"path\": \"/foo/bar\",\n    \"notebook\": \"20210808180117-czj9bvb\"\n  }\n  ```\n\n    * `path`: Human-readable path\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n        \"20200813004931-q4cu8na\"\n    ]\n  }\n  ```\n\n## Assets\n\n### Upload assets\n\n* `/api/asset/upload`\n* The parameter is an HTTP Multipart form\n\n    * `assetsDirPath`: The folder path where assets are stored, with the data folder as the root path, for example:\n        * `\"/assets/\"`: workspace/data/assets/ folder\n        * `\"/assets/sub/\"`: workspace/data/assets/sub/ folder\n\n      Under normal circumstances, it is recommended to use the first method, which is stored in the assets folder of the\n      workspace, since putting in a subdirectory has some side effects, please refer to the assets chapter of the user guide.\n    * `file[]`: Uploaded file list\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"disk full\",\n    \"data\": {\n      \"errFiles\": [\"bar.png\"],\n      \"failedFiles\": [\n        {\n          \"index\": 1,\n          \"name\": \"bar.png\",\n          \"error\": \"disk full\"\n        }\n      ],\n      \"succFiles\": [\n        {\n          \"index\": 0,\n          \"name\": \"foo.png\",\n          \"path\": \"assets/foo-20210719092549-9j5y79r.png\"\n        }\n      ],\n      \"succMap\": {\n        \"foo.png\": \"assets/foo-20210719092549-9j5y79r.png\"\n      }\n    }\n  }\n  ```\n\n    * `errFiles`: List of filenames with errors in upload processing\n    * `failedFiles`: Files explicitly reported as failed. `index` is the file's index in `file[]`, `name` is its upload filename, and `error` is the failure message. This field may omit files that were not attempted or not reported individually; use `succFiles` when each input item must be identified unambiguously\n    * `succFiles`: Successfully processed files in input order. `index` is the file's index in `file[]`, `name` is its upload filename, and `path` is the uploaded asset path. Use this field when a batch can contain duplicate filenames\n    * `succMap`: Compatibility mapping for existing callers. The key is the upload filename and the value is assets/foo-id.png. However, when a batch contains duplicate filenames, only the last item with a given key remains in this map\n\n## Blocks\n\n### Insert blocks\n\n* `/api/block/insertBlock`\n* Parameters\n\n  ```json\n  {\n    \"dataType\": \"markdown\",\n    \"data\": \"foo**bar**{: style=\\\"color: var(--b3-font-color8);\\\"}baz\",\n    \"nextID\": \"\",\n    \"previousID\": \"20211229114650-vrek5x6\",\n    \"parentID\": \"\"\n  }\n  ```\n\n    * `dataType`: The data type to be inserted, the value can be `markdown` or `dom`\n    * `data`: Data to be inserted\n    * `nextID`: The ID of the next block, used to anchor the insertion position\n    * `previousID`: The ID of the previous block, used to anchor the insertion position\n    * `parentID`: The ID of the parent block, used to anchor the insertion position\n\n  `nextID`, `previousID`, and `parentID` must have at least one value, using priority: `nextID` > `previousID` >\n  `parentID`\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"insert\",\n            \"data\": \"<div data-node-id=\\\"20211230115020-g02dfx0\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo<strong style=\\\"color: var(--b3-font-color8);\\\">bar</strong>baz</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\"></div></div>\",\n            \"id\": \"20211230115020-g02dfx0\",\n            \"parentID\": \"\",\n            \"previousID\": \"20211229114650-vrek5x6\",\n            \"retData\": null\n          }\n        ],\n        \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n    * `action.data`: DOM generated by the newly inserted block\n    * `action.id`: ID of the first inserted top-level block. When inserting multiple top-level blocks, send one block per request if each ID is needed\n\n### Prepend blocks\n\n* `/api/block/prependBlock`\n* Parameters\n\n  ```json\n  {\n    \"data\": \"foo**bar**{: style=\\\"color: var(--b3-font-color8);\\\"}baz\",\n    \"dataType\": \"markdown\",\n    \"parentID\": \"20220107173950-7f9m1nb\"\n  }\n  ```\n\n    * `dataType`: The data type to be inserted, the value can be `markdown` or `dom`\n    * `data`: Data to be inserted\n    * `parentID`: The ID of the parent block, used to anchor the insertion position\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"insert\",\n            \"data\": \"<div data-node-id=\\\"20220108003710-hm0x9sc\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo<strong style=\\\"color: var(--b3-font-color8);\\\">bar</strong>baz</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\"></div></div>\",\n            \"id\": \"20220108003710-hm0x9sc\",\n            \"parentID\": \"20220107173950-7f9m1nb\",\n            \"previousID\": \"\",\n            \"retData\": null\n          }\n        ],\n        \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n    * `action.data`: DOM generated by the newly inserted block\n    * `action.id`: ID of the newly inserted block\n\n### Append blocks\n\n* `/api/block/appendBlock`\n* Parameters\n\n  ```json\n  {\n    \"data\": \"foo**bar**{: style=\\\"color: var(--b3-font-color8);\\\"}baz\",\n    \"dataType\": \"markdown\",\n    \"parentID\": \"20220107173950-7f9m1nb\"\n  }\n  ```\n\n    * `dataType`: The data type to be inserted, the value can be `markdown` or `dom`\n    * `data`: Data to be inserted\n    * `parentID`: The ID of the parent block, used to anchor the insertion position\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"insert\",\n            \"data\": \"<div data-node-id=\\\"20220108003642-y2wmpcv\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo<strong style=\\\"color: var(--b3-font-color8);\\\">bar</strong>baz</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\"></div></div>\",\n            \"id\": \"20220108003642-y2wmpcv\",\n            \"parentID\": \"20220107173950-7f9m1nb\",\n            \"previousID\": \"20220108003615-7rk41t1\",\n            \"retData\": null\n          }\n        ],\n        \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n    * `action.data`: DOM generated by the newly inserted block\n    * `action.id`: ID of the first appended top-level block. When appending multiple top-level blocks, send one block per request if each ID is needed\n\n### Update a block\n\n* `/api/block/updateBlock`\n* Parameters\n\n  ```json\n  {\n    \"dataType\": \"markdown\",\n    \"data\": \"foobarbaz\",\n    \"id\": \"20211230161520-querkps\",\n    \"lockType\": false\n  }\n  ```\n\n    * `dataType`: The data type to be updated, the value can be `markdown` or `dom`\n    * `data`: Data to be updated\n    * `id`: ID of the block to be updated\n    * `lockType`: Whether to reject the update when the parsed block type differs from the existing block type; invalid parent-child structures are always rejected, while an empty paragraph can be converted to any valid block type; defaults to `false`\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"update\",\n            \"data\": \"<div data-node-id=\\\"20211230161520-querkps\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo<strong>bar</strong>baz</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\"></div></div>\",\n            \"id\": \"20211230161520-querkps\",\n            \"parentID\": \"\",\n            \"previousID\": \"\",\n            \"retData\": null\n            }\n          ],\n        \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n    * `action.data`: DOM generated by the updated block\n\n### Delete a block\n\n* `/api/block/deleteBlock`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20211230161520-querkps\"\n  }\n  ```\n\n    * `id`: ID of the block to be deleted\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"delete\",\n            \"data\": null,\n            \"id\": \"20211230162439-vtm09qo\",\n            \"parentID\": \"\",\n            \"previousID\": \"\",\n            \"retData\": null\n          }\n        ],\n       \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n### Move a block\n\n* `/api/block/moveBlock`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20230406180530-3o1rqkc\",\n    \"previousID\": \"20230406152734-if5kyx6\",\n    \"parentID\": \"20230404183855-woe52ko\"\n  }\n  ```\n\n    * `id`: Block ID to move\n    * `previousID`: The ID of the previous block, used to anchor the insertion position\n    * `parentID`: The ID of the parent block, used to anchor the insertion position, `previousID` and `parentID` cannot\n      be empty at the same time, if they exist at the same time, `previousID` will be used first\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n        {\n            \"doOperations\": [\n                {\n                    \"action\": \"move\",\n                    \"data\": null,\n                    \"id\": \"20230406180530-3o1rqkc\",\n                    \"parentID\": \"20230404183855-woe52ko\",\n                    \"previousID\": \"20230406152734-if5kyx6\",\n                    \"nextID\": \"\",\n                    \"retData\": null,\n                    \"srcIDs\": null,\n                    \"name\": \"\",\n                    \"type\": \"\"\n                }\n            ],\n            \"undoOperations\": null\n        }\n    ]\n  }\n  ```\n\n### Fold a block\n\n* `/api/block/foldBlock`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20231224160424-2f5680o\"\n  }\n  ```\n\n    * `id`: Block ID to fold\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Unfold a block\n\n* `/api/block/unfoldBlock`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20231224160424-2f5680o\"\n  }\n  ```\n\n    * `id`: Block ID to unfold\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Get a block kramdown\n\n* `/api/block/getBlockKramdown`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20201225220954-dlgzk1o\"\n  }\n  ```\n\n    * `id`: ID of the block to be got\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"id\": \"20201225220954-dlgzk1o\",\n      \"kramdown\": \"* {: id=\\\"20201225220954-e913snx\\\"}Create a new notebook, create a new document under the notebook\\n  {: id=\\\"20210131161940-kfs31q6\\\"}\\n* {: id=\\\"20201225220954-ygz217h\\\"}Enter <kbd>/</kbd> in the editor to trigger the function menu\\n  {: id=\\\"20210131161940-eo0riwq\\\"}\\n* {: id=\\\"20201225220954-875yybt\\\"}((20200924101200-gss5vee \\\"Navigate in the content block\\\")) and ((20200924100906-0u4zfq3 \\\"Window and tab\\\"))\\n  {: id=\\\"20210131161940-b5uow2h\\\"}\"\n    }\n  }\n  ```\n\n* Determinism: The returned Kramdown canonicalizes block-level IAL attribute ordering; the order remains stable while the block content and attributes are unchanged\n\n### Get child blocks\n\n* `/api/block/getChildBlocks`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20230506212712-vt9ajwj\"\n  }\n  ```\n\n    * `id`: Parent block ID\n    * The blocks below a heading are also counted as child blocks\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"id\": \"20230512083858-mjdwkbn\",\n        \"type\": \"h\",\n        \"subType\": \"h1\"\n      },\n      {\n        \"id\": \"20230513213727-thswvfd\",\n        \"type\": \"s\"\n      },\n      {\n        \"id\": \"20230513213633-9lsj4ew\",\n        \"type\": \"l\",\n        \"subType\": \"u\"\n      }\n    ]\n  }\n  ```\n\n### Transfer block ref\n\n* `/api/block/transferBlockRef`\n* Parameters\n\n  ```json\n  {\n    \"fromID\": \"20230612160235-mv6rrh1\",\n    \"toID\": \"20230613093045-uwcomng\",\n    \"refIDs\": [\"20230613092230-cpyimmd\"]\n  }\n  ```\n\n    * `fromID`: Def block ID\n    * `toID`: Target block ID\n    * `refIDs`: Ref block IDs point to def block ID, optional, if not specified, all ref block IDs will be transferred\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n## Attributes\n\n### Set block attributes\n\n* `/api/attr/setBlockAttrs`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210912214605-uhi5gco\",\n    \"attrs\": {\n      \"custom-attr1\": \"line1\\nline2\"\n    }\n  }\n  ```\n\n    * `id`: Block ID\n    * `attrs`: Block attributes, custom attributes must be prefixed with `custom-`\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Get block attributes\n\n* `/api/attr/getBlockAttrs`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210912214605-uhi5gco\"\n  }\n  ```\n\n    * `id`: Block ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"custom-attr1\": \"line1\\nline2\",\n      \"id\": \"20210912214605-uhi5gco\",\n      \"title\": \"PDF Annotation Demo\",\n      \"type\": \"doc\",\n      \"updated\": \"20210916120715\"\n    }\n  }\n  ```\n\n## SQL\n\n### Execute SQL query\n\n* `/api/query/sql`\n* Parameters\n\n  ```json\n  {\n    \"stmt\": \"SELECT * FROM blocks WHERE content LIKE'%content%' LIMIT 7\"\n  }\n  ```\n\n    * `stmt`: SQL statement\n\nWithout an explicit outer `LIMIT`, results default to at most `search.limit` rows (the configured search result limit). Use explicit `LIMIT` and `OFFSET` clauses to paginate, with a stable, unique ordering such as `ORDER BY hpath, id`. However, an explicit outer `LIMIT` overrides the default, including values larger than `search.limit`.\n\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      { \"col\": \"val\" }\n    ],\n    \"limit\": 0,\n    \"truncated\": false\n  }\n  ```\n\nOn success, `data` remains an array. `limit` is the server default limit applied to this query, or `0` when the SQL supplies an explicit outer `LIMIT`; it is not the value of that explicit clause. `truncated` is `true` only when the server default limit omitted at least one result row. Exactly meeting the limit does not imply truncation. For the example above, `LIMIT 7` is explicit, so `limit` is `0` and `truncated` is `false`. These fields are omitted on errors.\n\nNote: To ensure data security, access to this interface is prohibited in Publish Mode.\n\n### Flush transaction\n\n* `/api/sqlite/flushTransaction`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nBlock-writing APIs can return after their block-tree transaction is committed but before asynchronous SQL indexing has caught up. If a subsequent `/api/query/sql` call must observe that write, call `POST /api/sqlite/flushTransaction` after the write and wait for `code: 0` before querying SQL. For example, `getBlockKramdown` reads the block tree and can show newly written content before the SQL index does. Kramdown, DOM, and SQL columns are different representations and are not normalized plain text.\n\n## Templates\n\n### Render a template\n\n* `/api/template/render`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20220724223548-j6g0o87\",\n    \"path\": \"F:\\\\SiYuan\\\\data\\\\templates\\\\foo.md\",\n    \"mode\": \"editorInsert\"\n  }\n  ```\n\n    * `id`: The ID of the document where the rendering is called\n    * `path`: Template file absolute path\n    * `mode`: Optional rendering mode. Currently supports `\"preview\"` and `\"editorInsert\"` only. Preview mode produces a document tree plan without writing files; editor-insert mode produces a plan that can be confirmed and applied with the corresponding editor transaction\n    * When `mode` is omitted, the legacy `preview` Boolean parameter remains supported: `preview: true` is equivalent to `mode: \"preview\"`; otherwise the template is rendered as ordinary content and `createDocTree` is disabled\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"content\": \"<div data-node-id=\\\"20220729234848-dlgsah7\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\" updated=\\\"20220729234840\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\">​</div></div>\",\n      \"path\": \"F:\\\\SiYuan\\\\data\\\\templates\\\\foo.md\",\n      \"docTreePlan\": {\n        \"id\": \"template-plan-token\",\n        \"count\": 2,\n        \"nodes\": [\n          {\n            \"id\": \"20260830150000-abc1234\",\n            \"title\": \"Materials\",\n            \"parentID\": \"20220724223548-j6g0o87\",\n            \"hPath\": \"/Parent/Materials\",\n            \"depth\": 1\n          },\n          {\n            \"id\": \"20260830150001-def5678\",\n            \"title\": \"Review\",\n            \"parentID\": \"20260830150000-abc1234\",\n            \"hPath\": \"/Parent/Materials/Review\",\n            \"depth\": 2\n          }\n        ]\n      }\n    }\n  }\n  ```\n\n    * `docTreePlan`: Present when the template declares a child document tree with `createDocTree`\n        * `id`: Empty in preview mode, which never writes files. In editor-insert mode, this is a short-lived, one-time plan token; after confirmation, submit it as the top-level `templateDocTreePlanID` field of the corresponding transaction object\n        * `count`: Total number of child documents in the plan\n        * `nodes`: Static descriptions of the planned documents\n            * `id`: Planned document ID\n            * `title`: Planned document title\n            * `parentID`: Planned parent document ID\n            * `hPath`: Planned human-readable document path\n            * `depth`: Depth relative to the document where the template is inserted\n        * A single plan can contain at most 128 documents, and its declared child document tree can be at most 16 levels deep. The resulting absolute file tree depth remains subject to the setting that controls whether sub-documents deeper than 7 levels may be created\n\n### Save a document as a template\n\n* `/api/template/docSaveAsTemplate`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20220724223548-j6g0o87\",\n    \"name\": \"Project\",\n    \"overwrite\": false,\n    \"databaseMode\": \"copy\"\n  }\n  ```\n\n    * `id`: Source document ID\n    * `name`: Template name. The kernel sanitizes the name and adds the `.md` extension\n    * `overwrite`: Whether to replace an existing template with the same name. When `false` and the template exists, the response `code` is `1`\n    * `databaseMode`: Optional handling for all database blocks in the document. `copy` (the default) creates independent databases whenever the template is used and clears their block-level context filters; `reference` keeps the existing database IDs and context filters, so rendered blocks are mirrors that share data and view settings with the source databases. Rendering a reference-mode template fails if a source database is unavailable in the target document's encryption boundary\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Render Sprig\n\n* `/api/template/renderSprig`\n* Parameters\n\n  ```json\n  {\n    \"template\": \"/daily note/{{now | date \\\"2006/01\\\"}}/{{now | date \\\"2006-01-02\\\"}}\"\n  }\n  ```\n    * `template`: template content\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"/daily note/2023/03/2023-03-24\"\n  }\n  ```\n\n## File\n\n### Get file\n\n* `/api/file/getFile`\n* Parameters\n\n  ``json {\n  \"path\": \"/data/20210808180117-6v0mkxr/20200923234011-ieuun1p.sy\"\n  }\n  ``\n    * `path`: the file path under the workspace path\n* Return value\n\n    * Response status code `200`: File content\n    * Response status code `202`: Exception information\n\n      ```json\n      {\n        \"code\": 404,\n        \"msg\": \"\",\n        \"data\": null\n      }\n      ```\n\n        * `code`: non-zero for exceptions\n\n            * `-1`: Parameter parsing error\n            * `403`: Permission denied (file is not in the workspace)\n            * `404`: Not Found (file doesn't exist)\n            * `405`: Method Not Allowed (it's a directory)\n            * `500`: Server Error (stat file failed / read file failed)\n        * `msg`: a piece of text describing the error\n\n### Put file\n\n* `/api/file/putFile`\n* The parameter is an HTTP Multipart form\n\n    * `path`: the file path under the workspace path\n    * `isDir`: whether to create a folder, when `true` only create a folder, ignore `file`\n    * `modTime`: last access and modification time, Unix time\n    * `file`: the uploaded file\n* Return value\n\n   ```json\n   {\n     \"code\": 0,\n     \"msg\": \"\",\n     \"data\": null\n   }\n   ```\n\n### Remove file\n\n* `/api/file/removeFile`\n* Parameters\n\n  ```json\n  {\n    \"path\": \"/data/20210808180117-6v0mkxr/20200923234011-ieuun1p.sy\"\n  }\n  ```\n    * `path`: the file path under the workspace path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Rename file\n\n* `/api/file/renameFile`\n* Parameters\n\n  ```json\n  {\n    \"path\": \"/data/assets/image-20230523085812-k3o9t32.png\",\n    \"newPath\": \"/data/assets/test-20230523085812-k3o9t32.png\"\n  }\n  ```\n    * `path`: the file path under the workspace path\n    * `newPath`: the new file path under the workspace path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### List files\n\n* `/api/file/readDir`\n* Parameters\n\n  ```json\n  {\n    \"path\": \"/data/20210808180117-6v0mkxr/20200923234011-ieuun1p\"\n  }\n  ```\n    * `path`: the dir path under the workspace path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"isDir\": true,\n        \"isSymlink\": false,\n        \"name\": \"20210808180303-6yi0dv5\",\n        \"updated\": 1691467624\n      },\n      {\n        \"isDir\": false,\n        \"isSymlink\": false,\n        \"name\": \"20210808180303-6yi0dv5.sy\",\n        \"updated\": 1663298365\n      }\n    ]\n  }\n  ```\n\n## Export\n\n### Export Markdown\n\n* `/api/export/exportMdContent`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"\"\n  }\n  ```\n\n    * `id`: ID of the doc block to export\n    * `preserveTaskMarkers`: Optional boolean for Markdown copying; `true` retains task markers such as `[/]` and `[-]`. Omitted, `null`, or `false` keeps standard Markdown normalization to `[X]` for nonstandard markers\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"hPath\": \"/Please Start Here\",\n      \"content\": \"## 🍫 Content Block\\n\\nIn SiYuan, the only important core concept is...\"\n    }\n  }\n  ```\n\n    * `hPath`: human-readable path\n    * `content`: Markdown content, including automatic heading numbers when enabled for the document (the document setting overrides the editor default); uses the editor numbering format and preserves source-document numbers for partial exports\n\n### Export files and folders\n\n* `/api/export/exportResources`\n* Parameters\n\n  ```json\n  {\n    \"paths\": [\n      \"/conf/appearance/boot\",\n      \"/conf/appearance/langs\",\n      \"/conf/appearance/emojis/conf.json\",\n      \"/conf/appearance/icons/index.html\"\n    ],\n    \"name\": \"zip-file-name\"\n  }\n  ```\n\n    * `paths`: A list of file or folder paths to be exported, the same filename/folder name will be overwritten\n    * `name`: (Optional) The exported file name, which defaults to `export-YYYY-MM-DD_hh-mm-ss.zip` when not set\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"path\": \"temp/export/zip-file-name.zip\"\n    }\n  }\n  ```\n\n    * `path`: The path of `*.zip` file created\n        * The directory structure in `zip-file-name.zip` is as follows:\n            * `zip-file-name`\n                * `boot`\n                * `langs`\n                * `conf.json`\n                * `index.html`\n\n## Conversion\n\n### Pandoc\n\n* `/api/convert/pandoc`\n* Working directory\n    * Executing the pandoc command will set the working directory to `workspace/temp/convert/pandoc/${dir}`\n    * API [`Put file`](#put-file) can be used to write the file to be converted to this directory first\n    * Then call the API for conversion, and the converted file will also be written to this directory\n    * Finally, call the API [`Get file`](#get-file) to get the converted file\n        * Or call the API [Create a document with Markdown](#Create-a-document-with-Markdown)\n        * Or call the internal API `importStdMd` to import the converted folder directly\n* Parameters\n\n  ```json\n  {\n    \"dir\": \"test\",\n    \"args\": [\n      \"--to\", \"markdown_strict-raw_html\",\n      \"foo.epub\",\n      \"-o\", \"foo.md\"\n   ]\n  }\n  ```\n\n    * `args`: Pandoc command line parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n       \"path\": \"/temp/convert/pandoc/test\"\n    }\n  }\n  ```\n    * `path`: the path under the workspace\n\n## Notification\n\n### Push message\n\n* `/api/notification/pushMsg`\n* Parameters\n\n  ```json\n  {\n    \"msg\": \"test\",\n    \"timeout\": 7000\n  }\n  ```\n    * `timeout`: The duration of the message display in milliseconds. This field can be omitted, the default is 7000\n      milliseconds\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n        \"id\": \"62jtmqi\"\n    }\n  }\n  ```\n    * `id`: Message ID\n\n### Push error message\n\n* `/api/notification/pushErrMsg`\n* Parameters\n\n  ```json\n  {\n    \"msg\": \"test\",\n    \"timeout\": 7000\n  }\n  ```\n    * `timeout`: The duration of the message display in milliseconds. This field can be omitted, the default is 7000\n      milliseconds\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n        \"id\": \"qc9znut\"\n    }\n  }\n  ```\n    * `id`: Message ID\n\n## Network\n\n### Forward proxy\n\n#### JSON forward proxy\n\n* `/api/network/forwardProxy`\n* Parameters\n\n  ```json\n  {\n    \"url\": \"https://b3log.org/siyuan/\",\n    \"method\": \"GET\",\n    \"timeout\": 7000,\n    \"contentType\": \"text/html\",\n    \"headers\": [\n        {\n            \"Cookie\": \"\"\n        }\n    ],\n    \"redirect\": true,\n    \"payload\": {},\n    \"payloadEncoding\": \"json\",\n    \"responseEncoding\": \"text\"\n  }\n  ```\n\n    * `url`: URL to forward\n    * `method`: HTTP method, default is `POST`\n    * `timeout`: timeout in milliseconds, default is `7000`\n    * `contentType`: Content-Type, default is `application/json`\n    * `headers`: HTTP request header array; each key-value pair in the objects is set as a request header\n    * `redirect`: Whether to follow redirects, default is `true`, following up to 2 redirects; set it to `false` to disable redirects\n    * `payload`: HTTP payload, object or string\n    * `payloadEncoding`: The encoding scheme used by `payload`, default is `json`; `json` sends `payload` directly, and binary payloads can use the following encoded strings\n\n        * `json`\n        * `base64` | `base64-std`\n        * `base64-url`\n        * `base32` | `base32-std`\n        * `base32-hex`\n        * `hex`\n    * `responseEncoding`: The encoding scheme used by `body` in response data, default is `text`, optional values are as follows\n\n        * `text`\n        * `base64` | `base64-std`\n        * `base64-url`\n        * `base32` | `base32-std`\n        * `base32-hex`\n        * `hex`\n\n      `text` preserves the existing behavior and converts the character set to UTF-8 when applicable. The binary encodings encode the response body before character-set conversion; existing HTTP content decoding behavior, such as gzip decompression, is unchanged.\n\n      The response body is limited to 32 MiB after HTTP content decoding. If the limit is exceeded, the API returns error code `10` without a partial body. Therefore, use `/api/network/proxy` for large files or streaming responses.\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"body\": \"\",\n      \"bodyEncoding\": \"text\",\n      \"contentType\": \"text/html\",\n      \"elapsed\": 1976,\n      \"headers\": {\n      },\n      \"status\": 200,\n      \"url\": \"https://b3log.org/siyuan/\"\n    }\n  }\n  ```\n\n    * `body`: Response body\n    * `bodyEncoding`: The encoding scheme used by `body`; it is consistent with the `responseEncoding` field in the request, default is `text`, optional values are as follows\n\n        * `text`\n        * `base64` | `base64-std`\n        * `base64-url`\n        * `base32` | `base32-std`\n        * `base32-hex`\n        * `hex`\n    * `contentType`: Response header `Content-Type`\n    * `elapsed`: Request duration in milliseconds\n    * `headers`: Response headers returned by the target service\n    * `status`: HTTP status code returned by the target service\n    * `url`: Forwarded URL\n\n#### HTTP forward proxy\n\n* `/api/network/proxy`\n* Request method: any HTTP method\n* Query parameters\n\n    * `u`: Required, target `http` or `https` URL encoded with Go `base64.RawURLEncoding`, which is URL-safe Base64 without `=` padding\n    * `h`: Optional, request header JSON encoded in the same way; the JSON type is `map[string][]string`, for example `{\"Authorization\":[\"Bearer token\"]}`\n    * `t`: Optional, connection timeout in Go `time.ParseDuration` format, for example `30s` or `1500ms`\n* Request body: Forwards the current request body as-is, and forwards the current request's full `Content-Type` header to the target request\n* Return value: Directly returns the target service HTTP status code and response body without wrapping them in `code`, `msg`, or `data`; target response headers are returned with the `Siyuan-Proxy-` prefix, for example `Content-Type` is returned as `Siyuan-Proxy-Content-Type`\n\n#### WebSocket forward proxy\n\n* `/ws/network/proxy`\n* Request method: `GET`\n* Query parameters\n\n    * `u`: Required, target `ws` or `wss` URL encoded with Go `base64.RawURLEncoding`\n    * `h`: Optional, handshake request header JSON encoded in the same way; the JSON type is `map[string][]string`\n    * `t`: Optional, handshake timeout in Go `time.ParseDuration` format, for example `30s` or `1500ms`\n* Return value: Upgrades to WebSocket and then forwards messages bidirectionally; target handshake response headers are returned with the `Siyuan-Proxy-` prefix\n\n#### EventSource forward proxy\n\n* `/es/network/proxy`\n* Request method: `GET`\n* Query parameters\n\n    * `u`: Required, target `http` or `https` URL encoded with Go `base64.RawURLEncoding`\n    * `h`: Optional, request header JSON encoded in the same way; the JSON type is `map[string][]string`\n    * `t`: Optional, connection timeout in Go `time.ParseDuration` format, for example `30s` or `1500ms`\n* Return value: Directly streams the target service HTTP status code and response body without wrapping them in `code`, `msg`, or `data`; if the request headers do not include `Accept`, `text/event-stream` is used automatically; target response headers are returned with the `Siyuan-Proxy-` prefix\n\n## System\n\n### Get boot progress\n\n* `/api/system/bootProgress`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"details\": \"Finishing boot...\",\n      \"progress\": 100\n    }\n  }\n  ```\n\n### Get system version\n\n* `/api/system/version`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"1.3.5\"\n  }\n  ```\n\n### Get the current time of the system\n\n* `/api/system/currentTime`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": 1631850968131\n  }\n  ```\n\n    * `data`: Precision in milliseconds\n\n## Database\n\nA database (internally an \"attribute view\") stores structured data as fields (columns) and items (rows). Each database is identified by an `avID` and can be embedded into a document through one or more database blocks (`blockID`). A single database may contain multiple views (`viewID`) of different layout types: `table`, `list`, `gallery`, and `kanban`"},"files":{"kernel/util/builtin_skills/siyuan-plugin-development/SKILL.md":"---\nname: siyuan-plugin-development\ndescription: Official frontend plugin development workflow. After the user explicitly opts in, clarify missing requirements, confirm a short plan, implement in a managed copy, verify, and package. Installation and activation require separate authorization.\n---\n\n# SiYuan Frontend Plugin Development\n\nThis is a read-only official skill shipped with SiYuan. Its current scope is creating and modifying frontend plugins, using CommonJS JavaScript without third-party runtime dependencies by default. Full development, verification, and recovery of kernel.js are outside this workflow's scope.\n\nReplace example placeholders such as ${PLUGIN_NAME} according to the confirmed plan. Do not treat user-defined variable settings as official content.\n\n## Obtain an Explicit Choice, Then Confirm the Plan\n\n- Use this skill only when the user explicitly asks to develop or modify a plugin. Do not automatically turn ordinary template, document, or theme requests into plugins\n- Initiate the official choice with workflow.action=\"choose\" in the question tool; questions may be an empty array. The server displays a fixed, localized yes/no question. Do not treat ordinary questions, chat text, the model's own consent, or the user's previous answers as permission for this task\n- After the user chooses yes, load the body through skill with action=\"load\", source=\"builtin\", and name=\"siyuan-plugin-development\". Continue only after the server confirms successful loading. Do not read the official body if the user declines or cancels, or if the skill is disabled; user-requested custom development may still proceed under its original permissions\n- Extract known requirements from the current conversation, and ask only about gaps that would change the implementation: functionality, entry points, triggers, target frontend, data-read and data-write effects, and deliverables. Do not repeat questions already answered, guess a notebook, or expand the scope to every platform\n- Submit a short plan through question with workflow.action=\"plan\", bound to the taskId returned by the choice. The full plan must clearly state the plugin name, frontend, functionality and entry points, data changes, delivery scope, import source, and files allowed in the package. The server freezes and displays the entire plan; do not write source code before actual acceptance is confirmed\n- Update the plan and obtain new confirmation when changing a confirmed goal, source-code origin, data effects, or packaging scope. Do not fabricate a taskId, plan digest, or confirmed state; previous questions and versions do not authorize a new task\n\nExample choice call: {\"questions\":[],\"workflow\":{\"action\":\"choose\"}}. Use newTask=true only when the user explicitly requests another plugin; reconsider=true may be used when the user explicitly changes the current workflow choice. Ordinary repeated calls reuse the current choice without asking again.\n\nPlan fields: taskId, proposal (at most 2000 characters), packageName, frontend (one of desktop, desktop-window, browser-desktop, mobile, or browser-mobile), dataEffects and deliverables (at most 1000 characters each), and files (at most 200 exact relative file paths). Before importing an existing plugin, call bazaar's project_status with taskId, sourcePath, and exact sourceFiles; sourceFiles must match the workflow.files awaiting confirmation. The host returns a sourceRevision for the selected existing safe files; include that digest and sourcePath in the plan. Do not implicitly read or hash the entire repository. List excluded paths such as .git, node_modules, and config without reading their contents. Omit source fields when creating a new project. The technical file list is also shown to the user; do not add unapproved files after confirmation. The allowlist may contain optional files that do not yet exist, but this does not permit omitting the entry point, manifest, or declared resources.\n\n## Verify Tools and APIs First\n\nRead references/development.md first. Read every resource through skill with source=\"builtin\" and name=\"siyuan-plugin-development/relative-path\". Check the tools currently available and the target SiYuan version; consult the official plugin-sample, petal, and source code for that version as needed. Do not treat type declarations, outdated README versions, or unreleased APIs as evidence of support in the current host.\n\nUse CommonJS JavaScript by default. Use TypeScript only when the required dependencies and build tools actually exist and are authorized for use; source code is not equivalent to a runnable index.js. Native agents are not guaranteed to have a shell, dependency-installation tools, or arbitrary code execution. If a tool is missing, report the exact blocked step. Do not invent execution results or bypass a tool denial. If the user specifies TypeScript, confirm an alternative approach before switching to JavaScript.\n\n## Implement in a Managed Copy\n\n1. After plan confirmation, call bazaar's prepare_project with taskId so the host creates a source-code copy and baseline checkpoint; do not specify an absolute write directory yourself. For imports, first perform a read-only inspection of the exact sourceFiles, then import a copy after approval of the source and the selected files' baseline digest; leave the original directory unchanged. After reconfirming a plan for an existing managed project, obtain the current source revision with project_status(taskId), then call prepare_project(taskId, expectedSourceRevision) to establish a new checkpoint. This preserves the current source code without reimporting or overwriting it\n2. Use file.write with ifAbsent=true for new files. Before editing existing text, use file.read with withMetadata=true; continue reading with the same expectedRevision and nextOffsetByte until you have fully read the original content that needs changing. truncated=true does not represent a complete file\n3. Prefer file.edit for partial changes, providing expectedRevision and unique, nonoverlapping oldText/newText replacements. Use file.write with expectedRevision only when full replacement is necessary, and only after reading the entire file at that same revision. On a conflict, reread and verify first. Do not blindly retry writes or fall back to overwriting the entire file when a match is missing or ambiguous\n4. Use the paths and project revisions returned by the host; verify the current source code, plan, and checkpoint through bazaar's project_status. Stop writing if a checkpoint cannot be established, and report partial failures accurately. When recovery is needed, use restore_project and respect the current revision and tool confirmation requirements\n5. Do not bypass conditional writes for managed source code through copy, unzip, directory renaming, or HTTP file APIs. Do not touch host-controlled, backup, or artifact directories, or directly overwrite the actual plugin installation directory\n\nCanceling the workflow does not automatically roll back changes. When cancellation, an unknown write result, or partial preparation prevents the ordinary plan from continuing, request recovery-only authorization through question with questions=[], workflow.action=\"recover\", and taskId. The server displays and binds the most recently actually approved plan and its digest.\nAfter confirmation, you may call restore_project, or retry prepare_project only for the same frozen, interrupted preparation. Recovery does not roll back notes, runtime data, or other configuration, and does not authorize further source-code changes; subsequent ordinary source changes still require normal plan confirmation.\n\nUse the minimal entry-point pattern in examples/index.js as a reference and adapt it to the actual functionality. Create entry points through the public Plugin API and modify notes through kernel APIs; never use fs or Electron to modify data files directly. Put UI text in i18n. Handle return codes, missing targets, closed notebooks, repeated clicks, and late responses. onunload must handle partial initialization and clean up the plugin's own listeners, timers, observers, and requests; unloading cannot undo write requests that have already been sent.\n\n## Verify, Package, and Deliver\n\nRead references/verification.md first. Declare only frontends that are actually implemented and verified; do not declare kernels without kernel.js. Do not copy the example's author, repository, funding information, or data permissions, or invent a publishing identity for a local prototype.\n\nRequest a deterministic local ZIP through bazaar's package_local with taskId and the latest expectedSourceRevision. The host freezes only existing safe files on the allowlist. Optional paths may be absent, but plugin.json, index.js, and resources declared by the manifest must exist. It does not execute project scripts or download dependencies. If the entry point or declared resources are missing, or a secret file is detected, first correct the source code or reconfirm the scope. Retain the returned packageHash, sourceRevision, and validation results. Successful packaging does not mean JavaScript syntax, actual installation, frontend loading, or functional execution has passed verification.\n\nReport separate results and evidence for source code, static checks, builds, ZIP packaging, simulated tests, actual installation, activation configuration, frontend loading, and functional execution. Mark builds for plain JavaScript as \"not applicable\"; mark steps that were not run as \"unverified\" and explain why. Tests using SDK substitutes or a standalone Electron DOM environment are not SiYuan integration tests; verify desktop Electron and the desktop browser separately.\n\nInstallation and activation each require the user's explicit authorization for the corresponding action and target. Overwriting an enabled plugin may execute code immediately; a disable notification does not confirm unloading in every frontend, so do not claim it automatically guarantees execution has stopped. Once authorized, use only the native installation interface, bound to the package digest and target revision returned by the host; do not install through generic file writes. Rolling back code does not roll back the user's notes or configuration.\n","AGENTS.md":"# AGENTS.md\n\nSiYuan repository guide. Module path `github.com/siyuan-note/siyuan`, license AGPL-3.0.\n\n---\n\n## 1. Non-negotiable constraints\n\n### Verify the code baseline before repository tasks\n\n- Before each new repository task, verify the repository, current commit, branch, and working-tree state. For work targeting the latest branch, select the baseline source branch using section 2's Git rules and fetch the relevant remote refs before analysis or editing. Respect explicitly requested commits, tags, or pull requests; for a fixed-revision review or historical investigation, inspect that revision without advancing it or synchronizing the working branch. Fetch only if needed to resolve the requested revision. If fetching is unavailable, report the known commit and freshness limit; read-only analysis may continue, but do not claim to have verified the latest remote baseline\n- Follow the repository-specific Git rules for working branches and authorized push destinations. If the user explicitly requests a task branch or the repository's own rules require one, create it from the verified task baseline. Using an isolated worktree does not change the default working branch or authorize pushing to a task branch instead. When synchronizing a development branch, automatically fast-forward it only when its working tree is clean and it is merely behind. Rebase is permitted when synchronizing divergent development branches, provided existing work is preserved; this does not authorize force-pushing rewritten history. Read-only analysis does not itself authorize branch synchronization. If synchronization is blocked, preserve existing work and report the limitation; do not discard changes, automatically stash, create merge commits, or silently substitute another push destination\n- Reuse the verified baseline for follow-ups in the same task; recheck after a substantial pause, a repository or branch change, or when newer commits could affect the task. Unrelated conversation needs no fetch\n\n### Requirement evaluation\n\nBefore implementing a requirement, evaluate its premise and approach with effort proportional to its complexity and risk\n\n1. **Check compliance first:** For major features, review applicable compliance requirements, licensing terms, privacy risks, and costs before technical design\n2. **Check the premise and existing capabilities:** Inspect the relevant implementation, settings, shortcuts, APIs, or plugin capabilities, and reproduce reported behavior when feasible. Check applicable repository rules, encrypted notebook compatibility, the `.sy` format, and API contracts. Distinguish the user's goal and acceptance criteria from a suggested implementation; evaluate whether that implementation addresses the underlying problem. State any limits to verification\n3. **Choose an appropriate approach:** Prefer the smallest sufficient change consistent with existing practices and compatibility requirements. Consider whether an existing capability or a documentation correction already satisfies the need, including the option of making no code change\n4. **Briefly report before editing:** State the conclusion, key evidence, and remaining uncertainty in a progress update before the first change. Discuss alternatives only when they involve meaningful tradeoffs; simple tasks need only a short explanation\n5. **Ask only when a decision is needed:** If the premise is false or implementation is unwarranted, explain the evidence and ask how to proceed unless the user's existing instructions already resolve the next step. Ask before changing the agreed goal or scope, or making an unresolved product, priority, or compatibility tradeoff. Do not silently narrow or broaden the task\n6. **Otherwise proceed directly:** When the requirement is reasonable, implement it without another confirmation round. Choosing a different implementation that meets the agreed goal and acceptance criteria does not itself require approval\n\nUnambiguous mechanical edits with no behavioral or compatibility impact need no separate evaluation round. Continue to follow applicable repository checks; a rename, version bump, or documentation update is not automatically exempt\n\n### Do not hand-edit\n\n- `app/stage/protyle/js/lute/lute.min.js` (built from upstream `88250/lute`)\n- `app/stage/build/**`, `app/src/types/dist/**`\n- `app/changelogs/**` (generated by separate tooling)\n- `app/kernel/SiYuan-Kernel*`, `*.syso`, `kernel/kernel.aar`\n- `app/pandoc/*`\n- Other files marked as generated: edit their source and regenerate them instead. API declarations and schemas come from `kernel/apicontract/`; attribute-view capability declarations come from `kernel/av/capabilities.go`. Before changing either, read the generation instructions and output inventory in [docs/API-CONTRACTS.md](docs/API-CONTRACTS.md#generation-and-verification), including Petal synchronization\n\n### Verification and prohibited operations\n\n1. **Frontend verification:** Do not use `npx webpack` or `pnpm dev` to verify changes. After frontend code changes, run `pnpm run lint` from `app/`; it includes type checks, ESLint automatic fixes, and CSS, menu, and startup-language checks, but not behavioral tests. Review the resulting diff and keep fixes within the task scope without overwriting existing work. In read-only tasks, use applicable non-mutating checks instead, such as `pnpm run typecheck` and ESLint without `--fix` or `--cache`; do not run the auto-fixing lint command\n2. **Frontend build:** Do NOT run `pnpm build` — the developer runs `pnpm dev` manually, and `pnpm build` will conflict with it, producing broken bundles\n3. **Kernel development:** After modifying Go code, run `gofmt`, but do not compile the kernel binary or restart a running kernel; the developer handles both manually. Isolated regression tests and contract generation are allowed; they must not replace the product binary or restart the running instance\n4. **Git:** **NEVER** run `git commit` / `git push` unless explicitly asked — no exceptions\n5. **UI verification in a browser:** Open `/stage/build/desktop/` directly instead of `/`. The kernel selects the frontend bundle by User-Agent in `kernel/server/serve.go`, and a UA containing `Electron` is redirected to the Electron-only `/stage/build/app/`, which fails in a plain browser with `require is not defined`; VSCode's built-in browser sends such a UA. The Electron main window does not expose a remote debugging port by default\n6. **Verification scope:** Run the checks and regression tests applicable to the changed behavior, including non-API Go code, frontend interactions, and scripts. Select scope according to impact and risk; a full suite is not required for every task, and lint or formatting alone does not establish behavioral correctness. Use `app/scripts/test.mjs` and the relevant maintenance guides to identify test discovery and commands. For documentation-only changes, check accuracy, links, language alignment, and the diff rather than running auto-fixing code checks. Report passed, failed, and unrun checks with their limits; do not infer that tests passed solely from a successful CI job or release\n7. **Test synchronization:** Whenever code or files change, handle the corresponding tests and fixtures in the same change rather than leaving them for later\n   - Rename, move, merge, split, or delete a test file together with its source, and keep the file name reflecting the code it covers; a test file whose source no longer exists or whose name no longer matches is stale and must be repaired or removed\n   - When behavior changes intentionally, update the affected assertions, case names, and comments to the new contract instead of dropping the coverage; when behavior is removed, delete only the cases that cover the removed behavior and keep adjacent assertions\n   - Keep test doubles aligned with the code under test: remove mocks of exports, constants, flags, and helpers that no longer exist, and do not preserve assertions that only pass because a stale stub keeps them alive\n   - Do not use a test that already fails for unrelated reasons as permission to leave a new mismatch; state such failures separately and verify whether the task changed their outcome\n   - Merge test files only when they cover the same subject and merging removes duplication without losing cases or discoverability; otherwise keep the current split\n   - Run the affected tests after the synchronization, and name the files and cases that were updated, merged, or deleted\n\n### Encrypted notebook compatibility\n\n- Encrypted notebook development is complete. Maintain it as a released feature\n- Existing encrypted data is the compatibility baseline. Upgrades must preserve access and recovery; do not treat it as disposable development data or require users to delete or recreate it\n- Changes to document, asset, attribute-view, key-envelope, backup, history, or sync formats must retain authenticated reads of supported existing formats or provide a recoverable migration before removing a reader\n- Changes to envelopes, AAD semantics, or key derivation must use an explicit format version and a compatibility plan. Migration must authenticate the source, preserve recovery material, and remain recoverable after failure or interruption; never regenerate MasterSalt or discard keys to work around incompatibility\n- Unknown formats, corruption, and authentication failures must preserve the original data and return an error; compatibility must not bypass authentication or fall back to plaintext. Derived indexes may be rebuilt after the source ciphertext authenticates\n- Format changes require regression coverage using fixtures from the supported previous format, including the affected read, export, history, backup, and recovery paths. See `docs/ENCRYPTED-NOTEBOOK.md` and `docs/ENCRYPTED-NOTEBOOK.zh-CN.md`; keep their compatibility policies aligned\n\n---\n\n## 2. Project-specific rules\n\n1. **i18n:**\n   - New keys go at the **top** of each `langs/*.json` object; add to every language file (reference `en.json`)\n   - Indent `langs/*.json` with tabs, using one tab per nesting level; do not use spaces for indentation\n   - Exception: inside the `_kernel` object, append new entries at the **end** using the next incremental numeric key\n   - Each language must be properly translated — do NOT copy the same text across all language files\n   - Use three ASCII periods (`...`) for ellipses in all localized strings; do not use Unicode ellipsis characters (`…` or `……`)\n   - Setting description tip strings must not end with a period or equivalent sentence-ending mark (for example `.`, `。`, or `।`)\n   - User-facing error messages must not end with a period or its language-specific equivalent (for example `.`, `。`, or `।`); preserve punctuation between sentences and apply this rule consistently across all languages, including `_kernel` messages\n   - Domains: `ld246.com` only in `zh-CN.json`; use `liuyun.io` in all other languages\n   - In `zh-TW` localization and the Traditional Chinese user guide, consistently translate the content-model term Block as `區塊`, including compounds (e.g. `子區塊`, `程式碼區塊`, `區塊 ID`); never abbreviate it as `塊`, and count blocks with `個區塊`\n   - Translate Block Reference as `區塊引用` and Blockquote as `引述區塊`; preserve non-content-block terms such as `分塊` (data chunks) and `覈取方塊` (Checkbox)\n   - Preserve each `_kernel` message's Go format argument positions and verbs from `en.json`; when translation requires a different order, use explicit argument indexes such as `%[4]s` and `%[3]d`. Preserve `%%` for literal percent signs\n   - After modifying i18n files, run `python scripts/check-lang-keys.py` to verify key completeness and kernel format argument compatibility across all language files\n2. **Cross-platform scripting:**\n   - Do not assume the current shell is Bash, zsh, or PowerShell. Confirm the shell before using shell-specific syntax; otherwise avoid constructs such as `&&`, heredocs, and `/dev/null`\n   - For simple sequences, use separate command calls and set the command working directory instead of chaining `cd` with another command\n   - For multi-step logic, write and run a temporary Node.js or Python script. On Windows, avoid PowerShell unless necessary\n   - Do not pass non-ASCII text through shell pipelines, PowerShell here-strings, `python -c`, or `node -e`; write the text to a UTF-8 file with a file-editing tool and consume that file instead\n3. **Icons:** Do not hand-write SVG; use existing icons from `app/appearance/icons/litheness/icon.js` when possible\n   - If no existing icon is suitable, source one from the official [Lucide icon library](https://lucide.dev/icons/) and adapt only attributes such as stroke width to match the established icon style; preserve the upstream path data\n   - When adding an icon to `app/appearance/icons/litheness/icon.js`, add its preview entry to `app/appearance/icons/index.html` in the same change and keep the order aligned\n4. **User guide:** When editing the user guide, follow `docs/SY-FORMAT.md`\n   - User-guide changes must be synchronized across all four bundled languages: English (`app/guide/20210808180117-6v0mkxr`), Simplified Chinese (`app/guide/20210808180117-czj9bvb`), Traditional Chinese (`app/guide/20211226090932-5lcq56f`), and Japanese (`app/guide/20240530133126-axarxgx`). Do not omit Japanese or discover corresponding pages only by Chinese/English title matching\n   - Before finishing a guide change, enumerate the language directories under `app/guide/` and verify the corresponding section in each language. Translate the content, preserve existing document/block IDs, generate fresh IDs for new blocks, and validate the changed `.sy` JSON. `docs/` developer documentation does not replace the bundled user guide\n   - When a feature adds or changes shortcuts, update the shortcut documentation in the user guide in the same change; if the appropriate section is unclear, ask the user where it should be placed\n   - Outside list items, complete prose paragraphs must end with appropriate punctuation for their language (for example `.`, `。`, `?`, or `！`); use a colon when introducing following content. Headings, navigation/reference directories, short labels, standalone links, and code, formulas, or technical syntax are exceptions and must not receive punctuation mechanically\n   - List item text must not end with a period or equivalent sentence-ending mark (for example `.`, `。`, or `।`)\n   - Represent in-app UI navigation paths as segmented `kbd` text marks: use one `NodeTextMark` with `TextMarkType: \"kbd\"` per navigation level, and place a plain `NodeText` containing ` - ` between adjacent levels\n   - In every language, separate a `kbd` path from adjacent prose with exactly one ASCII space on each side, except at block boundaries, after full-width punctuation, or before any punctuation. Half-width punctuation before the path still requires a space; keep internal ` - ` separators unchanged\n5. **Git:**\n   - Branch policy is repository-specific: in `siyuan-note/siyuan`, use `dev` as the baseline source branch and default working branch. If `dev` is unavailable, use the repository's verified default branch. Do not commit on or push to `master` while `dev` is available; when `dev` is unavailable and `master` is the verified default branch, it is permitted\n   - Select the working branch from the user's requested outcome. For direct commits and explicitly authorized pushes, use the selected baseline as the working branch, commit branch, and push destination unless the user explicitly requests a task branch. When the user explicitly requests an ordinary development pull request, create a task branch from the verified baseline before the first change and use it as the PR head, including when the authorized push destination is a fork; a shared baseline branch such as `dev`, or a fork mirror of it, must not be used as the PR head except for the official release workflow below. GitHub does not support changing an existing PR's head branch, so verify the head before creating the PR. Creating a branch does not authorize committing or pushing without the user's authorization. Do not create or push a task branch merely to isolate changes or because the selected working branch has uncommitted work or is behind the remote\n   - **Official releases:** Before release work, read and follow [docs/RELEASE-VERIFICATION.zh-CN.md](docs/RELEASE-VERIFICATION.zh-CN.md) for preparation, build verification, integration, and history synchronization. Build from a verified `dev` commit and keep it fixed until the release PR is merged; if it advances, stop and reconcile the build inputs. With authorized release integration, use `dev` as the PR head and `master` as the base, the sole exception to the shared-branch PR rule. Use a merge commit; if repository settings prohibit it, report the limitation instead of substituting squash or rebase. Verify the resulting `master` tree against the package build commit before creating a release tag\n   - **Release history and authorization:** Before further development, merge the released `master` commit back into `dev`, preserving both histories and verifying an unchanged released code tree when no subsequent code changes have occurred. Perform this synchronization only with authorization for the merge and explicit authorization for its push. The release exception does not authorize direct commits or pushes to `master`, or unrequested PR merges, tags, uploads, or publication\n   - In other repositories, follow their own branch rules for baseline selection and work; otherwise prefer `dev` when it exists locally or on the remote, and use the repository's actual default branch when `dev` does not exist. Do not extend SiYuan's `master` prohibition to other repositories or assume their default branch is named `master` or `main`\n   - If the intended branch cannot be determined confidently from the user's instructions, the repository's rules, and local or remote branch information, ask the user which branch to use and wait for their answer before editing, committing, or pushing; do not guess or silently choose a fallback\n   - Before editing, verify the repository and current branch. Before every explicitly authorized commit or push, verify the current branch again and, for a push, the destination remote and branch; never rely only on the upstream configuration. If SiYuan is checked out on a branch other than the selected working branch, switch to the selected working branch before making changes, or create and switch to a task branch based on the selected baseline when the user explicitly requests a pull request or task branch; preserve existing work and do not reset or discard it to switch branches\n   - When explicitly asked to commit, follow the style of recent commits (gitmoji prefix + subject, in English)\n   - Append the full issue/PR URL to the end of the commit title (e.g. `https://github.com/siyuan-note/siyuan/issues/<NNN>`, not the `#NNN` short form — it is clickable) only when a related issue exists; never put the URL in the commit body, and do not fabricate one\n   - When creating a pull request, write an English title without a gitmoji shortcode or emoji prefix. Describe the final change in the PR title instead of copying a commit title with its gitmoji prefix or appended issue URL\n6. **GitHub:** Prefer the GitHub CLI (`gh`) for all GitHub operations, including reading issues, comments, pull requests, commits, statuses, and metadata. If `gh` is unavailable or does not support the operation, fall back to the GitHub API or web interface\n   - When creating an issue in this repository, use an English title and a Chinese body whose first paragraph is the corresponding Chinese title; do not use this repository's own issue templates or reproduce their form fields, and write a task-specific body directly. Preserve the relevant details from the discussion, including the problem, reproduction steps, expected and actual behavior, evidence, agreed approach, constraints, compatibility concerns, alternatives and tradeoffs, acceptance criteria, and unresolved questions, as applicable. Remove repetition and unrelated material, but do not omit information needed to understand, evaluate, or implement the issue merely to make it shorter\n   - Before filing an issue or pull request in any other repository, read that repository's own templates first and follow the one matching the task: check `.github/ISSUE_TEMPLATE/` (issue forms and Markdown templates) and the PR template, then fill every section it marks as required. The conventions above — an English title, a Chinese body, and bypassing the templates — apply only to this repository and must not be carried over. Confirm separately whether and where the project accepts issues and pull requests; do not infer whether pull requests are accepted from mirror status or disabled issues. Identify the correct issue tracker (for example, `golang/go` carries gopls issues, not `golang/tools`) and code contribution destination before submitting\n   - When changing the title of an existing issue, keep the replaced title as the first paragraph of the body, separated from the remaining content by a blank line, and keep the rest of the body unchanged\n   - When replying to an issue or pull request, explain the conclusion and its basis with detail proportional to the task. Include relevant implementation decisions, behavior changes, compatibility concerns, and unresolved issues. Report only verification and tests actually performed and the conclusions they support; do not list unperformed tests. Briefly disclose any verification gap that materially limits the conclusion, and never imply broader coverage than was verified. Avoid routine logs and unrelated commands. For implemented changes, invite the user to test the new version and provide feedback. If the issue or pull request body is in Chinese, reply in Chinese; otherwise, reply bilingually in the language of the body and Chinese.\n   - When the selected endpoint supports labels, they may be included in the same create or update payload. Verify the issue or pull request identity and the requested non-label changes, including title, body, state, or base branch as applicable. The exception applies only to labels: do not check whether they were applied or send a follow-up request solely to apply them, and do not claim they were verified; GitHub may silently drop label changes when the actor lacks push access\n   - For GitHub write operations containing non-ASCII text on Windows or when shell encoding is uncertain, use this file-based workflow. Do not use this workflow for ASCII-only requests:\n     1. Create the request payload as UTF-8 JSON with a file-editing tool, not an inline shell command\n     2. Store it in the operating system's temporary directory with a unique name such as `siyuan-gh-<operation>-<timestamp>.json`; do not leave temporary payloads in the repository\n     3. Call the appropriate endpoint with `gh api --method <method> \"<endpoint>\" --input \"<absolute-json-path>\"`\n     4. Inspect the returned resource and read it back with `gh api`. Prefer parsing JSON and comparing its fields with the submitted payload to verify the published text exactly, including line breaks and non-ASCII characters. Normalize CRLF to LF if needed, but preserve meaningful leading/trailing whitespace and blank lines. For `--jq` text output, query one field per call (for example `.title` and `.body`) and remove only the single output line terminator added by the command\n     5. Keep the submitted payload until comparison passes; then delete temporary payload and read-back files and confirm their removal\n   - Prefer capturing `gh` stdout directly in a Node.js or Python script and decoding it as UTF-8. When using PowerShell to save output, explicitly use `Out-File -Encoding utf8`; Windows PowerShell 5.1 defaults to UTF-16LE for `>` and `Out-File`, and its UTF-8 output includes a BOM, so readers must handle that BOM. Explicit file encoding cannot repair text already corrupted by pipeline decoding; use direct process capture in that case\n   - `gh api` applies `--jq` locally after sending the request, so an invalid or failing expression can cause a non-zero exit even when the write succeeded. Omit `--jq` on write commands and use separate read-back calls to verify individual fields. When a write command exits non-zero, confirm the remote state before deciding whether to retry; the write may already have succeeded, and retrying can create duplicate issues or comments or repeat other side effects\n7. **Issue titles:** Whenever the user asks to generate an issue title, provide it in English regardless of the wording of the request, and do not start it with `Fix`. These rules choose title wording from the issue's nature; they are not an instruction to apply GitHub labels\n   - For a bug, objectively describe the problem or symptom instead of writing from a bug-fix perspective\n   - For an improvement to existing functionality, write the title from an improvement perspective and prefer `Improve ...`\n   - For a capability that did not previously exist, write the title from a support perspective and prefer `Support ...`\n   - If the nature is unclear, infer the perspective from the issue content\n8. **LD246:**\n   - When accessing `ld246.com`, set the HTTP `User-Agent` header to `SiYuan-Coding-Agent`\n   - Read post details with `GET https://ld246.com/api/v2/article/{articleId}?p=1`; `data.article` contains the post, and `p` paginates replies. For Markdown, use `GET https://ld246.com/article/raw/{articleId}`\n   - Authenticate both endpoints with `Authorization: token <LD_TOKEN>` using the environment variable; on Windows, check process, user, then machine scope. Do not expose the Token or rely on UA exemptions\n   - Check HTTP status and JSON `code` (`0` means success); report missing credentials or authentication errors\n9. **Configurable entries:**\n   - Treat configurable desktop menu `data-id` values and stable dock entry keys as persisted configuration identifiers. Resolve dock keys through `getDockEntryKey` in `app/src/config/entryVisibility/catalog.ts`: it prefers `data-entry-id`, maps plugin runtime types to stable keys derived from plugin name and dock ID, and falls back to the built-in `data-type`. Do not rename or reuse persisted keys unless the same change migrates existing visibility and order configuration\n   - When adding, removing, renaming, or moving a configurable desktop menu item or dock entry, or changing its identifier mapping, update `app/src/config/entryVisibility/catalog.ts` in the same change, including its type, hierarchy, label, Simple profile default, and default position, and update the related tests\n   - Give every configurable desktop menu separator a stable `data-id` and register it in the catalog as a `separator`. Keep the catalog order aligned with the actual menu declaration order because it defines the built-in order and where new entries are merged into existing custom profiles\n   - Keep parent and child paths aligned with the actual menu hierarchy. Dock entries support visibility, ordering, and movement across the six scopes in `dockOrder.ts`; use that mechanism rather than treating dock entries as ordinary menu siblings. Preserve existing placement, hidden entries, unavailable plugin slots, and compatibility with profiles that have no dock orders\n   - Cover catalog consistency, separator placement, order migration, and plugin-slot preservation in the related tests. Configured menus must not produce leading, trailing, or consecutive separators\n   - The menu `ignore` option controls conditional rendering and must not be used to opt an entry out of visibility or order configuration\n10. **API contracts:**\n    - Follow [docs/API-CONTRACTS.md](docs/API-CONTRACTS.md) when adding or changing kernel HTTP APIs. Define new endpoints in `kernel/apicontract/` and bind their handlers through `contractHandler`; keep contracts synchronized when changing existing endpoints\n    - Treat `docs/API-CONTRACTS.md` and its localized versions as maintenance guides. For routine endpoint or feature changes, do not append endpoint-specific behavior, feature summaries, verification results, or focused test commands. Update these guides only when the contract mechanism, compatibility policy, generation workflow, or test discovery/verification rules change, or when the user explicitly requests a documentation update; keep localized versions aligned\n    - Document endpoint behavior, defaults, constraints, and compatibility in comments on the corresponding contract source declarations. Synchronize plugin-facing explanations in the corresponding maintained declarations in `petal`, following the Petal documentation rule below. Put user-facing feature instructions in the bundled user guide\n    - Do not automatically add newly implemented endpoints to `docs/API.md` or its localized versions (`docs/API.zh-CN.md`, `docs/API.ja.md`); add such documentation only when explicitly requested by the user. Continue maintaining API contracts, generated declarations, and required regression tests. These documentation scope rules take precedence over broader documentation instructions in the maintenance guides\n    - After changing an existing endpoint's request, response, or documented behavior, check its existing reference entries in `docs/API.md`, `docs/API.zh-CN.md`, and `docs/API.ja.md`. Update affected entries together; do not assume the language versions cover identical endpoints. Newly added endpoints remain documented only on request\n    - Preserve existing input compatibility, response variants, authorization, and encrypted notebook lease behavior; cover affected behavior with regression tests\n    - Every route must have a typed contract; remove both the registration and contract when deleting a route, and never bypass contract checks with `any` or type assertions\n    - After contract changes, run `pnpm run api:generate --petal ../../petal` and `pnpm run api:check --petal ../../petal` from `app/`; verify the Petal checkout first using the cross-repo rules below. The example path is relative to the generator's `kernel/` working directory and assumes a sibling `petal` repository; substitute the verified path when different. Synchronize related public declarations in `petal` and do not hand-edit generated declarations or schemas\n    - Run `pnpm run lint` from `app/`, `go test ./apicontract/...` from `kernel/`, and the applicable API compatibility and route coverage tests described in the maintenance document. Verify that existing CI selections and documented commands discover new regression cases; automatic discovery or coverage by an existing full-suite command satisfies this requirement without a documentation edit. Update CI selections and documented commands only when they would otherwise miss the new tests. Report task-specific verification commands and results in the task response or PR description\n\n11. **Desktop and mobile parity:**\n    - When changing functionality shared by desktop and mobile, inspect the corresponding mobile implementation under `app/src/mobile/` and update it in the same task. This includes menus, toolbars, dialogs, interactions, and their event handlers; do not assume that desktop changes automatically apply to mobile\n    - Before finishing, verify both implementations and their shared dependencies, preserving platform-appropriate interactions. If no corresponding mobile feature exists or the change is desktop-only, state why mobile changes are not needed in the task response\n\n12. **UI copy and user guides:** Change wording only when needed, following nearby style and keeping necessary guidance and warnings. Put specific instructions beside the relevant control or in the corresponding guide section; do not overload general descriptions with feature lists or implementation details\n\n13. **Configuration defaults:** Put a new setting's default value in its constructor (`NewAppearance`, `NewEditor`, `NewFileTree`, etc.) and let the shared normalization functions fill missing containers and values. `InitConf` only projects runtime state and runs versioned migrations; do not add a startup function that patches a single default\n    - `conf.json` written by an older version has no key for a new field, and configuration sections unmarshal into zero values, so a plain `bool` is only correct when the default is `false`\n    - A default of `true` requires `*bool` (and a defaulted non-zero enumeration requires `*int`): initialize it in the constructor, keep the `nil` branch in normalization, and preserve an explicitly saved `false`. See `FileTree.CreateDocAtTop`, `Editor.CheckBlockRef`, and `Search.HanSensitive`\n    - Keep a default-`true` plain `bool` only when existing workspaces must stay `false` while new workspaces start `true`, as with `Appearance.MenuMouseScroll`: the constructor supplies the new-workspace value and the zero value supplies the existing-workspace one\n    - `POST /api/setting/set<Section>` merges the request into the current section: omitted fields keep their current value and `null` restores the default; declare `Merge` on `SettingWrite` instead of adding per-field preservation branches. Decoding starts from the section constructor defaults so `null` never falls back to a zero value\n    - Keep `*bool` on the API request and response declaration when the wire format must distinguish `null`, even if the persisted configuration uses a plain `bool`; see `apicontract.SettingFileTree`\n    - When an existing workspace must retain a legacy default that is not the zero value (for example `OCR.Auto`), resolve it in the load path that knows whether `conf.json` existed; do not read that fact from a global inside a constructor or normalization helper. Resolve a segment that exists but lacks the decisive field the same way as a missing segment\n\n---\n\n## 3. Coding conventions\n\n1. **Comments:** Write in Chinese, wrap at 120 characters, and describe what the code does without referencing the old implementation\n2. **Punctuation:** Use language-appropriate punctuation in UI text, comments, user guides, and documentation (e.g. Chinese punctuation ，。：；！？「」 for Chinese). Keep UI text and its punctuation in i18n language files rather than hard-coding them in code\n3. **UI paths:** In all contexts, including code comments, UI text, i18n, user guides, documentation, issue/PR content, and responses, separate navigation levels with a hyphen surrounded by spaces (for example, `设置 - 快捷键 - 通用`); do not use arrow symbols such as `→`\n4. **Markdown:** Do not hand-wrap; keep each line (paragraphs, table rows, list items, etc.) on a single line\n5. **TypeScript/JavaScript:** Semicolons required, use double quotes, indent with spaces\n   - When moving or extracting a symbol into another module, update all affected imports to reference its defining module directly. Do not leave forwarding re-exports in the original module merely to avoid updating callers\n6. **CSS:** Do not use the `:has()` selector because of its performance impact, and prefer a comma-separated selector list over `:is()`, whose extra matching layer adds measurable cost\n   - Expand selector groups instead of grouping them with `:is()` or `:where()`; use `:where()` only to drop specificity from a single selector\n   - Before adding or styling a basic control, inspect and reuse the existing component, markup pattern, and shared styles in `app/src/assets/scss/component/` (for example, `b3-button`, `b3-select`, and `b3-text-field`), including existing modifiers; use `block__icon` for established icon-button patterns\n   - Apply the same reuse-first rule to menus, dialogs, tooltips, and drag interactions: inspect existing implementations and APIs before adding a feature-specific replacement\n   - For single-choice enumerations in menus, use submenu items with the current option checked rather than embedding a select control. Show a right-arrow indicator on the parent item, open its submenu on hover on desktop, and support tapping on mobile. Reuse the existing menu implementation and positioning behavior\n   - Feature styles should describe layout (such as placement, width constraints, gaps, and wrapping), rather than duplicate or override basic control appearance (such as height, padding, typography, colors, borders, shadows, and hover/focus/disabled states). Do not use deeper selectors, inline styles, or `!important` merely to restyle a shared control\n   - If existing controls cannot meet a requirement, explain the concrete need and prefer extending a shared component or modifier when the need is reusable. Keep necessary feature-specific exceptions narrowly scoped; use theme variables for appearance and retain keyboard focus and disabled feedback\n   - Reuse components according to their purpose; do not borrow an unrelated component (for example, menu items for a form) and then cancel its styles. Preserve useful feature classes as theme hooks when switching to shared controls\n   - When reviewing control-style changes, check consistency with shared controls, theme overrides through shared classes, light/dark themes, narrow layouts, and large editor fonts. Preserve necessary layout and touch-target constraints; do not remove all feature styles indiscriminately\n7. **CSS positioning and scrolling:** When changing `position`, `transform`, `contain`, or `overflow` on a shared container, check the effects on descendant positioning reference frames, overlay coverage, and clipping. Prefer a dedicated container when a local control needs a positioning reference. For settings dialog changes, verify detail overlays, the top drag area, and scrollbar placement at different window widths\n\n8. **Custom attributes:**\n   - HTML custom attributes used only for DOM/UI state must use the `data-` prefix without an extra `sy` namespace, for example `data-ui-density`\n   - Custom attributes owned by built-in features and persisted in notebook, document, or block data must use the `custom-sy-` prefix. Preserve existing persisted attribute names and their compatibility; DOM/UI naming changes must not rename these data attributes\n   - Define custom attribute name constants in `app/src/constants.ts`, alongside similar constants in `Constants`, rather than in individual feature modules\n\n9. **Frontend preference storage:** Do not use browser `localStorage` directly, including `window.localStorage` and `globalThis.localStorage`\n   - New user-facing preferences, including settings switches and explicit reader or panel preferences, default to `conf.json`; define them in the corresponding configuration namespace, normalize them in the kernel, and write through `createConfigNamespaceApi().patch`\n   - Reserve `local.json` for growing mappings keyed by document, view, or transaction IDs; transient or session state such as scroll positions, dialog geometry, and restored tabs; usage history and reading positions; and plugin runtime state\n   - Writing frequency does not determine storage: `uiLayout` is already a frequently updated configuration namespace with silent notifications. Neither `conf.json` nor `local.json` participates in data sync; configuration export includes `conf.json`\n   - Read local state from `window.siyuan.storage`; when changing it, update its in-memory value and persist it with `setStorageVal` from `app/src/protyle/util/compatibility.ts`. SiYuan persists these values in the workspace's `data/storage/local.json`; do not use browser `storage` events to observe changes to this store\n\n10. **Frontend platform identification:**\n   - Use the mutually exclusive `body` class names written by `applyPlatformClasses()` in `app/src/protyle/util/compatibility.ts`: `body--mac`, `body--windows`, `body--linux`, `body--ios`, `body--android`, and `body--harmony`\n   - `body--win32` and `body--win32-browser` are retained only for existing third-party themes; use neither in new code, and do not add consumers when changing related styles\n   - Do not derive the frontend platform from the kernel reported container or from `data-backend`: a remote kernel reports its own host, so both differ from the platform of the current frontend\n   - `data-frontend` on the root element reports the frontend form (`desktop`, `desktop-window`, `mobile`, `browser-desktop`, `browser-mobile`) and may be used by built-in styles and third-party theme CSS; `data-backend` on the root element is for third-party JavaScript only and must not appear in built-in CSS\n\n---\n\n## 4. Required toolchain\n\n| Tool | Version | Source of truth |\n|---|---|---|\n| Go | see `go` directive | `kernel/go.mod` |\n| Node (+ pnpm) | see CI matrix | `.github/workflows/cd.yml`, `app/package.json` (`packageManager` field) |\n\n---\n\n## 5. Repository layout\n\n**Architecture:** Go kernel (`kernel/`) + TypeScript frontend (`app/`). Read versions from `kernel/go.mod`, `app/package.json`, `kernel/util/working.go`.\n\nTop level (repo root):\n\n| Path | Contents |\n|---|---|\n| `kernel/` | Go backend — server, data engine, API, all domain logic |\n| `app/` | TypeScript frontend (Electron/web), built by webpack into `app/stage/build/` |\n| `app/appearance/` | Themes, icons, **i18n** (`appearance/langs/*.json`) |\n| `app/stage/` | Build output served by the kernel |\n| `app/changelogs/` | Per-version changelog markdown |\n| `.github/` | Contribution and security policies, issue/PR templates, CI workflows |\n| `scripts/` | Release packaging, changelog generation, language-key checks |\n\n### Major `kernel/` packages (under `kernel/`)\n\n| Package | Responsibility |\n|---|---|\n| `main.go` (`//go:build !mobile`) | Desktop entry point, delegates to `cli/cmd` |\n| `cli/cmd/` | Cobra CLI subcommands |\n| `model/` | Core domain: blocks, transactions, indexing, search, attribute views, export, history, sync, flashcards, AI, CalDAV/CardDAV, auth |\n| `treenode/` | In-memory tree over the Lute AST + `blocktree.db` |\n| `av/` | **Attribute View** (database) engine: values, filters, sorts, layouts (table/kanban/gallery) |\n| `sql/` | **Embedded SQLite** (`siyuan.db`, `history.db`, `asset_content.db`) + FTS5; async index queues |\n| `search/` | FTS tokenizer helpers, CJK conversion (`hanconv.go`) |\n| `bazaar/` | Marketplace: plugins/widgets/themes/icons/templates |\n| `filesys/` | Read/write `.sy` files on disk (via `filelock`) |\n| `server/` | Gin server bootstrap (`serve.go`): middleware, TLS/cmux, WebDAV/CalDAV/CardDAV, WebSocket, MCP |\n| `api/` | HTTP route registration (`router.go::ServeAPI`) + per-area handlers |\n| `conf/` | Configuration structs |\n| `util/` | Cross-cutting: `working.go` (workspace, `Boot()`), `lute.go`, `i18n.go`, `websocket.go` (melody push), `result.go` (API envelope) |\n| `plugin/` | Plugin subsystem (kernel side) |\n| `mcp/` | MCP (Model Context Protocol) server |\n| `agent/` | AI agent runtime |\n| `mobile/`, `harmony/` | `//go:build mobile` gomobile bindings for Android/iOS/HarmonyOS |\n\n### Frontend (`app/src/`) highlights\n\n| Dir | Purpose |\n|---|---|\n| `index.ts` | Main `App` class — boots SPA, opens main WebSocket, handles WS push events |\n| `window/` | Detached Electron window variant |\n| `protyle/` | Block editor; rich-content rendering in `render/`, attribute-view rendering in `render/av/` |\n| `editor/`, `layout/`, `menus/`, `dialog/`, `config/`, `mobile/`, `ai/`, `sync/`, `history/`, `search/`, `card/` | Feature modules |\n| `util/fetch.ts` | `fetchGet`/`fetchPost` — all kernel calls |\n| `layout/Model.ts` | WebSocket client all UI binds to |\n| `constants.ts` | Global constants (version, IDs, storage keys) |\n\nFour webpack configs emit bundles to `app/stage/build/{app,desktop,mobile,export}/`; the kernel's `serveAppearance` selects the app bundle by User-Agent. The `export` bundle is a rendering library (global `Protyle`, entry `app/src/protyle/method.ts`) for code, math, and diagrams. Pages assembled by `app/src/protyle/export/index.ts` load it for desktop PDF preview and standalone exported HTML.\n\n---\n\n## 6. Related repositories (navigation)\n\n| Repo | Role / what to know |\n|---|---|\n| `siyuan-android` / `siyuan-ios` / `siyuan-harmony` | Native apps wrapping the gomobile kernel; read each project's README for platform-specific build, kernel-binding, and integration steps |\n| `siyuan-chrome` | Independent TypeScript browser extension (web clipper); interacts with the running kernel only through the HTTP API in `docs/API.md` |\n| `siyuan-testing` | Playwright end-to-end tests for a running SiYuan instance; test data belongs in the `SiYuan Testing` notebook — see that repository's `AGENTS.md` |\n| `petal` | SiYuan Plugin API declaration (the plugin system is named \"petal\"); consumed by plugins, not a kernel Go dependency |\n| `lute` | Markdown/Kramdown AST engine for the editor and `.sy` format; source of `lute.min.js`. The maintainer's default checkout is `$GOPATH/src/github.com/88250/lute`, not a sibling repo; verify the actual checkout before use |\n| `dejavu` | Data repo / sync engine (encrypted snapshots) |\n| `riff` | Spaced-repetition (SRS) flashcard scheduler |\n| `gulu` / `eventbus` / `filelock` / `httpclient` / `logging` | General utilities, in-process events, file locking, HTTP client, and logging |\n| `go-sqlite3` / `pdfcpu` | Maintainer's forks, pulled in via permanent `replace` in `kernel/go.mod` (keep those) |\n| `epub` / `clipboard` / `go-humanize` / `vitess-sqlparser` / `dataparser` / `encryption` | Smaller Go libraries (export / clipboard / formatting / SQL parse / data parse / crypto) |\n\nAll Go libraries above are dependencies in `kernel/go.mod`. GitHub org: `siyuan-note/*` for the `siyuan-` apps and most libs; `88250/*` for lute, gulu, and the forks (go-sqlite3 / pdfcpu).\n\n### Cross-repo notes\n\n- **Checkout availability:** Before cross-repo work, verify each checkout's path, identity, branch, baseline, and existing changes under its own rules. If a required checkout is unavailable, complete independent work where possible and report the exact synchronization or verification blocker; do not claim the task is fully synchronized. These rules do not authorize automatically creating or downloading another repository\n- **Editing Go dependencies:** To test a local change, add a temporary `replace` in `kernel/go.mod` pointing at your local checkout; **never commit that temporary `replace`**.\n- **Rebuilding `lute.min.js`:** Change `lute`, rebuild with GopherJS, and copy the artifact into `app/stage/protyle/js/lute/`.\n- **Type declarations:** when changing files under `app/src/types/` or other TypeScript declarations and constants exposed to plugins, synchronize the corresponding declarations and constants in the `petal` repository in the same task.\n- **Petal changelog:** Whenever changing files in the `petal` repository, update `petal/CHANGELOG.md` in the same task. Add a concise entry under the current unreleased version before committing the Petal change.\n- **Petal documentation:** Keep `petal/README.md` limited to the project title and the `plugin-sample` link. Do not add feature descriptions, API usage explanations, or code examples to the README. Document API behavior, constraints, and lifecycle in comments on the corresponding API declarations in `petal`; put executable usage examples in the actual source code of `plugin-sample`.\n\n---\n\n## 7. Response style\n\n1. **Language:** Match the user's language; do not mix languages mid-sentence (keep proper nouns / identifiers in their original form)\n","kernel/util/builtin_skills/siyuan-plugin-development/references/verification.md":"# Verification Record\n\nReport only actual evidence for each item: passed, failed, unverified, or not applicable. Completed source code and ZIP files do not replace runtime evidence.\n\n- Source code: implementation matches the confirmed plan, frontend and resource declarations are accurate, and there are no unapproved data effects\n- Static checks: syntax, type, or lint checks actually run and their results; mark as unverified if no parser is available\n- Build: the actual TypeScript command, exit status, and artifacts; not applicable for plain JavaScript\n- ZIP: package path, hash, source revision, file list, and validation results returned by package_local\n- Simulated tests: identify SDK substitutes, DOM environments, or standalone Electron environments; do not describe them as an actual SiYuan instance\n- Installation: authorized target, package hash, revisions before and after installation, and actual native installation results\n- Activation: actual configuration state; successful installation does not imply activation\n- Frontend loading: check entry points and errors separately in desktop Electron and the desktop browser; neither substitutes for the other\n- Functional execution: main operation, error feedback, repeated clicks, unloading after partial initialization, and late asynchronous responses\n- Data writes: target, count, and actual returned status; verify unknown results read-only first to avoid duplicate submissions\n\nIf an actual SiYuan instance, target frontend, or necessary permission is unavailable, explicitly leave the corresponding stages unverified. Do not compensate for a missing test environment by installing into the user's actual environment. Rolling back code does not roll back user data.\n\nRecovery is not guaranteed to handle every process crash automatically. If a temporary file is only partially written, or a conditional create has published the target but has not yet cleaned up the temporary hard link, the tool preserves both names, their bytes, and the logs, and returns result_unknown; manual inspection may be necessary. Do not delete files whose state is uncertain, weaken hard-link checks, or blindly retry to claim that recovery is complete.\n","kernel/util/builtin_skills/siyuan-plugin-development/references/development.md":"# Development References and Boundaries\n\n## Official References\n\n- Plugin sample: https://github.com/siyuan-note/plugin-sample\n- Plugin API declarations: https://github.com/siyuan-note/petal\n- Implementation of the target SiYuan version: https://github.com/siyuan-note/siyuan\n\nVerify the currently running version and target frontend, then use the public APIs for that version. The host loads the frontend plugin entry point index.js; CommonJS uses require(\"siyuan\") and module.exports. Do not assume the host exports an API simply because its type exists in an npm package. Take target frontend values from the current manifest specification instead of blindly reusing an all-platform array.\n\n## Minimum Files and Builds\n\nA minimal frontend package requires plugin.json and a real index.js, together with any i18n, README, index.css, and other resources actually used. The manifest's name, version, minimum host version, and frontend declarations must comply with the current native installer's parsing rules. Publishing metadata requires a real identity; do not invent an author or repository for a local prototype.\n\nexamples/index.js and examples/en_US.json are original, small, dependency-free examples that demonstrate only public entry points and lifecycle handling. They are not a completed plugin for the user; do not treat example text as user requirements. Verify addTopBar, showMessage, and language-file keys against the target version. Register layout-related entry points in onLayoutReady and avoid duplicate registration.\n\nTypeScript produces an installable index.js only through an actually available build toolchain; changing a .ts extension is not a build. package_local only freezes and packages files; it does not build or execute arbitrary project scripts. If build capabilities are unavailable, accurately deliver the source code and blocker details, or switch to plain JavaScript with the user's agreement.\n\n## Imports and Reconfirmation\n\nBefore importing, inspect the candidate source with project_status(taskId, sourcePath, sourceFiles). sourceFiles is an exact allowlist of relative file paths and must match the plan's workflow.files; sourceRevision binds the selected existing safe files, not the entire repository. The host only lists excluded paths without reading contents such as .git, node_modules, or config. An optional path on the allowlist that does not yet exist may be created later; do not treat it as an imported file.\n\nAfter reconfirming a plan for an existing managed project, obtain the current sourceRevision through project_status(taskId), then establish a new checkpoint with prepare_project(taskId, expectedSourceRevision). Do not overwrite the current source code or reimport the previous source. If revisions do not match, first inspect the changes read-only, then handle their actual effects.\n\nPackaging freezes only existing safe files on the confirmed allowlist. Missing optional files may be omitted from the package, but plugin.json, index.js, and resources declared by the manifest must be complete. Do not use optional allowlist entries to bypass entry-point or resource validation.\n\n## Data and Lifecycle\n\nRead and modify notes through public kernel APIs, and check code before reporting success. Do not directly modify .sy files, databases, or the actual data directory. Define the target and scope of effects clearly, access encrypted notebooks under existing permissions, and do not bypass locks or denials.\n\nPrevent duplicate clicks during long-running writes, and retain request and instance state. In onunload, clean up the plugin's own events, timers, and observers, and cancel reads that can be canceled; after an asynchronous response, check that the instance is still valid. Canceling a request or unloading does not guarantee that a server-side write has not already executed. If the result is unknown, verify it read-only first.\n\n## Installation\n\nGenerating a local ZIP does not grant permission to install or activate it. Install through the native local-package interface, bound to the package hash and expected installation-target revision; keep the immutable installation snapshot separate from development source code. Explain the effects of overwriting an enabled plugin before obtaining authorization for an action that may execute code. If the user approves only file delivery, stop at source code and artifacts.\n\nRecover installation data through the existing repo snapshot and recovery tools, respecting the user's sync-ignore rules. Snapshots cover non-ignored code under data/plugins and data under data/storage/petal; they do not include the workspace conf directory or managed-project source code. Managed source code is still restored through restore_project. The agent creates a snapshot before the first local write in each conversation round, rather than creating a separate recovery point for every installation; the installer does not keep a separate plugin-code backup either. A full-repository checkout rolls back all data included in the snapshot, disables sync, and reloads the interface. Single-file recovery overwrites only the selected files and does not delete files added later. Restoring petals.json affects the entire plugin configuration. Verify snapshot contents and recovery scope first, follow the existing tool confirmation requirements, do not automatically check out the entire repository, and do not promise to stop plugin code that is already running.\n","docs/API.md":"**English**\n| [中文](API.zh-CN.md)\n| [日本語](API.ja.md)\n\nPlugin resource declarations, data authorization, and publishing APIs are documented in [Plugin publishing](PLUGIN-PUBLISH.md).\n\n* [Specification](#Specification)\n    * [Parameters and return values](#Parameters-and-return-values)\n    * [TypeScript contracts](#TypeScript-contracts)\n    * [Behavior semantics](#Behavior-semantics)\n    * [Authentication](#Authentication)\n* [Notebooks](#Notebooks)\n    * [List notebooks](#List-notebooks)\n    * [Open a notebook](#Open-a-notebook)\n    * [Close a notebook](#Close-a-notebook)\n    * [Rename a notebook](#Rename-a-notebook)\n    * [Create a notebook](#Create-a-notebook)\n    * [Remove a notebook](#Remove-a-notebook)\n    * [Get notebook configuration](#Get-notebook-configuration)\n    * [Save notebook configuration](#Save-notebook-configuration)\n* [Documents](#Documents)\n    * [Create a document with Markdown](#Create-a-document-with-Markdown)\n    * [Rename a document](#Rename-a-document)\n    * [Remove a document](#Remove-a-document)\n    * [Move documents](#Move-documents)\n    * [Set notebook and document sort values](#Set-notebook-and-document-sort-values)\n    * [Set a document's child document sort mode](#Set-a-documents-child-document-sort-mode)\n    * [Get human-readable path based on path](#Get-human-readable-path-based-on-path)\n    * [Get human-readable path based on ID](#Get-human-readable-path-based-on-ID)\n    * [Get storage path based on ID](#Get-storage-path-based-on-ID)\n    * [Get IDs based on human-readable path](#Get-IDs-based-on-human-readable-path)\n* [Assets](#Assets)\n    * [Upload assets](#Upload-assets)\n* [Blocks](#Blocks)\n    * [Insert blocks](#Insert-blocks)\n    * [Prepend blocks](#Prepend-blocks)\n    * [Append blocks](#Append-blocks)\n    * [Update a block](#Update-a-block)\n    * [Delete a block](#Delete-a-block)\n    * [Move a block](#Move-a-block)\n    * [Fold a block](#Fold-a-block)\n    * [Unfold a block](#Unfold-a-block)\n    * [Get a block kramdown](#Get-a-block-kramdown)\n    * [Get child blocks](#get-child-blocks)\n    * [Transfer block ref](#transfer-block-ref)\n* [Attributes](#Attributes)\n    * [Set block attributes](#Set-block-attributes)\n    * [Get block attributes](#Get-block-attributes)\n* [Database](#Database)\n    * [Render](#Render)\n    * [Get](#Get)\n    * [Get primary key values](#Get-primary-key-values)\n    * [Search](#Search)\n    * [Set a cell value](#Set-a-cell-value)\n    * [Add items](#Add-items)\n    * [Remove items](#Remove-items)\n    * [Change layout](#Change-layout)\n    * [Set grouping](#Set-grouping)\n    * [Get filter and sort](#Get-filter-and-sort)\n    * [Set filter](#Set-filter)\n    * [Set sort](#Set-sort)\n    * [Add a field](#Add-a-field)\n    * [Remove a field](#Remove-a-field)\n    * [Set global field sort](#Set-global-field-sort)\n    * [Set per-view field sort](#Set-per-view-field-sort)\n* [Search](#Search)\n    * [Get saved search criteria](#Get-saved-search-criteria)\n    * [Save a search criterion](#Save-a-search-criterion)\n    * [Remove a search criterion](#Remove-a-search-criterion)\n* [SQL](#SQL)\n    * [Execute SQL query](#Execute-SQL-query)\n    * [Flush transaction](#Flush-transaction)\n* [Templates](#Templates)\n    * [Render a template](#Render-a-template)\n    * [Render Sprig](#Render-Sprig)\n* [File](#File)\n    * [Get file](#Get-file)\n    * [Put file](#Put-file)\n    * [Remove file](#Remove-file)\n    * [Rename file](#Rename-file)\n    * [List files](#List-files)\n* [Export](#Export)\n    * [Export Markdown](#Export-Markdown)\n    * [Export Files and Folders](#Export-files-and-folders)\n* [Conversion](#Conversion)\n    * [Pandoc](#Pandoc)\n* [Notification](#Notification)\n    * [Push message](#Push-message)\n    * [Push error message](#Push-error-message)\n* [Network](#Network)\n    * [Forward proxy](#Forward-proxy)\n        * [JSON forward proxy](#JSON-forward-proxy)\n        * [HTTP forward proxy](#HTTP-forward-proxy)\n        * [WebSocket forward proxy](#WebSocket-forward-proxy)\n        * [EventSource forward proxy](#EventSource-forward-proxy)\n* [System](#System)\n    * [Get boot progress](#Get-boot-progress)\n    * [Get system version](#Get-system-version)\n    * [Get the current time of the system](#Get-the-current-time-of-the-system)\n\n---\n\n## Specification\n\n### Parameters and return values\n\n* Endpoint: `http://127.0.0.1:6806`\n* Unless otherwise stated, API interfaces use the POST method\n* For interfaces that take JSON parameters, the parameter is a JSON string placed in the body, and the header Content-Type is `application/json`\n* Return value\n\n   ````json\n   {\n     \"code\": 0,\n     \"msg\": \"\",\n     \"data\": {}\n   }\n   ````\n\n    * `code`: non-zero for exceptions\n    * `msg`: an empty string under normal circumstances, an error text will be returned under abnormal conditions\n    * `data`: may be `{}`, `[]` or `NULL`, depending on the interface\n\n### TypeScript contracts\n\nThe plugin `fetchPost`, `fetchSyncPost`, and `fetchGet` declarations infer request and response types for migrated API paths from generated kernel contracts. Coverage is expanding and includes system utilities, batch block attributes, tag and bookmark operations, selected block queries, notebook listing, history search, and snapshot operations. Existing untyped endpoints and dynamic URLs remain supported. Check the response code before reading successful data from asynchronous calls, and handle nullable fields explicitly.\n\n```typescript\nimport {fetchSyncPost} from \"siyuan\";\n\nconst response = await fetchSyncPost(\"/api/attr/getBlockAttrs\", {id: blockID});\nif (response.code === 0 && response.data) {\n    const value = response.data[\"custom-value\"];\n}\n```\n\nSee the [generated route declarations](../app/src/types/api/index.d.ts) for exact coverage and the [contract maintenance guide](API-CONTRACTS.md) for generation and compatibility rules. Type declarations do not validate JSON at runtime.\n\n### Behavior semantics\n\n* Only interfaces with dedicated interface sections in this document are public APIs. Other kernel routes and `/api/transactions` operations are internal implementations and provide no compatibility or behavioral stability guarantees unless otherwise stated\n* `code: 0` means the interface reported no error while handling the request. It guarantees only the outcome explicitly documented for that interface; it does not mean that related indexes, caches, WebSocket broadcasts, or sync state have all been updated\n* The meanings of omitted fields, `null`, empty objects, and empty arrays are interface-specific. Whether an object or array replaces, merges with, or partially updates existing state, and whether its order is significant, are also defined by each interface\n* An interface may trim, ignore, complete, or transform input. When its documentation states that a normalized result is returned, callers should use the returned `data` as the actual accepted result\n* Do not infer that an operation is read-only from its name. Persistent side effects and their scope are described by each interface when applicable\n* Repeating the same request is idempotent or safe to retry only when explicitly documented. If a response is interrupted or otherwise indeterminate, read the current state before retrying whenever possible\n\n### Authentication\n\nView the API token in <kbd>Settings - Authentication - API token</kbd>. Use it in the request header: `Authorization: Token xxx`\n\n## Notebooks\n\n### List notebooks\n\n* `/api/notebook/lsNotebooks`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"notebooks\": [\n        {\n          \"id\": \"20210817205410-2kvfpfn\", \n          \"name\": \"Test Notebook\",\n          \"icon\": \"1f41b\",\n          \"sort\": 0,\n          \"closed\": false\n        },\n        {\n          \"id\": \"20210808180117-czj9bvb\",\n          \"name\": \"SiYuan User Guide\",\n          \"icon\": \"1f4d4\",\n          \"sort\": 1,\n          \"closed\": false\n        }\n      ]\n    }\n  }\n  ```\n\n### Open a notebook\n\n* `/api/notebook/openNotebook`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Close a notebook\n\n* `/api/notebook/closeNotebook`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nThe close endpoint validates the notebook ID without trimming whitespace. Typed request and response declarations are generated in `app/src/types/api/index.d.ts` and synchronized to `petal`.\n\n### Rename a notebook\n\n* `/api/notebook/renameNotebook`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\",\n    \"name\": \"New name for notebook\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Create a notebook\n\n* `/api/notebook/createNotebook`\n* Parameters\n\n  ```json\n  {\n    \"name\": \"Notebook name\"\n  }\n  ```\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"notebook\": {\n        \"id\": \"20220126215949-r1wvoch\",\n        \"name\": \"Notebook name\",\n        \"icon\": \"\",\n        \"sort\": 0,\n        \"closed\": false\n      }\n    }\n  }\n  ```\n\n### Remove a notebook\n\n* `/api/notebook/removeNotebook`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Get notebook configuration\n\n* `/api/notebook/getNotebookConf`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210817205410-2kvfpfn\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"box\": \"20210817205410-2kvfpfn\",\n      \"conf\": {\n        \"name\": \"Test Notebook\",\n        \"closed\": false,\n        \"refCreateSavePath\": \"\",\n        \"createDocNameTemplate\": \"\",\n        \"dailyNoteSavePath\": \"/daily note/{{now | date \\\"2006/01\\\"}}/{{now | date \\\"2006-01-02\\\"}}\",\n        \"dailyNoteTemplatePath\": \"\"\n      },\n      \"name\": \"Test Notebook\"\n    }\n  }\n  ```\n\n### Save notebook configuration\n\n* `/api/notebook/setNotebookConf`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210817205410-2kvfpfn\",\n    \"conf\": {\n        \"name\": \"Test Notebook\",\n        \"closed\": false,\n        \"refCreateSavePath\": \"\",\n        \"createDocNameTemplate\": \"\",\n        \"dailyNoteSavePath\": \"/daily note/{{now | date \\\"2006/01\\\"}}/{{now | date \\\"2006-01-02\\\"}}\",\n        \"dailyNoteTemplatePath\": \"\"\n      }\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"name\": \"Test Notebook\",\n      \"closed\": false,\n      \"refCreateSavePath\": \"\",\n      \"createDocNameTemplate\": \"\",\n      \"dailyNoteSavePath\": \"/daily note/{{now | date \\\"2006/01\\\"}}/{{now | date \\\"2006-01-02\\\"}}\",\n      \"dailyNoteTemplatePath\": \"\"\n    }\n  }\n  ```\n\n## Documents\n\n### Create a document with Markdown\n\n* `/api/filetree/createDocWithMd`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210817205410-2kvfpfn\",\n    \"path\": \"/foo/bar\",\n    \"markdown\": \"\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n    * `path`: Document path, which needs to start with `/` and separate levels with `/` (corresponds to the database `hpath` field)\n        * `/` is a hierarchy separator and cannot represent a literal slash in a document title; missing parent documents are created automatically\n        * For example, `/Notes/Programming in C/C++` creates a document titled `C++` under `Programming in C` under `Notes`\n        * Importers should sanitize each title before joining titles into a path, for example by replacing ASCII `/` with full-width `／` (U+FF0F): `/Notes/Programming in C／C++` creates a document titled `Programming in C／C++` under `Notes`. This replacement changes the title text\n    * `markdown`: GFM Markdown content\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"20210914223645-oj2vnx2\"\n  }\n  ```\n\n    * `data`: Created document ID\n    * If you use the same `path` to call this interface repeatedly, the existing document will not be overwritten\n\n### Rename a document\n\n* `/api/filetree/renameDoc`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\",\n    \"path\": \"/20210902210113-0avi12f.sy\",\n    \"title\": \"New document title\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n    * `path`: Document path\n    * `title`: New document title\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nRename a document by `id`:\n\n* `/api/filetree/renameDocByID`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210902210113-0avi12f\",\n    \"title\": \"New document title\"\n  }\n  ```\n\n    * `id`: Document ID\n    * `title`: New document title\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Remove a document\n\n* `/api/filetree/removeDoc`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\",\n    \"path\": \"/20210902210113-0avi12f.sy\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n    * `path`: Document path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nRemove a document by `id`:\n\n* `/api/filetree/removeDocByID`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210902210113-0avi12f\"\n  }\n  ```\n\n    * `id`: Document ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Move documents\n\n* `/api/filetree/moveDocs`\n* Parameters\n\n  ```json\n  {\n    \"fromPaths\": [\"/20210917220056-yxtyl7i.sy\"],\n    \"toNotebook\": \"20210817205410-2kvfpfn\",\n    \"toPath\": \"/\"\n  }\n  ```\n\n    * `fromPaths`: Source paths\n    * `toNotebook`: Target notebook ID\n    * `toPath`: Target path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nMove documents by `id`:\n\n* `/api/filetree/moveDocsByID`\n* Parameters\n\n  ```json\n  {\n    \"fromIDs\": [\"20210917220056-yxtyl7i\"],\n    \"toID\": \"20210817205410-2kvfpfn\"\n  }\n  ```\n\n    * `fromIDs`: Source docs' IDs\n    * `toID`: Target parent doc's ID or notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Reorder documents relative to a sibling\n\n* `/api/filetree/reorderDocs`\n* Parameters\n\n  ```json\n  {\n    \"sourceIDs\": [\"20210917220056-yxtyl7i\"],\n    \"targetID\": \"20210917220057-abcdefg\",\n    \"position\": \"before\"\n  }\n  ```\n\n    * `sourceIDs`: Source document IDs inserted in array order\n    * `targetID`: Target sibling document ID\n    * `position`: `before` or `after`\n    * After moving, every source document must have the same notebook and parent as the target; sorting uses the complete sibling list, including hidden and unlisted documents\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"changed\": true,\n      \"notebook\": \"20210817205410-2kvfpfn\",\n      \"parentPath\": \"/\"\n    }\n  }\n  ```\n\n### Reorder notebooks relative to another notebook\n\n* `/api/notebook/reorder`\n* Parameters\n\n  ```json\n  {\n    \"sourceIDs\": [\"20210817205410-2kvfpfn\"],\n    \"targetID\": \"20210817205411-abcdefg\",\n    \"position\": \"after\"\n  }\n  ```\n\n    * `sourceIDs`: Source notebook IDs inserted in array order\n    * `targetID`: Target notebook ID\n    * `position`: `before` or `after`\n    * Sorting uses the complete notebook list, including closed notebooks\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"changed\": true\n    }\n  }\n  ```\n\n### Set notebook and document sort values\n\n* `/api/filetree/setSort`\n* Parameters\n\n  ```json\n  {\n    \"notebookSorts\": [\n      {\n        \"id\": \"20210817205410-2kvfpfn\",\n        \"sort\": -10\n      }\n    ],\n    \"docSorts\": [\n      {\n        \"id\": \"20210917220056-yxtyl7i\",\n        \"sort\": -8\n      }\n    ]\n  }\n  ```\n\n    * `notebookSorts`: Notebook IDs and their sort values, optional\n    * `docSorts`: Document IDs and their sort values, optional\n    * Documents in `docSorts` must belong to opened and unlocked notebooks; notebook root document IDs are not accepted\n    * At least one of `notebookSorts` and `docSorts` must be non-empty. Array order does not affect sorting; each `sort` value is stored directly\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"notebookIDs\": [\"20210817205410-2kvfpfn\"],\n      \"docIDs\": [\"20210917220056-yxtyl7i\"]\n    }\n  }\n  ```\n\n### Set a document's child document sort mode\n\n* `/api/filetree/setDocSortMode`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210917220056-yxtyl7i\",\n    \"sortMode\": 4\n  }\n  ```\n\n    * `id`: ID of the regular document whose child documents use this sort mode; notebook root document IDs are not accepted\n    * `sortMode`: Integer from `0` through `14`; `null` clears the document's explicit setting and inherits the nearest parent document, notebook, or global document tree sort rule, in that order\n    * Values: `0`/`1` file name ascending/descending; `2`/`3` update time ascending/descending; `4`/`5` natural file name ascending/descending; `6` custom; `7`/`8` reference count ascending/descending; `9`/`10` creation time ascending/descending; `11`/`12` size ascending/descending; `13`/`14` child document count ascending/descending\n    * The declared sort mode is inherited by deeper descendants until another document declares its own sort mode\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"box\": \"20210817205410-2kvfpfn\",\n      \"id\": \"20210917220056-yxtyl7i\",\n      \"path\": \"/20210917220056-yxtyl7i.sy\",\n      \"sortMode\": 4,\n      \"effectiveSortMode\": 4\n    }\n  }\n  ```\n\n    * `sortMode` is the explicit setting (`null` when inheriting), while `effectiveSortMode` is the actual sort mode after inheritance is resolved\n\n### Get human-readable path based on path\n\n* `/api/filetree/getHPathByPath`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\",\n    \"path\": \"/20210917220500-sz588nq/20210917220056-yxtyl7i.sy\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n    * `path`: Document path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"/foo/bar\"\n  }\n  ```\n\n### Get human-readable path based on ID\n\n* `/api/filetree/getHPathByID`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210917220056-yxtyl7i\"\n  }\n  ```\n\n    * `id`: Block ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"/foo/bar\"\n  }\n  ```\n\n### Get storage path based on ID\n\n* `/api/filetree/getPathByID`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210808180320-fqgskfj\"\n  }\n  ```\n\n    * `id`: Block ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n    \"notebook\": \"20210808180117-czj9bvb\",\n    \"path\": \"/20200812220555-lj3enxa/20210808180320-fqgskfj.sy\"\n    }\n  }\n  ```\n\n### Get IDs based on human-readable path\n\n* `/api/filetree/getIDsByHPath`\n* Parameters\n\n  ```json\n  {\n    \"path\": \"/foo/bar\",\n    \"notebook\": \"20210808180117-czj9bvb\"\n  }\n  ```\n\n    * `path`: Human-readable path\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n        \"20200813004931-q4cu8na\"\n    ]\n  }\n  ```\n\n## Assets\n\n### Upload assets\n\n* `/api/asset/upload`\n* The parameter is an HTTP Multipart form\n\n    * `assetsDirPath`: The folder path where assets are stored, with the data folder as the root path, for example:\n        * `\"/assets/\"`: workspace/data/assets/ folder\n        * `\"/assets/sub/\"`: workspace/data/assets/sub/ folder\n\n      Under normal circumstances, it is recommended to use the first method, which is stored in the assets folder of the\n      workspace, since putting in a subdirectory has some side effects, please refer to the assets chapter of the user guide.\n    * `file[]`: Uploaded file list\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"disk full\",\n    \"data\": {\n      \"errFiles\": [\"bar.png\"],\n      \"failedFiles\": [\n        {\n          \"index\": 1,\n          \"name\": \"bar.png\",\n          \"error\": \"disk full\"\n        }\n      ],\n      \"succFiles\": [\n        {\n          \"index\": 0,\n          \"name\": \"foo.png\",\n          \"path\": \"assets/foo-20210719092549-9j5y79r.png\"\n        }\n      ],\n      \"succMap\": {\n        \"foo.png\": \"assets/foo-20210719092549-9j5y79r.png\"\n      }\n    }\n  }\n  ```\n\n    * `errFiles`: List of filenames with errors in upload processing\n    * `failedFiles`: Files explicitly reported as failed. `index` is the file's index in `file[]`, `name` is its upload filename, and `error` is the failure message. This field may omit files that were not attempted or not reported individually; use `succFiles` when each input item must be identified unambiguously\n    * `succFiles`: Successfully processed files in input order. `index` is the file's index in `file[]`, `name` is its upload filename, and `path` is the uploaded asset path. Use this field when a batch can contain duplicate filenames\n    * `succMap`: Compatibility mapping for existing callers. The key is the upload filename and the value is assets/foo-id.png. However, when a batch contains duplicate filenames, only the last item with a given key remains in this map\n\n## Blocks\n\n### Insert blocks\n\n* `/api/block/insertBlock`\n* Parameters\n\n  ```json\n  {\n    \"dataType\": \"markdown\",\n    \"data\": \"foo**bar**{: style=\\\"color: var(--b3-font-color8);\\\"}baz\",\n    \"nextID\": \"\",\n    \"previousID\": \"20211229114650-vrek5x6\",\n    \"parentID\": \"\"\n  }\n  ```\n\n    * `dataType`: The data type to be inserted, the value can be `markdown` or `dom`\n    * `data`: Data to be inserted\n    * `nextID`: The ID of the next block, used to anchor the insertion position\n    * `previousID`: The ID of the previous block, used to anchor the insertion position\n    * `parentID`: The ID of the parent block, used to anchor the insertion position\n\n  `nextID`, `previousID`, and `parentID` must have at least one value, using priority: `nextID` > `previousID` >\n  `parentID`\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"insert\",\n            \"data\": \"<div data-node-id=\\\"20211230115020-g02dfx0\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo<strong style=\\\"color: var(--b3-font-color8);\\\">bar</strong>baz</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\"></div></div>\",\n            \"id\": \"20211230115020-g02dfx0\",\n            \"parentID\": \"\",\n            \"previousID\": \"20211229114650-vrek5x6\",\n            \"retData\": null\n          }\n        ],\n        \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n    * `action.data`: DOM generated by the newly inserted block\n    * `action.id`: ID of the first inserted top-level block. When inserting multiple top-level blocks, send one block per request if each ID is needed\n\n### Prepend blocks\n\n* `/api/block/prependBlock`\n* Parameters\n\n  ```json\n  {\n    \"data\": \"foo**bar**{: style=\\\"color: var(--b3-font-color8);\\\"}baz\",\n    \"dataType\": \"markdown\",\n    \"parentID\": \"20220107173950-7f9m1nb\"\n  }\n  ```\n\n    * `dataType`: The data type to be inserted, the value can be `markdown` or `dom`\n    * `data`: Data to be inserted\n    * `parentID`: The ID of the parent block, used to anchor the insertion position\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"insert\",\n            \"data\": \"<div data-node-id=\\\"20220108003710-hm0x9sc\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo<strong style=\\\"color: var(--b3-font-color8);\\\">bar</strong>baz</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\"></div></div>\",\n            \"id\": \"20220108003710-hm0x9sc\",\n            \"parentID\": \"20220107173950-7f9m1nb\",\n            \"previousID\": \"\",\n            \"retData\": null\n          }\n        ],\n        \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n    * `action.data`: DOM generated by the newly inserted block\n    * `action.id`: ID of the newly inserted block\n\n### Append blocks\n\n* `/api/block/appendBlock`\n* Parameters\n\n  ```json\n  {\n    \"data\": \"foo**bar**{: style=\\\"color: var(--b3-font-color8);\\\"}baz\",\n    \"dataType\": \"markdown\",\n    \"parentID\": \"20220107173950-7f9m1nb\"\n  }\n  ```\n\n    * `dataType`: The data type to be inserted, the value can be `markdown` or `dom`\n    * `data`: Data to be inserted\n    * `parentID`: The ID of the parent block, used to anchor the insertion position\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"insert\",\n            \"data\": \"<div data-node-id=\\\"20220108003642-y2wmpcv\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo<strong style=\\\"color: var(--b3-font-color8);\\\">bar</strong>baz</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\"></div></div>\",\n            \"id\": \"20220108003642-y2wmpcv\",\n            \"parentID\": \"20220107173950-7f9m1nb\",\n            \"previousID\": \"20220108003615-7rk41t1\",\n            \"retData\": null\n          }\n        ],\n        \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n    * `action.data`: DOM generated by the newly inserted block\n    * `action.id`: ID of the first appended top-level block. When appending multiple top-level blocks, send one block per request if each ID is needed\n\n### Update a block\n\n* `/api/block/updateBlock`\n* Parameters\n\n  ```json\n  {\n    \"dataType\": \"markdown\",\n    \"data\": \"foobarbaz\",\n    \"id\": \"20211230161520-querkps\",\n    \"lockType\": false\n  }\n  ```\n\n    * `dataType`: The data type to be updated, the value can be `markdown` or `dom`\n    * `data`: Data to be updated\n    * `id`: ID of the block to be updated\n    * `lockType`: Whether to reject the update when the parsed block type differs from the existing block type; invalid parent-child structures are always rejected, while an empty paragraph can be converted to any valid block type; defaults to `false`\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"update\",\n            \"data\": \"<div data-node-id=\\\"20211230161520-querkps\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo<strong>bar</strong>baz</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\"></div></div>\",\n            \"id\": \"20211230161520-querkps\",\n            \"parentID\": \"\",\n            \"previousID\": \"\",\n            \"retData\": null\n            }\n          ],\n        \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n    * `action.data`: DOM generated by the updated block\n\n### Delete a block\n\n* `/api/block/deleteBlock`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20211230161520-querkps\"\n  }\n  ```\n\n    * `id`: ID of the block to be deleted\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"delete\",\n            \"data\": null,\n            \"id\": \"20211230162439-vtm09qo\",\n            \"parentID\": \"\",\n            \"previousID\": \"\",\n            \"retData\": null\n          }\n        ],\n       \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n### Move a block\n\n* `/api/block/moveBlock`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20230406180530-3o1rqkc\",\n    \"previousID\": \"20230406152734-if5kyx6\",\n    \"parentID\": \"20230404183855-woe52ko\"\n  }\n  ```\n\n    * `id`: Block ID to move\n    * `previousID`: The ID of the previous block, used to anchor the insertion position\n    * `parentID`: The ID of the parent block, used to anchor the insertion position, `previousID` and `parentID` cannot\n      be empty at the same time, if they exist at the same time, `previousID` will be used first\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n        {\n            \"doOperations\": [\n                {\n                    \"action\": \"move\",\n                    \"data\": null,\n                    \"id\": \"20230406180530-3o1rqkc\",\n                    \"parentID\": \"20230404183855-woe52ko\",\n                    \"previousID\": \"20230406152734-if5kyx6\",\n                    \"nextID\": \"\",\n                    \"retData\": null,\n                    \"srcIDs\": null,\n                    \"name\": \"\",\n                    \"type\": \"\"\n                }\n            ],\n            \"undoOperations\": null\n        }\n    ]\n  }\n  ```\n\n### Fold a block\n\n* `/api/block/foldBlock`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20231224160424-2f5680o\"\n  }\n  ```\n\n    * `id`: Block ID to fold\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Unfold a block\n\n* `/api/block/unfoldBlock`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20231224160424-2f5680o\"\n  }\n  ```\n\n    * `id`: Block ID to unfold\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Get a block kramdown\n\n* `/api/block/getBlockKramdown`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20201225220954-dlgzk1o\"\n  }\n  ```\n\n    * `id`: ID of the block to be got\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"id\": \"20201225220954-dlgzk1o\",\n      \"kramdown\": \"* {: id=\\\"20201225220954-e913snx\\\"}Create a new notebook, create a new document under the notebook\\n  {: id=\\\"20210131161940-kfs31q6\\\"}\\n* {: id=\\\"20201225220954-ygz217h\\\"}Enter <kbd>/</kbd> in the editor to trigger the function menu\\n  {: id=\\\"20210131161940-eo0riwq\\\"}\\n* {: id=\\\"20201225220954-875yybt\\\"}((20200924101200-gss5vee \\\"Navigate in the content block\\\")) and ((20200924100906-0u4zfq3 \\\"Window and tab\\\"))\\n  {: id=\\\"20210131161940-b5uow2h\\\"}\"\n    }\n  }\n  ```\n\n* Determinism: The returned Kramdown canonicalizes block-level IAL attribute ordering; the order remains stable while the block content and attributes are unchanged\n\n### Get child blocks\n\n* `/api/block/getChildBlocks`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20230506212712-vt9ajwj\"\n  }\n  ```\n\n    * `id`: Parent block ID\n    * The blocks below a heading are also counted as child blocks\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"id\": \"20230512083858-mjdwkbn\",\n        \"type\": \"h\",\n        \"subType\": \"h1\"\n      },\n      {\n        \"id\": \"20230513213727-thswvfd\",\n        \"type\": \"s\"\n      },\n      {\n        \"id\": \"20230513213633-9lsj4ew\",\n        \"type\": \"l\",\n        \"subType\": \"u\"\n      }\n    ]\n  }\n  ```\n\n### Transfer block ref\n\n* `/api/block/transferBlockRef`\n* Parameters\n\n  ```json\n  {\n    \"fromID\": \"20230612160235-mv6rrh1\",\n    \"toID\": \"20230613093045-uwcomng\",\n    \"refIDs\": [\"20230613092230-cpyimmd\"]\n  }\n  ```\n\n    * `fromID`: Def block ID\n    * `toID`: Target block ID\n    * `refIDs`: Ref block IDs point to def block ID, optional, if not specified, all ref block IDs will be transferred\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n## Attributes\n\n### Set block attributes\n\n* `/api/attr/setBlockAttrs`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210912214605-uhi5gco\",\n    \"attrs\": {\n      \"custom-attr1\": \"line1\\nline2\"\n    }\n  }\n  ```\n\n    * `id`: Block ID\n    * `attrs`: Block attributes, custom attributes must be prefixed with `custom-`\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Get block attributes\n\n* `/api/attr/getBlockAttrs`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210912214605-uhi5gco\"\n  }\n  ```\n\n    * `id`: Block ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"custom-attr1\": \"line1\\nline2\",\n      \"id\": \"20210912214605-uhi5gco\",\n      \"title\": \"PDF Annotation Demo\",\n      \"type\": \"doc\",\n      \"updated\": \"20210916120715\"\n    }\n  }\n  ```\n\n## SQL\n\n### Execute SQL query\n\n* `/api/query/sql`\n* Parameters\n\n  ```json\n  {\n    \"stmt\": \"SELECT * FROM blocks WHERE content LIKE'%content%' LIMIT 7\"\n  }\n  ```\n\n    * `stmt`: SQL statement\n\nWithout an explicit outer `LIMIT`, results default to at most `search.limit` rows (the configured search result limit). Use explicit `LIMIT` and `OFFSET` clauses to paginate, with a stable, unique ordering such as `ORDER BY hpath, id`. However, an explicit outer `LIMIT` overrides the default, including values larger than `search.limit`.\n\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      { \"col\": \"val\" }\n    ],\n    \"limit\": 0,\n    \"truncated\": false\n  }\n  ```\n\nOn success, `data` remains an array. `limit` is the server default limit applied to this query, or `0` when the SQL supplies an explicit outer `LIMIT`; it is not the value of that explicit clause. `truncated` is `true` only when the server default limit omitted at least one result row. Exactly meeting the limit does not imply truncation. For the example above, `LIMIT 7` is explicit, so `limit` is `0` and `truncated` is `false`. These fields are omitted on errors.\n\nNote: To ensure data security, access to this interface is prohibited in Publish Mode.\n\n### Flush transaction\n\n* `/api/sqlite/flushTransaction`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nBlock-writing APIs can return after their block-tree transaction is committed but before asynchronous SQL indexing has caught up. If a subsequent `/api/query/sql` call must observe that write, call `POST /api/sqlite/flushTransaction` after the write and wait for `code: 0` before querying SQL. For example, `getBlockKramdown` reads the block tree and can show newly written content before the SQL index does. Kramdown, DOM, and SQL columns are different representations and are not normalized plain text.\n\n## Templates\n\n### Render a template\n\n* `/api/template/render`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20220724223548-j6g0o87\",\n    \"path\": \"F:\\\\SiYuan\\\\data\\\\templates\\\\foo.md\",\n    \"mode\": \"editorInsert\"\n  }\n  ```\n\n    * `id`: The ID of the document where the rendering is called\n    * `path`: Template file absolute path\n    * `mode`: Optional rendering mode. Currently supports `\"preview\"` and `\"editorInsert\"` only. Preview mode produces a document tree plan without writing files; editor-insert mode produces a plan that can be confirmed and applied with the corresponding editor transaction\n    * When `mode` is omitted, the legacy `preview` Boolean parameter remains supported: `preview: true` is equivalent to `mode: \"preview\"`; otherwise the template is rendered as ordinary content and `createDocTree` is disabled\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"content\": \"<div data-node-id=\\\"20220729234848-dlgsah7\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\" updated=\\\"20220729234840\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\">​</div></div>\",\n      \"path\": \"F:\\\\SiYuan\\\\data\\\\templates\\\\foo.md\",\n      \"docTreePlan\": {\n        \"id\": \"template-plan-token\",\n        \"count\": 2,\n        \"nodes\": [\n          {\n            \"id\": \"20260830150000-abc1234\",\n            \"title\": \"Materials\",\n            \"parentID\": \"20220724223548-j6g0o87\",\n            \"hPath\": \"/Parent/Materials\",\n            \"depth\": 1\n          },\n          {\n            \"id\": \"20260830150001-def5678\",\n            \"title\": \"Review\",\n            \"parentID\": \"20260830150000-abc1234\",\n            \"hPath\": \"/Parent/Materials/Review\",\n            \"depth\": 2\n          }\n        ]\n      }\n    }\n  }\n  ```\n\n    * `docTreePlan`: Present when the template declares a child document tree with `createDocTree`\n        * `id`: Empty in preview mode, which never writes files. In editor-insert mode, this is a short-lived, one-time plan token; after confirmation, submit it as the top-level `templateDocTreePlanID` field of the corresponding transaction object\n        * `count`: Total number of child documents in the plan\n        * `nodes`: Static descriptions of the planned documents\n            * `id`: Planned document ID\n            * `title`: Planned document title\n            * `parentID`: Planned parent document ID\n            * `hPath`: Planned human-readable document path\n            * `depth`: Depth relative to the document where the template is inserted\n        * A single plan can contain at most 128 documents, and its declared child document tree can be at most 16 levels deep. The resulting absolute file tree depth remains subject to the setting that controls whether sub-documents deeper than 7 levels may be created\n\n### Save a document as a template\n\n* `/api/template/docSaveAsTemplate`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20220724223548-j6g0o87\",\n    \"name\": \"Project\",\n    \"overwrite\": false,\n    \"databaseMode\": \"copy\"\n  }\n  ```\n\n    * `id`: Source document ID\n    * `name`: Template name. The kernel sanitizes the name and adds the `.md` extension\n    * `overwrite`: Whether to replace an existing template with the same name. When `false` and the template exists, the response `code` is `1`\n    * `databaseMode`: Optional handling for all database blocks in the document. `copy` (the default) creates independent databases whenever the template is used and clears their block-level context filters; `reference` keeps the existing database IDs and context filters, so rendered blocks are mirrors that share data and view settings with the source databases. Rendering a reference-mode template fails if a source database is unavailable in the target document's encryption boundary\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Render Sprig\n\n* `/api/template/renderSprig`\n* Parameters\n\n  ```json\n  {\n    \"template\": \"/daily note/{{now | date \\\"2006/01\\\"}}/{{now | date \\\"2006-01-02\\\"}}\"\n  }\n  ```\n    * `template`: template content\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"/daily note/2023/03/2023-03-24\"\n  }\n  ```\n\n## File\n\n### Get file\n\n* `/api/file/getFile`\n* Parameters\n\n  ``json {\n  \"path\": \"/data/20210808180117-6v0mkxr/20200923234011-ieuun1p.sy\"\n  }\n  ``\n    * `path`: the file path under the workspace path\n* Return value\n\n    * Response status code `200`: File content\n    * Response status code `202`: Exception information\n\n      ```json\n      {\n        \"code\": 404,\n        \"msg\": \"\",\n        \"data\": null\n      }\n      ```\n\n        * `code`: non-zero for exceptions\n\n            * `-1`: Parameter parsing error\n            * `403`: Permission denied (file is not in the workspace)\n            * `404`: Not Found (file doesn't exist)\n            * `405`: Method Not Allowed (it's a directory)\n            * `500`: Server Error (stat file failed / read file failed)\n        * `msg`: a piece of text describing the error\n\n### Put file\n\n* `/api/file/putFile`\n* The parameter is an HTTP Multipart form\n\n    * `path`: the file path under the workspace path\n    * `isDir`: whether to create a folder, when `true` only create a folder, ignore `file`\n    * `modTime`: last access and modification time, Unix time\n    * `file`: the uploaded file\n* Return value\n\n   ```json\n   {\n     \"code\": 0,\n     \"msg\": \"\",\n     \"data\": null\n   }\n   ```\n\n### Remove file\n\n* `/api/file/removeFile`\n* Parameters\n\n  ```json\n  {\n    \"path\": \"/data/20210808180117-6v0mkxr/20200923234011-ieuun1p.sy\"\n  }\n  ```\n    * `path`: the file path under the workspace path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Rename file\n\n* `/api/file/renameFile`\n* Parameters\n\n  ```json\n  {\n    \"path\": \"/data/assets/image-20230523085812-k3o9t32.png\",\n    \"newPath\": \"/data/assets/test-20230523085812-k3o9t32.png\"\n  }\n  ```\n    * `path`: the file path under the workspace path\n    * `newPath`: the new file path under the workspace path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### List files\n\n* `/api/file/readDir`\n* Parameters\n\n  ```json\n  {\n    \"path\": \"/data/20210808180117-6v0mkxr/20200923234011-ieuun1p\"\n  }\n  ```\n    * `path`: the dir path under the workspace path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"isDir\": true,\n        \"isSymlink\": false,\n        \"name\": \"20210808180303-6yi0dv5\",\n        \"updated\": 1691467624\n      },\n      {\n        \"isDir\": false,\n        \"isSymlink\": false,\n        \"name\": \"20210808180303-6yi0dv5.sy\",\n        \"updated\": 1663298365\n      }\n    ]\n  }\n  ```\n\n## Export\n\n### Export Markdown\n\n* `/api/export/exportMdContent`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"\"\n  }\n  ```\n\n    * `id`: ID of the doc block to export\n    * `preserveTaskMarkers`: Optional boolean for Markdown copying; `true` retains task markers such as `[/]` and `[-]`. Omitted, `null`, or `false` keeps standard Markdown normalization to `[X]` for nonstandard markers\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"hPath\": \"/Please Start Here\",\n      \"content\": \"## 🍫 Content Block\\n\\nIn SiYuan, the only important core concept is...\"\n    }\n  }\n  ```\n\n    * `hPath`: human-readable path\n    * `content`: Markdown content, including automatic heading numbers when enabled for the document (the document setting overrides the editor default); uses the editor numbering format and preserves source-document numbers for partial exports\n\n### Export files and folders\n\n* `/api/export/exportResources`\n* Parameters\n\n  ```json\n  {\n    \"paths\": [\n      \"/conf/appearance/boot\",\n      \"/conf/appearance/langs\",\n      \"/conf/appearance/emojis/conf.json\",\n      \"/conf/appearance/icons/index.html\"\n    ],\n    \"name\": \"zip-file-name\"\n  }\n  ```\n\n    * `paths`: A list of file or folder paths to be exported, the same filename/folder name will be overwritten\n    * `name`: (Optional) The exported file name, which defaults to `export-YYYY-MM-DD_hh-mm-ss.zip` when not set\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"path\": \"temp/export/zip-file-name.zip\"\n    }\n  }\n  ```\n\n    * `path`: The path of `*.zip` file created\n        * The directory structure in `zip-file-name.zip` is as follows:\n            * `zip-file-name`\n                * `boot`\n                * `langs`\n                * `conf.json`\n                * `index.html`\n\n## Conversion\n\n### Pandoc\n\n* `/api/convert/pandoc`\n* Working directory\n    * Executing the pandoc command will set the working directory to `workspace/temp/convert/pandoc/${dir}`\n    * API [`Put file`](#put-file) can be used to write the file to be converted to this directory first\n    * Then call the API for conversion, and the converted file will also be written to this directory\n    * Finally, call the API [`Get file`](#get-file) to get the converted file\n        * Or call the API [Create a document with Markdown](#Create-a-document-with-Markdown)\n        * Or call the internal API `importStdMd` to import the converted folder directly\n* Parameters\n\n  ```json\n  {\n    \"dir\": \"test\",\n    \"args\": [\n      \"--to\", \"markdown_strict-raw_html\",\n      \"foo.epub\",\n      \"-o\", \"foo.md\"\n   ]\n  }\n  ```\n\n    * `args`: Pandoc command line parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n       \"path\": \"/temp/convert/pandoc/test\"\n    }\n  }\n  ```\n    * `path`: the path under the workspace\n\n## Notification\n\n### Push message\n\n* `/api/notification/pushMsg`\n* Parameters\n\n  ```json\n  {\n    \"msg\": \"test\",\n    \"timeout\": 7000\n  }\n  ```\n    * `timeout`: The duration of the message display in milliseconds. This field can be omitted, the default is 7000\n      milliseconds\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n        \"id\": \"62jtmqi\"\n    }\n  }\n  ```\n    * `id`: Message ID\n\n### Push error message\n\n* `/api/notification/pushErrMsg`\n* Parameters\n\n  ```json\n  {\n    \"msg\": \"test\",\n    \"timeout\": 7000\n  }\n  ```\n    * `timeout`: The duration of the message display in milliseconds. This field can be omitted, the default is 7000\n      milliseconds\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n        \"id\": \"qc9znut\"\n    }\n  }\n  ```\n    * `id`: Message ID\n\n## Network\n\n### Forward proxy\n\n#### JSON forward proxy\n\n* `/api/network/forwardProxy`\n* Parameters\n\n  ```json\n  {\n    \"url\": \"https://b3log.org/siyuan/\",\n    \"method\": \"GET\",\n    \"timeout\": 7000,\n    \"contentType\": \"text/html\",\n    \"headers\": [\n        {\n            \"Cookie\": \"\"\n        }\n    ],\n    \"redirect\": true,\n    \"payload\": {},\n    \"payloadEncoding\": \"json\",\n    \"responseEncoding\": \"text\"\n  }\n  ```\n\n    * `url`: URL to forward\n    * `method`: HTTP method, default is `POST`\n    * `timeout`: timeout in milliseconds, default is `7000`\n    * `contentType`: Content-Type, default is `application/json`\n    * `headers`: HTTP request header array; each key-value pair in the objects is set as a request header\n    * `redirect`: Whether to follow redirects, default is `true`, following up to 2 redirects; set it to `false` to disable redirects\n    * `payload`: HTTP payload, object or string\n    * `payloadEncoding`: The encoding scheme used by `payload`, default is `json`; `json` sends `payload` directly, and binary payloads can use the following encoded strings\n\n        * `json`\n        * `base64` | `base64-std`\n        * `base64-url`\n        * `base32` | `base32-std`\n        * `base32-hex`\n        * `hex`\n    * `responseEncoding`: The encoding scheme used by `body` in response data, default is `text`, optional values are as follows\n\n        * `text`\n        * `base64` | `base64-std`\n        * `base64-url`\n        * `base32` | `base32-std`\n        * `base32-hex`\n        * `hex`\n\n      `text` preserves the existing behavior and converts the character set to UTF-8 when applicable. The binary encodings encode the response body before character-set conversion; existing HTTP content decoding behavior, such as gzip decompression, is unchanged.\n\n      The response body is limited to 32 MiB after HTTP content decoding. If the limit is exceeded, the API returns error code `10` without a partial body. Therefore, use `/api/network/proxy` for large files or streaming responses.\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"body\": \"\",\n      \"bodyEncoding\": \"text\",\n      \"contentType\": \"text/html\",\n      \"elapsed\": 1976,\n      \"headers\": {\n      },\n      \"status\": 200,\n      \"url\": \"https://b3log.org/siyuan/\"\n    }\n  }\n  ```\n\n    * `body`: Response body\n    * `bodyEncoding`: The encoding scheme used by `body`; it is consistent with the `responseEncoding` field in the request, default is `text`, optional values are as follows\n\n        * `text`\n        * `base64` | `base64-std`\n        * `base64-url`\n        * `base32` | `base32-std`\n        * `base32-hex`\n        * `hex`\n    * `contentType`: Response header `Content-Type`\n    * `elapsed`: Request duration in milliseconds\n    * `headers`: Response headers returned by the target service\n    * `status`: HTTP status code returned by the target service\n    * `url`: Forwarded URL\n\n#### HTTP forward proxy\n\n* `/api/network/proxy`\n* Request method: any HTTP method\n* Query parameters\n\n    * `u`: Required, target `http` or `https` URL encoded with Go `base64.RawURLEncoding`, which is URL-safe Base64 without `=` padding\n    * `h`: Optional, request header JSON encoded in the same way; the JSON type is `map[string][]string`, for example `{\"Authorization\":[\"Bearer token\"]}`\n    * `t`: Optional, connection timeout in Go `time.ParseDuration` format, for example `30s` or `1500ms`\n* Request body: Forwards the current request body as-is, and forwards the current request's full `Content-Type` header to the target request\n* Return value: Directly returns the target service HTTP status code and response body without wrapping them in `code`, `msg`, or `data`; target response headers are returned with the `Siyuan-Proxy-` prefix, for example `Content-Type` is returned as `Siyuan-Proxy-Content-Type`\n\n#### WebSocket forward proxy\n\n* `/ws/network/proxy`\n* Request method: `GET`\n* Query parameters\n\n    * `u`: Required, target `ws` or `wss` URL encoded with Go `base64.RawURLEncoding`\n    * `h`: Optional, handshake request header JSON encoded in the same way; the JSON type is `map[string][]string`\n    * `t`: Optional, handshake timeout in Go `time.ParseDuration` format, for example `30s` or `1500ms`\n* Return value: Upgrades to WebSocket and then forwards messages bidirectionally; target handshake response headers are returned with the `Siyuan-Proxy-` prefix\n\n#### EventSource forward proxy\n\n* `/es/network/proxy`\n* Request method: `GET`\n* Query parameters\n\n    * `u`: Required, target `http` or `https` URL encoded with Go `base64.RawURLEncoding`\n    * `h`: Optional, request header JSON encoded in the same way; the JSON type is `map[string][]string`\n    * `t`: Optional, connection timeout in Go `time.ParseDuration` format, for example `30s` or `1500ms`\n* Return value: Directly streams the target service HTTP status code and response body without wrapping them in `code`, `msg`, or `data`; if the request headers do not include `Accept`, `text/event-stream` is used automatically; target response headers are returned with the `Siyuan-Proxy-` prefix\n\n## System\n\n### Get boot progress\n\n* `/api/system/bootProgress`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"details\": \"Finishing boot...\",\n      \"progress\": 100\n    }\n  }\n  ```\n\n### Get system version\n\n* `/api/system/version`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"1.3.5\"\n  }\n  ```\n\n### Get the current time of the system\n\n* `/api/system/currentTime`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": 1631850968131\n  }\n  ```\n\n    * `data`: Precision in milliseconds\n\n## Database\n\nA database (internally an \"attribute view\") stores structured data as fields (columns) and items (rows). Each database is identified by an `avID` and can be embedded into a document through one or more database blocks (`blockID`). A single database may contain multiple views (`viewID`) of different layout types: `table`, `list`, `gallery`, and `kanban`"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/siyuan-note/siyuan/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\nSiYuan repository guide. Module path `github.com/siyuan-note/siyuan`, license AGPL-3.0.\n\n---\n\n## 1. Non-negotiable constraints\n\n### Verify the code baseline before repository tasks\n\n- Before each new repository task, verify the repository, current commit, branch, and working-tree state. For work targeting the latest branch, select the baseline source branch using section 2's Git rules and fetch the relevant remote refs before analysis or editing. Respect explicitly requested commits, tags, or pull requests; for a fixed-revision review or historical investigation, inspect that revision without advancing it or synchronizing the working branch. Fetch only if needed to resolve the requested revision. If fetching is unavailable, report the known commit and freshness limit; read-only analysis may continue, but do not claim to have verified the latest remote baseline\n- Follow the repository-specific Git rules for working branches and authorized push destinations. If the user explicitly requests a task branch or the repository's own rules require one, create it from the verified task baseline. Using an isolated worktree does not change the default working branch or authorize pushing to a task branch instead. When synchronizing a development branch, automatically fast-forward it only when its working tree is clean and it is merely behind. Rebase is permitted when synchronizing divergent development branches, provided existing work is preserved; this does not authorize force-pushing rewritten history. Read-only analysis does not itself authorize branch synchronization. If synchronization is blocked, preserve existing work and report the limitation; do not discard changes, automatically stash, create merge commits, or silently substitute another push destination\n- Reuse the verified baseline for follow-ups in the same task; recheck after a substantial pause, a repository or branch change, or when newer commits could affect the task. Unrelated conversation needs no fetch\n\n### Requirement evaluation\n\nBefore implementing a requirement, evaluate its premise and approach with effort proportional to its complexity and risk\n\n1. **Check compliance first:** For major features, review applicable compliance requirements, licensing terms, privacy risks, and costs before technical design\n2. **Check the premise and existing capabilities:** Inspect the relevant implementation, settings, shortcuts, APIs, or plugin capabilities, and reproduce reported behavior when feasible. Check applicable repository rules, encrypted notebook compatibility, the `.sy` format, and API contracts. Distinguish the user's goal and acceptance criteria from a suggested implementation; evaluate whether that implementation addresses the underlying problem. State any limits to verification\n3. **Choose an appropriate approach:** Prefer the smallest sufficient change consistent with existing practices and compatibility requirements. Consider whether an existing capability or a documentation correction already satisfies the need, including the option of making no code change\n4. **Briefly report before editing:** State the conclusion, key evidence, and remaining uncertainty in a progress update before the first change. Discuss alternatives only when they involve meaningful tradeoffs; simple tasks need only a short explanation\n5. **Ask only when a decision is needed:** If the premise is false or implementation is unwarranted, explain the evidence and ask how to proceed unless the user's existing instructions already resolve the next step. Ask before changing the agreed goal or scope, or making an unresolved product, priority, or compatibility tradeoff. Do not silently narrow or broaden the task\n6. **Otherwise proceed directly:** When the requirement is reasonable, implement it without another confirmation round. Choosing a different implementation that meets the agreed goal and acceptance criteria does not itself require approval\n\nUnambiguous mechanical edits with no behavioral or compatibility impact need no separate evaluation round. Continue to follow applicable repository checks; a rename, version bump, or documentation update is not automatically exempt\n\n### Do not hand-edit\n\n- `app/stage/protyle/js/lute/lute.min.js` (built from upstream `88250/lute`)\n- `app/stage/build/**`, `app/src/types/dist/**`\n- `app/changelogs/**` (generated by separate tooling)\n- `app/kernel/SiYuan-Kernel*`, `*.syso`, `kernel/kernel.aar`\n- `app/pandoc/*`\n- Other files marked as generated: edit their source and regenerate them instead. API declarations and schemas come from `kernel/apicontract/`; attribute-view capability declarations come from `kernel/av/capabilities.go`. Before changing either, read the generation instructions and output inventory in [docs/API-CONTRACTS.md](docs/API-CONTRACTS.md#generation-and-verification), including Petal synchronization\n\n### Verification and prohibited operations\n\n1. **Frontend verification:** Do not use `npx webpack` or `pnpm dev` to verify changes. After frontend code changes, run `pnpm run lint` from `app/`; it includes type checks, ESLint automatic fixes, and CSS, menu, and startup-language checks, but not behavioral tests. Review the resulting diff and keep fixes within the task scope without overwriting existing work. In read-only tasks, use applicable non-mutating checks instead, such as `pnpm run typecheck` and ESLint without `--fix` or `--cache`; do not run the auto-fixing lint command\n2. **Frontend build:** Do NOT run `pnpm build` — the developer runs `pnpm dev` manually, and `pnpm build` will conflict with it, producing broken bundles\n3. **Kernel development:** After modifying Go code, run `gofmt`, but do not compile the kernel binary or restart a running kernel; the developer handles both manually. Isolated regression tests and contract generation are allowed; they must not replace the product binary or restart the running instance\n4. **Git:** **NEVER** run `git commit` / `git push` unless explicitly asked — no exceptions\n5. **UI verification in a browser:** Open `/stage/build/desktop/` directly instead of `/`. The kernel selects the frontend bundle by User-Agent in `kernel/server/serve.go`, and a UA containing `Electron` is redirected to the Electron-only `/stage/build/app/`, which fails in a plain browser with `require is not defined`; VSCode's built-in browser sends such a UA. The Electron main window does not expose a remote debugging port by default\n6. **Verification scope:** Run the checks and regression tests applicable to the changed behavior, including non-API Go code, frontend interactions, and scripts. Select scope according to impact and risk; a full suite is not required for every task, and lint or formatting alone does not establish behavioral correctness. Use `app/scripts/test.mjs` and the relevant maintenance guides to identify test discovery and commands. For documentation-only changes, check accuracy, links, language alignment, and the diff rather than running auto-fixing code checks. Report passed, failed, and unrun checks with their limits; do not infer that tests passed solely from a successful CI job or release\n7. **Test synchronization:** Whenever code or files change, handle the corresponding tests and fixtures in the same change rather than leaving them for later\n   - Rename, move, merge, split, or delete a test file together with its source, and keep the file name reflecting the code it covers; a test file whose source no longer exists or whose name no longer matches is stale and must be repaired or removed\n   - When behavior changes intentionally, update the affected assertions, case names, and comments to the new contract instead of dropping the coverage; when behavior is removed, delete only the cases that cover the removed behavior and keep adjacent assertions\n   - Keep test doubles aligned with the code under test: remove mocks of exports, constants, flags, and helpers that no longer exist, and do not preserve assertions that only pass because a stale stub keeps them alive\n   - Do not use a test that already fails for unrelated reasons as permission to leave a new mismatch; state such failures separately and verify whether the task changed their outcome\n   - Merge test files only when they cover the same subject and merging removes duplication without losing cases or discoverability; otherwise keep the current split\n   - Run the affected tests after the synchronization, and name the files and cases that were updated, merged, or deleted\n\n### Encrypted notebook compatibility\n\n- Encrypted notebook development is complete. Maintain it as a released feature\n- Existing encrypted data is the compatibility baseline. Upgrades must preserve access and recovery; do not treat it as disposable development data or require users to delete or recreate it\n- Changes to document, asset, attribute-view, key-envelope, backup, history, or sync formats must retain authenticated reads of supported existing formats or provide a recoverable migration before removing a reader\n- Changes to envelopes, AAD semantics, or key derivation must use an explicit format version and a compatibility plan. Migration must authenticate the source, preserve recovery material, and remain recoverable after failure or interruption; never regenerate MasterSalt or discard keys to work around incompatibility\n- Unknown formats, corruption, and authentication failures must preserve the original data and return an error; compatibility must not bypass authentication or fall back to plaintext. Derived indexes may be rebuilt after the source ciphertext authenticates\n- Format changes require regression coverage using fixtures from the supported previous format, including the affected read, export, history, backup, and recovery paths. See `docs/ENCRYPTED-NOTEBOOK.md` and `docs/ENCRYPTED-NOTEBOOK.zh-CN.md`; keep their compatibility policies aligned\n\n---\n\n## 2. Project-specific rules\n\n1. **i18n:**\n   - New keys go at the **top** of each `langs/*.json` object; add to every language file (reference `en.json`)\n   - Indent `langs/*.json` with tabs, using one tab per nesting level; do not use spaces for indentation\n   - Exception: inside the `_kernel` object, append new entries at the **end** using the next incremental numeric key\n   - Each language must be properly translated — do NOT copy the same text across all language files\n   - Use three ASCII periods (`...`) for ellipses in all localized strings; do not use Unicode ellipsis characters (`…` or `……`)\n   - Setting description tip strings must not end with a period or equivalent sentence-ending mark (for example `.`, `。`, or `।`)\n   - User-facing error messages must not end with a period or its language-specific equivalent (for example `.`, `。`, or `।`); preserve punctuation between sentences and apply this rule consistently across all languages, including `_kernel` messages\n   - Domains: `ld246.com` only in `zh-CN.json`; use `liuyun.io` in all other languages\n   - In `zh-TW` localization and the Traditional Chinese user guide, consistently translate the content-model term Block as `區塊`, including compounds (e.g. `子區塊`, `程式碼區塊`, `區塊 ID`); never abbreviate it as `塊`, and count blocks with `個區塊`\n   - Translate Block Reference as `區塊引用` and Blockquote as `引述區塊`; preserve non-content-block terms such as `分塊` (data chunks) and `覈取方塊` (Checkbox)\n   - Preserve each `_kernel` message's Go format argument positions and verbs from `en.json`; when translation requires a different order, use explicit argument indexes such as `%[4]s` and `%[3]d`. Preserve `%%` for literal percent signs\n   - After modifying i18n files, run `python scripts/check-lang-keys.py` to verify key completeness and kernel format argument compatibility across all language files\n2. **Cross-platform scripting:**\n   - Do not assume the current shell is Bash, zsh, or PowerShell. Confirm the shell before using shell-specific syntax; otherwise avoid constructs such as `&&`, heredocs, and `/dev/null`\n   - For simple sequences, use separate command calls and set the command working directory instead of chaining `cd` with another command\n   - For multi-step logic, write and run a temporary Node.js or Python script. On Windows, avoid PowerShell unless necessary\n   - Do not pass non-ASCII text through shell pipelines, PowerShell here-strings, `python -c`, or `node -e`; write the text to a UTF-8 file with a file-editing tool and consume that file instead\n3. **Icons:** Do not hand-write SVG; use existing icons from `app/appearance/icons/litheness/icon.js` when possible\n   - If no existing icon is suitable, source one from the official [Lucide icon library](https://lucide.dev/icons/) and adapt only attributes such as stroke width to match the established icon style; preserve the upstream path data\n   - When adding an icon to `app/appearance/icons/litheness/icon.js`, add its preview entry to `app/appearance/icons/index.html` in the same change and keep the order aligned\n4. **User guide:** When editing the user guide, follow `docs/SY-FORMAT.md`\n   - User-guide changes must be synchronized across all four bundled languages: English (`app/guide/20210808180117-6v0mkxr`), Simplified Chinese (`app/guide/20210808180117-czj9bvb`), Traditional Chinese (`app/guide/20211226090932-5lcq56f`), and Japanese (`app/guide/20240530133126-axarxgx`). Do not omit Japanese or discover corresponding pages only by Chinese/English title matching\n   - Before finishing a guide change, enumerate the language directories under `app/guide/` and verify the corresponding section in each language. Translate the content, preserve existing document/block IDs, generate fresh IDs for new blocks, and validate the changed `.sy` JSON. `docs/` developer documentation does not replace the bundled user guide\n   - When a feature adds or changes shortcuts, update the shortcut documentation in the user guide in the same change; if the appropriate section is unclear, ask the user where it should be placed\n   - Outside list items, complete prose paragraphs must end with appropriate punctuation for their language (for example `.`, `。`, `?`, or `！`); use a colon when introducing following content. Headings, navigation/reference directories, short labels, standalone links, and code, formulas, or technical syntax are exceptions and must not receive punctuation mechanically\n   - List item text must not end with a period or equivalent sentence-ending mark (for example `.`, `。`, or `।`)\n   - Represent in-app UI navigation paths as segmented `kbd` text marks: use one `NodeTextMark` with `TextMarkType: \"kbd\"` per navigation level, and place a plain `NodeText` containing ` - ` between adjacent levels\n   - In every language, separate a `kbd` path from adjacent prose with exactly one ASCII space on each side, except at block boundaries, after full-width punctuation, or before any punctuation. Half-width punctuation before the path still requires a space; keep internal ` - ` separators unchanged\n5. **Git:**\n   - Branch policy is repository-specific: in `siyuan-note/siyuan`, use `dev` as the baseline source branch and default working branch. If `dev` is unavailable, use the repository's verified default branch. Do not commit on or push to `master` while `dev` is available; when `dev` is unavailable and `master` is the verified default branch, it is permitted\n   - Select the working branch from the user's requested outcome. For direct commits and explicitly authorized pushes, use the selected baseline as the working branch, commit branch, and push destination unless the user explicitly requests a task branch. When the user explicitly requests an ordinary development pull request, create a task branch from the verified baseline before the first change and use it as the PR head, including when the authorized push destination is a fork; a shared baseline branch such as `dev`, or a fork mirror of it, must not be used as the PR head except for the official release workflow below. GitHub does not support changing an existing PR's head branch, so verify the head before creating the PR. Creating a branch does not authorize committing or pushing without the user's authorization. Do not create or push a task branch merely to isolate changes or because the selected working branch has uncommitted work or is behind the remote\n   - **Official releases:** Before release work, read and follow [docs/RELEASE-VERIFICATION.zh-CN.md](docs/RELEASE-VERIFICATION.zh-CN.md) for preparation, build verification, integration, and history synchronization. Build from a verified `dev` commit and keep it fixed until the release PR is merged; if it advances, stop and reconcile the build inputs. With authorized release integration, use `dev` as the PR head and `master` as the base, the sole exception to the shared-branch PR rule. Use a merge commit; if repository settings prohibit it, report the limitation instead of substituting squash or rebase. Verify the resulting `master` tree against the package build commit before creating a release tag\n   - **Release history and authorization:** Before further development, merge the released `master` commit back into `dev`, preserving both histories and verifying an unchanged released code tree when no subsequent code changes have occurred. Perform this synchronization only with authorization for the merge and explicit authorization for its push. The release exception does not authorize direct commits or pushes to `master`, or unrequested PR merges, tags, uploads, or publication\n   - In other repositories, follow their own branch rules for baseline selection and work; otherwise prefer `dev` when it exists locally or on the remote, and use the repository's actual default branch when `dev` does not exist. Do not extend SiYuan's `master` prohibition to other repositories or assume their default branch is named `master` or `main`\n   - If the intended branch cannot be determined confidently from the user's instructions, the repository's rules, and local or remote branch information, ask the user which branch to use and wait for their answer before editing, committing, or pushing; do not guess or silently choose a fallback\n   - Before editing, verify the repository and current branch. Before every explicitly authorized commit or push, verify the current branch again and, for a push, the destination remote and branch; never rely only on the upstream configuration. If SiYuan is checked out on a branch other than the selected working branch, switch to the selected working branch before making changes, or create and switch to a task branch based on the selected baseline when the user explicitly requests a pull request or task branch; preserve existing work and do not reset or discard it to switch branches\n   - When explicitly asked to commit, follow the style of recent commits (gitmoji prefix + subject, in English)\n   - Append the full issue/PR URL to the end of the commit title (e.g. `https://github.com/siyuan-note/siyuan/issues/<NNN>`, not the `#NNN` short form — it is clickable) only when a related issue exists; never put the URL in the commit body, and do not fabricate one\n   - When creating a pull request, write an English title without a gitmoji shortcode or emoji prefix. Describe the final change in the PR title instead of copying a commit title with its gitmoji prefix or appended issue URL\n6. **GitHub:** Prefer the GitHub CLI (`gh`) for all GitHub operations, including reading issues, comments, pull requests, commits, statuses, and metadata. If `gh` is unavailable or does not support the operation, fall back to the GitHub API or web interface\n   - When creating an issue in this repository, use an English title and a Chinese body whose first paragraph is the corresponding Chinese title; do not use this repository's own issue templates or reproduce their form fields, and write a task-specific body directly. Preserve the relevant details from the discussion, including the problem, reproduction steps, expected and actual behavior, evidence, agreed approach, constraints, compatibility concerns, alternatives and tradeoffs, acceptance criteria, and unresolved questions, as applicable. Remove repetition and unrelated material, but do not omit information needed to understand, evaluate, or implement the issue merely to make it shorter\n   - Before filing an issue or pull request in any other repository, read that repository's own templates first and follow the one matching the task: check `.github/ISSUE_TEMPLATE/` (issue forms and Markdown templates) and the PR template, then fill every section it marks as required. The conventions above — an English title, a Chinese body, and bypassing the templates — apply only to this repository and must not be carried over. Confirm separately whether and where the project accepts issues and pull requests; do not infer whether pull requests are accepted from mirror status or disabled issues. Identify the correct issue tracker (for example, `golang/go` carries gopls issues, not `golang/tools`) and code contribution destination before submitting\n   - When changing the title of an existing issue, keep the replaced title as the first paragraph of the body, separated from the remaining content by a blank line, and keep the rest of the body unchanged\n   - When replying to an issue or pull request, explain the conclusion and its basis with detail proportional to the task. Include relevant implementation decisions, behavior changes, compatibility concerns, and unresolved issues. Report only verification and tests actually performed and the conclusions they support; do not list unperformed tests. Briefly disclose any verification gap that materially limits the conclusion, and never imply broader coverage than was verified. Avoid routine logs and unrelated commands. For implemented changes, invite the user to test the new version and provide feedback. If the issue or pull request body is in Chinese, reply in Chinese; otherwise, reply bilingually in the language of the body and Chinese.\n   - When the selected endpoint supports labels, they may be included in the same create or update payload. Verify the issue or pull request identity and the requested non-label changes, including title, body, state, or base branch as applicable. The exception applies only to labels: do not check whether they were applied or send a follow-up request solely to apply them, and do not claim they were verified; GitHub may silently drop label changes when the actor lacks push access\n   - For GitHub write operations containing non-ASCII text on Windows or when shell encoding is uncertain, use this file-based workflow. Do not use this workflow for ASCII-only requests:\n     1. Create the request payload as UTF-8 JSON with a file-editing tool, not an inline shell command\n     2. Store it in the operating system's temporary directory with a unique name such as `siyuan-gh-<operation>-<timestamp>.json`; do not leave temporary payloads in the repository\n     3. Call the appropriate endpoint with `gh api --method <method> \"<endpoint>\" --input \"<absolute-json-path>\"`\n     4. Inspect the returned resource and read it back with `gh api`. Prefer parsing JSON and comparing its fields with the submitted payload to verify the published text exactly, including line breaks and non-ASCII characters. Normalize CRLF to LF if needed, but preserve meaningful leading/trailing whitespace and blank lines. For `--jq` text output, query one field per call (for example `.title` and `.body`) and remove only the single output line terminator added by the command\n     5. Keep the submitted payload until comparison passes; then delete temporary payload and read-back files and confirm their removal\n   - Prefer capturing `gh` stdout directly in a Node.js or Python script and decoding it as UTF-8. When using PowerShell to save output, explicitly use `Out-File -Encoding utf8`; Windows PowerShell 5.1 defaults to UTF-16LE for `>` and `Out-File`, and its UTF-8 output includes a BOM, so readers must handle that BOM. Explicit file encoding cannot repair text already corrupted by pipeline decoding; use direct process capture in that case\n   - `gh api` applies `--jq` locally after sending the request, so an invalid or failing expression can cause a non-zero exit even when the write succeeded. Omit `--jq` on write commands and use separate read-back calls to verify individual fields. When a write command exits non-zero, confirm the remote state before deciding whether to retry; the write may already have succeeded, and retrying can create duplicate issues or comments or repeat other side effects\n7. **Issue titles:** Whenever the user asks to generate an issue title, provide it in English regardless of the wording of the request, and do not start it with `Fix`. These rules choose title wording from the issue's nature; they are not an instruction to apply GitHub labels\n   - For a bug, objectively describe the problem or symptom instead of writing from a bug-fix perspective\n   - For an improvement to existing functionality, write the title from an improvement perspective and prefer `Improve ...`\n   - For a capability that did not previously exist, write the title from a support perspective and prefer `Support ...`\n   - If the nature is unclear, infer the perspective from the issue content\n8. **LD246:**\n   - When accessing `ld246.com`, set the HTTP `User-Agent` header to `SiYuan-Coding-Agent`\n   - Read post details with `GET https://ld246.com/api/v2/article/{articleId}?p=1`; `data.article` contains the post, and `p` paginates replies. For Markdown, use `GET https://ld246.com/article/raw/{articleId}`\n   - Authenticate both endpoints with `Authorization: token <LD_TOKEN>` using the environment variable; on Windows, check process, user, then machine scope. Do not expose the Token or rely on UA exemptions\n   - Check HTTP status and JSON `code` (`0` means success); report missing credentials or authentication errors\n9. **Configurable entries:**\n   - Treat configurable desktop menu `data-id` values and stable dock entry keys as persisted configuration identifiers. Resolve dock keys through `getDockEntryKey` in `app/src/config/entryVisibility/catalog.ts`: it prefers `data-entry-id`, maps plugin runtime types to stable keys derived from plugin name and dock ID, and falls back to the built-in `data-type`. Do not rename or reuse persisted keys unless the same change migrates existing visibility and order configuration\n   - When adding, removing, renaming, or moving a configurable desktop menu item or dock entry, or changing its identifier mapping, update `app/src/config/entryVisibility/catalog.ts` in the same change, including its type, hierarchy, label, Simple profile default, and default position, and update the related tests\n   - Give every configurable desktop menu separator a stable `data-id` and register it in the catalog as a `separator`. Keep the catalog order aligned with the actual menu declaration order because it defines the built-in order and where new entries are merged into existing custom profiles\n   - Keep parent and child paths aligned with the actual menu hierarchy. Dock entries support visibility, ordering, and movement across the six scopes in `dockOrder.ts`; use that mechanism rather than treating dock entries as ordinary menu siblings. Preserve existing placement, hidden entries, unavailable plugin slots, and compatibility with profiles that have no dock orders\n   - Cover catalog consistency, separator placement, order migration, and plugin-slot preservation in the related tests. Configured menus must not produce leading, trailing, or consecutive separators\n   - The menu `ignore` option controls conditional rendering and must not be used to opt an entry out of visibility or order configuration\n10. **API contracts:**\n    - Follow [docs/API-CONTRACTS.md](docs/API-CONTRACTS.md) when adding or changing kernel HTTP APIs. Define new endpoints in `kernel/apicontract/` and bind their handlers through `contractHandler`; keep contracts synchronized when changing existing endpoints\n    - Treat `docs/API-CONTRACTS.md` and its localized versions as maintenance guides. For routine endpoint or feature changes, do not append endpoint-specific behavior, feature summaries, verification results, or focused test commands. Update these guides only when the contract mechanism, compatibility policy, generation workflow, or test discovery/verification rules change, or when the user explicitly requests a documentation update; keep localized versions aligned\n    - Document endpoint behavior, defaults, constraints, and compatibility in comments on the corresponding contract source declarations. Synchronize plugin-facing explanations in the corresponding maintained declarations in `petal`, following the Petal documentation rule below. Put user-facing feature instructions in the bundled user guide\n    - Do not automatically add newly implemented endpoints to `docs/API.md` or its localized versions (`docs/API.zh-CN.md`, `docs/API.ja.md`); add such documentation only when explicitly requested by the user. Continue maintaining API contracts, generated declarations, and required regression tests. These documentation scope rules take precedence over broader documentation instructions in the maintenance guides\n    - After changing an existing endpoint's request, response, or documented behavior, check its existing reference entries in `docs/API.md`, `docs/API.zh-CN.md`, and `docs/API.ja.md`. Update affected entries together; do not assume the language versions cover identical endpoints. Newly added endpoints remain documented only on request\n    - Preserve existing input compatibility, response variants, authorization, and encrypted notebook lease behavior; cover affected behavior with regression tests\n    - Every route must have a typed contract; remove both the registration and contract when deleting a route, and never bypass contract checks with `any` or type assertions\n    - After contract changes, run `pnpm run api:generate --petal ../../petal` and `pnpm run api:check --petal ../../petal` from `app/`; verify the Petal checkout first using the cross-repo rules below. The example path is relative to the generator's `kernel/` working directory and assumes a sibling `petal` repository; substitute the verified path when different. Synchronize related public declarations in `petal` and do not hand-edit generated declarations or schemas\n    - Run `pnpm run lint` from `app/`, `go test ./apicontract/...` from `kernel/`, and the applicable API compatibility and route coverage tests described in the maintenance document. Verify that existing CI selections and documented commands discover new regression cases; automatic discovery or coverage by an existing full-suite command satisfies this requirement without a documentation edit. Update CI selections and documented commands only when they would otherwise miss the new tests. Report task-specific verification commands and results in the task response or PR description\n\n11. **Desktop and mobile parity:**\n    - When changing functionality shared by desktop and mobile, inspect the corresponding mobile implementation under `app/src/mobile/` and update it in the same task. This includes menus, toolbars, dialogs, interactions, and their event handlers; do not assume that desktop changes automatically apply to mobile\n    - Before finishing, verify both implementations and their shared dependencies, preserving platform-appropriate interactions. If no corresponding mobile feature exists or the change is desktop-only, state why mobile changes are not needed in the task response\n\n12. **UI copy and user guides:** Change wording only when needed, following nearby style and keeping necessary guidance and warnings. Put specific instructions beside the relevant control or in the corresponding guide section; do not overload general descriptions with feature lists or implementation details\n\n13. **Configuration defaults:** Put a new setting's default value in its constructor (`NewAppearance`, `NewEditor`, `NewFileTree`, etc.) and let the shared normalization functions fill missing containers and values. `InitConf` only projects runtime state and runs versioned migrations; do not add a startup function that patches a single default\n    - `conf.json` written by an older version has no key for a new field, and configuration sections unmarshal into zero values, so a plain `bool` is only correct when the default is `false`\n    - A default of `true` requires `*bool` (and a defaulted non-zero enumeration requires `*int`): initialize it in the constructor, keep the `nil` branch in normalization, and preserve an explicitly saved `false`. See `FileTree.CreateDocAtTop`, `Editor.CheckBlockRef`, and `Search.HanSensitive`\n    - Keep a default-`true` plain `bool` only when existing workspaces must stay `false` while new workspaces start `true`, as with `Appearance.MenuMouseScroll`: the constructor supplies the new-workspace value and the zero value supplies the existing-workspace one\n    - `POST /api/setting/set<Section>` merges the request into the current section: omitted fields keep their current value and `null` restores the default; declare `Merge` on `SettingWrite` instead of adding per-field preservation branches. Decoding starts from the section constructor defaults so `null` never falls back to a zero value\n    - Keep `*bool` on the API request and response declaration when the wire format must distinguish `null`, even if the persisted configuration uses a plain `bool`; see `apicontract.SettingFileTree`\n    - When an existing workspace must retain a legacy default that is not the zero value (for example `OCR.Auto`), resolve it in the load path that knows whether `conf.json` existed; do not read that fact from a global inside a constructor or normalization helper. Resolve a segment that exists but lacks the decisive field the same way as a missing segment\n\n---\n\n## 3. Coding conventions\n\n1. **Comments:** Write in Chinese, wrap at 120 characters, and describe what the code does without referencing the old implementation\n2. **Punctuation:** Use language-appropriate punctuation in UI text, comments, user guides, and documentation (e.g. Chinese punctuation ，。：；！？「」 for Chinese). Keep UI text and its punctuation in i18n language files rather than hard-coding them in code\n3. **UI paths:** In all contexts, including code comments, UI text, i18n, user guides, documentation, issue/PR content, and responses, separate navigation levels with a hyphen surrounded by spaces (for example, `设置 - 快捷键 - 通用`); do not use arrow symbols such as `→`\n4. **Markdown:** Do not hand-wrap; keep each line (paragraphs, table rows, list items, etc.) on a single line\n5. **TypeScript/JavaScript:** Semicolons required, use double quotes, indent with spaces\n   - When moving or extracting a symbol into another module, update all affected imports to reference its defining module directly. Do not leave forwarding re-exports in the original module merely to avoid updating callers\n6. **CSS:** Do not use the `:has()` selector because of its performance impact, and prefer a comma-separated selector list over `:is()`, whose extra matching layer adds measurable cost\n   - Expand selector groups instead of grouping them with `:is()` or `:where()`; use `:where()` only to drop specificity from a single selector\n   - Before adding or styling a basic control, inspect and reuse the existing component, markup pattern, and shared styles in `app/src/assets/scss/component/` (for example, `b3-button`, `b3-select`, and `b3-text-field`), including existing modifiers; use `block__icon` for established icon-button patterns\n   - Apply the same reuse-first rule to menus, dialogs, tooltips, and drag interactions: inspect existing implementations and APIs before adding a feature-specific replacement\n   - For single-choice enumerations in menus, use submenu items with the current option checked rather than embedding a select control. Show a right-arrow indicator on the parent item, open its submenu on hover on desktop, and support tapping on mobile. Reuse the existing menu implementation and positioning behavior\n   - Feature styles should describe layout (such as placement, width constraints, gaps, and wrapping), rather than duplicate or override basic control appearance (such as height, padding, typography, colors, borders, shadows, and hover/focus/disabled states). Do not use deeper selectors, inline styles, or `!important` merely to restyle a shared control\n   - If existing controls cannot meet a requirement, explain the concrete need and prefer extending a shared component or modifier when the need is reusable. Keep necessary feature-specific exceptions narrowly scoped; use theme variables for appearance and retain keyboard focus and disabled feedback\n   - Reuse components according to their purpose; do not borrow an unrelated component (for example, menu items for a form) and then cancel its styles. Preserve useful feature classes as theme hooks when switching to shared controls\n   - When reviewing control-style changes, check consistency with shared controls, theme overrides through shared classes, light/dark themes, narrow layouts, and large editor fonts. Preserve necessary layout and touch-target constraints; do not remove all feature styles indiscriminately\n7. **CSS positioning and scrolling:** When changing `position`, `transform`, `contain`, or `overflow` on a shared container, check the effects on descendant positioning reference frames, overlay coverage, and clipping. Prefer a dedicated container when a local control needs a positioning reference. For settings dialog changes, verify detail overlays, the top drag area, and scrollbar placement at different window widths\n\n8. **Custom attributes:**\n   - HTML custom attributes used only for DOM/UI state must use the `data-` prefix without an extra `sy` namespace, for example `data-ui-density`\n   - Custom attributes owned by built-in features and persisted in notebook, document, or block data must use the `custom-sy-` prefix. Preserve existing persisted attribute names and their compatibility; DOM/UI naming changes must not rename these data attributes\n   - Define custom attribute name constants in `app/src/constants.ts`, alongside similar constants in `Constants`, rather than in individual feature modules\n\n9. **Frontend preference storage:** Do not use browser `localStorage` directly, including `window.localStorage` and `globalThis.localStorage`\n   - New user-facing preferences, including settings switches and explicit reader or panel preferences, default to `conf.json`; define them in the corresponding configuration namespace, normalize them in the kernel, and write through `createConfigNamespaceApi().patch`\n   - Reserve `local.json` for growing mappings keyed by document, view, or transaction IDs; transient or session state such as scroll positions, dialog geometry, and restored tabs; usage history and reading positions; and plugin runtime state\n   - Writing frequency does not determine storage: `uiLayout` is already a frequently updated configuration namespace with silent notifications. Neither `conf.json` nor `local.json` participates in data sync; configuration export includes `conf.json`\n   - Read local state from `window.siyuan.storage`; when changing it, update its in-memory value and persist it with `setStorageVal` from `app/src/protyle/util/compatibility.ts`. SiYuan persists these values in the workspace's `data/storage/local.json`; do not use browser `storage` events to observe changes to this store\n\n10. **Frontend platform identification:**\n   - Use the mutually exclusive `body` class names written by `applyPlatformClasses()` in `app/src/protyle/util/compatibility.ts`: `body--mac`, `body--windows`, `body--linux`, `body--ios`, `body--android`, and `body--harmony`\n   - `body--win32` and `body--win32-browser` are retained only for existing third-party themes; use neither in new code, and do not add consumers when changing related styles\n   - Do not derive the frontend platform from the kernel reported container or from `data-backend`: a remote kernel reports its own host, so both differ from the platform of the current frontend\n   - `data-frontend` on the root element reports the frontend form (`desktop`, `desktop-window`, `mobile`, `browser-desktop`, `browser-mobile`) and may be used by built-in styles and third-party theme CSS; `data-backend` on the root element is for third-party JavaScript only and must not appear in built-in CSS\n\n---\n\n## 4. Required toolchain\n\n| Tool | Version | Source of truth |\n|---|---|---|\n| Go | see `go` directive | `kernel/go.mod` |\n| Node (+ pnpm) | see CI matrix | `.github/workflows/cd.yml`, `app/package.json` (`packageManager` field) |\n\n---\n\n## 5. Repository layout\n\n**Architecture:** Go kernel (`kernel/`) + TypeScript frontend (`app/`). Read versions from `kernel/go.mod`, `app/package.json`, `kernel/util/working.go`.\n\nTop level (repo root):\n\n| Path | Contents |\n|---|---|\n| `kernel/` | Go backend — server, data engine, API, all domain logic |\n| `app/` | TypeScript frontend (Electron/web), built by webpack into `app/stage/build/` |\n| `app/appearance/` | Themes, icons, **i18n** (`appearance/langs/*.json`) |\n| `app/stage/` | Build output served by the kernel |\n| `app/changelogs/` | Per-version changelog markdown |\n| `.github/` | Contribution and security policies, issue/PR templates, CI workflows |\n| `scripts/` | Release packaging, changelog generation, language-key checks |\n\n### Major `kernel/` packages (under `kernel/`)\n\n| Package | Responsibility |\n|---|---|\n| `main.go` (`//go:build !mobile`) | Desktop entry point, delegates to `cli/cmd` |\n| `cli/cmd/` | Cobra CLI subcommands |\n| `model/` | Core domain: blocks, transactions, indexing, search, attribute views, export, history, sync, flashcards, AI, CalDAV/CardDAV, auth |\n| `treenode/` | In-memory tree over the Lute AST + `blocktree.db` |\n| `av/` | **Attribute View** (database) engine: values, filters, sorts, layouts (table/kanban/gallery) |\n| `sql/` | **Embedded SQLite** (`siyuan.db`, `history.db`, `asset_content.db`) + FTS5; async index queues |\n| `search/` | FTS tokenizer helpers, CJK conversion (`hanconv.go`) |\n| `bazaar/` | Marketplace: plugins/widgets/themes/icons/templates |\n| `filesys/` | Read/write `.sy` files on disk (via `filelock`) |\n| `server/` | Gin server bootstrap (`serve.go`): middleware, TLS/cmux, WebDAV/CalDAV/CardDAV, WebSocket, MCP |\n| `api/` | HTTP route registration (`router.go::ServeAPI`) + per-area handlers |\n| `conf/` | Configuration structs |\n| `util/` | Cross-cutting: `working.go` (workspace, `Boot()`), `lute.go`, `i18n.go`, `websocket.go` (melody push), `result.go` (API envelope) |\n| `plugin/` | Plugin subsystem (kernel side) |\n| `mcp/` | MCP (Model Context Protocol) server |\n| `agent/` | AI agent runtime |\n| `mobile/`, `harmony/` | `//go:build mobile` gomobile bindings for Android/iOS/HarmonyOS |\n\n### Frontend (`app/src/`) highlights\n\n| Dir | Purpose |\n|---|---|\n| `index.ts` | Main `App` class — boots SPA, opens main WebSocket, handles WS push events |\n| `window/` | Detached Electron window variant |\n| `protyle/` | Block editor; rich-content rendering in `render/`, attribute-view rendering in `render/av/` |\n| `editor/`, `layout/`, `menus/`, `dialog/`, `config/`, `mobile/`, `ai/`, `sync/`, `history/`, `search/`, `card/` | Feature modules |\n| `util/fetch.ts` | `fetchGet`/`fetchPost` — all kernel calls |\n| `layout/Model.ts` | WebSocket client all UI binds to |\n| `constants.ts` | Global constants (version, IDs, storage keys) |\n\nFour webpack configs emit bundles to `app/stage/build/{app,desktop,mobile,export}/`; the kernel's `serveAppearance` selects the app bundle by User-Agent. The `export` bundle is a rendering library (global `Protyle`, entry `app/src/protyle/method.ts`) for code, math, and diagrams. Pages assembled by `app/src/protyle/export/index.ts` load it for desktop PDF preview and standalone exported HTML.\n\n---\n\n## 6. Related repositories (navigation)\n\n| Repo | Role / what to know |\n|---|---|\n| `siyuan-android` / `siyuan-ios` / `siyuan-harmony` | Native apps wrapping the gomobile kernel; read each project's README for platform-specific build, kernel-binding, and integration steps |\n| `siyuan-chrome` | Independent TypeScript browser extension (web clipper); interacts with the running kernel only through the HTTP API in `docs/API.md` |\n| `siyuan-testing` | Playwright end-to-end tests for a running SiYuan instance; test data belongs in the `SiYuan Testing` notebook — see that repository's `AGENTS.md` |\n| `petal` | SiYuan Plugin API declaration (the plugin system is named \"petal\"); consumed by plugins, not a kernel Go dependency |\n| `lute` | Markdown/Kramdown AST engine for the editor and `.sy` format; source of `lute.min.js`. The maintainer's default checkout is `$GOPATH/src/github.com/88250/lute`, not a sibling repo; verify the actual checkout before use |\n| `dejavu` | Data repo / sync engine (encrypted snapshots) |\n| `riff` | Spaced-repetition (SRS) flashcard scheduler |\n| `gulu` / `eventbus` / `filelock` / `httpclient` / `logging` | General utilities, in-process events, file locking, HTTP client, and logging |\n| `go-sqlite3` / `pdfcpu` | Maintainer's forks, pulled in via permanent `replace` in `kernel/go.mod` (keep those) |\n| `epub` / `clipboard` / `go-humanize` / `vitess-sqlparser` / `dataparser` / `encryption` | Smaller Go libraries (export / clipboard / formatting / SQL parse / data parse / crypto) |\n\nAll Go libraries above are dependencies in `kernel/go.mod`. GitHub org: `siyuan-note/*` for the `siyuan-` apps and most libs; `88250/*` for lute, gulu, and the forks (go-sqlite3 / pdfcpu).\n\n### Cross-repo notes\n\n- **Checkout availability:** Before cross-repo work, verify each checkout's path, identity, branch, baseline, and existing changes under its own rules. If a required checkout is unavailable, complete independent work where possible and report the exact synchronization or verification blocker; do not claim the task is fully synchronized. These rules do not authorize automatically creating or downloading another repository\n- **Editing Go dependencies:** To test a local change, add a temporary `replace` in `kernel/go.mod` pointing at your local checkout; **never commit that temporary `replace`**.\n- **Rebuilding `lute.min.js`:** Change `lute`, rebuild with GopherJS, and copy the artifact into `app/stage/protyle/js/lute/`.\n- **Type declarations:** when changing files under `app/src/types/` or other TypeScript declarations and constants exposed to plugins, synchronize the corresponding declarations and constants in the `petal` repository in the same task.\n- **Petal changelog:** Whenever changing files in the `petal` repository, update `petal/CHANGELOG.md` in the same task. Add a concise entry under the current unreleased version before committing the Petal change.\n- **Petal documentation:** Keep `petal/README.md` limited to the project title and the `plugin-sample` link. Do not add feature descriptions, API usage explanations, or code examples to the README. Document API behavior, constraints, and lifecycle in comments on the corresponding API declarations in `petal`; put executable usage examples in the actual source code of `plugin-sample`.\n\n---\n\n## 7. Response style\n\n1. **Language:** Match the user's language; do not mix languages mid-sentence (keep proper nouns / identifiers in their original form)\n","isInternal":false,"tokens":10118,"sizeBytes":47608},{"name":"API.md","path":"docs/API.md","rawUrl":"https://raw.githubusercontent.com/siyuan-note/siyuan/HEAD/docs/API.md","title":"docs - API Interface Contract & Specifications","category":"project-spec","format":"markdown","content":"**English**\n| [中文](API.zh-CN.md)\n| [日本語](API.ja.md)\n\nPlugin resource declarations, data authorization, and publishing APIs are documented in [Plugin publishing](PLUGIN-PUBLISH.md).\n\n* [Specification](#Specification)\n    * [Parameters and return values](#Parameters-and-return-values)\n    * [TypeScript contracts](#TypeScript-contracts)\n    * [Behavior semantics](#Behavior-semantics)\n    * [Authentication](#Authentication)\n* [Notebooks](#Notebooks)\n    * [List notebooks](#List-notebooks)\n    * [Open a notebook](#Open-a-notebook)\n    * [Close a notebook](#Close-a-notebook)\n    * [Rename a notebook](#Rename-a-notebook)\n    * [Create a notebook](#Create-a-notebook)\n    * [Remove a notebook](#Remove-a-notebook)\n    * [Get notebook configuration](#Get-notebook-configuration)\n    * [Save notebook configuration](#Save-notebook-configuration)\n* [Documents](#Documents)\n    * [Create a document with Markdown](#Create-a-document-with-Markdown)\n    * [Rename a document](#Rename-a-document)\n    * [Remove a document](#Remove-a-document)\n    * [Move documents](#Move-documents)\n    * [Set notebook and document sort values](#Set-notebook-and-document-sort-values)\n    * [Set a document's child document sort mode](#Set-a-documents-child-document-sort-mode)\n    * [Get human-readable path based on path](#Get-human-readable-path-based-on-path)\n    * [Get human-readable path based on ID](#Get-human-readable-path-based-on-ID)\n    * [Get storage path based on ID](#Get-storage-path-based-on-ID)\n    * [Get IDs based on human-readable path](#Get-IDs-based-on-human-readable-path)\n* [Assets](#Assets)\n    * [Upload assets](#Upload-assets)\n* [Blocks](#Blocks)\n    * [Insert blocks](#Insert-blocks)\n    * [Prepend blocks](#Prepend-blocks)\n    * [Append blocks](#Append-blocks)\n    * [Update a block](#Update-a-block)\n    * [Delete a block](#Delete-a-block)\n    * [Move a block](#Move-a-block)\n    * [Fold a block](#Fold-a-block)\n    * [Unfold a block](#Unfold-a-block)\n    * [Get a block kramdown](#Get-a-block-kramdown)\n    * [Get child blocks](#get-child-blocks)\n    * [Transfer block ref](#transfer-block-ref)\n* [Attributes](#Attributes)\n    * [Set block attributes](#Set-block-attributes)\n    * [Get block attributes](#Get-block-attributes)\n* [Database](#Database)\n    * [Render](#Render)\n    * [Get](#Get)\n    * [Get primary key values](#Get-primary-key-values)\n    * [Search](#Search)\n    * [Set a cell value](#Set-a-cell-value)\n    * [Add items](#Add-items)\n    * [Remove items](#Remove-items)\n    * [Change layout](#Change-layout)\n    * [Set grouping](#Set-grouping)\n    * [Get filter and sort](#Get-filter-and-sort)\n    * [Set filter](#Set-filter)\n    * [Set sort](#Set-sort)\n    * [Add a field](#Add-a-field)\n    * [Remove a field](#Remove-a-field)\n    * [Set global field sort](#Set-global-field-sort)\n    * [Set per-view field sort](#Set-per-view-field-sort)\n* [Search](#Search)\n    * [Get saved search criteria](#Get-saved-search-criteria)\n    * [Save a search criterion](#Save-a-search-criterion)\n    * [Remove a search criterion](#Remove-a-search-criterion)\n* [SQL](#SQL)\n    * [Execute SQL query](#Execute-SQL-query)\n    * [Flush transaction](#Flush-transaction)\n* [Templates](#Templates)\n    * [Render a template](#Render-a-template)\n    * [Render Sprig](#Render-Sprig)\n* [File](#File)\n    * [Get file](#Get-file)\n    * [Put file](#Put-file)\n    * [Remove file](#Remove-file)\n    * [Rename file](#Rename-file)\n    * [List files](#List-files)\n* [Export](#Export)\n    * [Export Markdown](#Export-Markdown)\n    * [Export Files and Folders](#Export-files-and-folders)\n* [Conversion](#Conversion)\n    * [Pandoc](#Pandoc)\n* [Notification](#Notification)\n    * [Push message](#Push-message)\n    * [Push error message](#Push-error-message)\n* [Network](#Network)\n    * [Forward proxy](#Forward-proxy)\n        * [JSON forward proxy](#JSON-forward-proxy)\n        * [HTTP forward proxy](#HTTP-forward-proxy)\n        * [WebSocket forward proxy](#WebSocket-forward-proxy)\n        * [EventSource forward proxy](#EventSource-forward-proxy)\n* [System](#System)\n    * [Get boot progress](#Get-boot-progress)\n    * [Get system version](#Get-system-version)\n    * [Get the current time of the system](#Get-the-current-time-of-the-system)\n\n---\n\n## Specification\n\n### Parameters and return values\n\n* Endpoint: `http://127.0.0.1:6806`\n* Unless otherwise stated, API interfaces use the POST method\n* For interfaces that take JSON parameters, the parameter is a JSON string placed in the body, and the header Content-Type is `application/json`\n* Return value\n\n   ````json\n   {\n     \"code\": 0,\n     \"msg\": \"\",\n     \"data\": {}\n   }\n   ````\n\n    * `code`: non-zero for exceptions\n    * `msg`: an empty string under normal circumstances, an error text will be returned under abnormal conditions\n    * `data`: may be `{}`, `[]` or `NULL`, depending on the interface\n\n### TypeScript contracts\n\nThe plugin `fetchPost`, `fetchSyncPost`, and `fetchGet` declarations infer request and response types for migrated API paths from generated kernel contracts. Coverage is expanding and includes system utilities, batch block attributes, tag and bookmark operations, selected block queries, notebook listing, history search, and snapshot operations. Existing untyped endpoints and dynamic URLs remain supported. Check the response code before reading successful data from asynchronous calls, and handle nullable fields explicitly.\n\n```typescript\nimport {fetchSyncPost} from \"siyuan\";\n\nconst response = await fetchSyncPost(\"/api/attr/getBlockAttrs\", {id: blockID});\nif (response.code === 0 && response.data) {\n    const value = response.data[\"custom-value\"];\n}\n```\n\nSee the [generated route declarations](../app/src/types/api/index.d.ts) for exact coverage and the [contract maintenance guide](API-CONTRACTS.md) for generation and compatibility rules. Type declarations do not validate JSON at runtime.\n\n### Behavior semantics\n\n* Only interfaces with dedicated interface sections in this document are public APIs. Other kernel routes and `/api/transactions` operations are internal implementations and provide no compatibility or behavioral stability guarantees unless otherwise stated\n* `code: 0` means the interface reported no error while handling the request. It guarantees only the outcome explicitly documented for that interface; it does not mean that related indexes, caches, WebSocket broadcasts, or sync state have all been updated\n* The meanings of omitted fields, `null`, empty objects, and empty arrays are interface-specific. Whether an object or array replaces, merges with, or partially updates existing state, and whether its order is significant, are also defined by each interface\n* An interface may trim, ignore, complete, or transform input. When its documentation states that a normalized result is returned, callers should use the returned `data` as the actual accepted result\n* Do not infer that an operation is read-only from its name. Persistent side effects and their scope are described by each interface when applicable\n* Repeating the same request is idempotent or safe to retry only when explicitly documented. If a response is interrupted or otherwise indeterminate, read the current state before retrying whenever possible\n\n### Authentication\n\nView the API token in <kbd>Settings - Authentication - API token</kbd>. Use it in the request header: `Authorization: Token xxx`\n\n## Notebooks\n\n### List notebooks\n\n* `/api/notebook/lsNotebooks`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"notebooks\": [\n        {\n          \"id\": \"20210817205410-2kvfpfn\", \n          \"name\": \"Test Notebook\",\n          \"icon\": \"1f41b\",\n          \"sort\": 0,\n          \"closed\": false\n        },\n        {\n          \"id\": \"20210808180117-czj9bvb\",\n          \"name\": \"SiYuan User Guide\",\n          \"icon\": \"1f4d4\",\n          \"sort\": 1,\n          \"closed\": false\n        }\n      ]\n    }\n  }\n  ```\n\n### Open a notebook\n\n* `/api/notebook/openNotebook`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Close a notebook\n\n* `/api/notebook/closeNotebook`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nThe close endpoint validates the notebook ID without trimming whitespace. Typed request and response declarations are generated in `app/src/types/api/index.d.ts` and synchronized to `petal`.\n\n### Rename a notebook\n\n* `/api/notebook/renameNotebook`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\",\n    \"name\": \"New name for notebook\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Create a notebook\n\n* `/api/notebook/createNotebook`\n* Parameters\n\n  ```json\n  {\n    \"name\": \"Notebook name\"\n  }\n  ```\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"notebook\": {\n        \"id\": \"20220126215949-r1wvoch\",\n        \"name\": \"Notebook name\",\n        \"icon\": \"\",\n        \"sort\": 0,\n        \"closed\": false\n      }\n    }\n  }\n  ```\n\n### Remove a notebook\n\n* `/api/notebook/removeNotebook`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Get notebook configuration\n\n* `/api/notebook/getNotebookConf`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210817205410-2kvfpfn\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"box\": \"20210817205410-2kvfpfn\",\n      \"conf\": {\n        \"name\": \"Test Notebook\",\n        \"closed\": false,\n        \"refCreateSavePath\": \"\",\n        \"createDocNameTemplate\": \"\",\n        \"dailyNoteSavePath\": \"/daily note/{{now | date \\\"2006/01\\\"}}/{{now | date \\\"2006-01-02\\\"}}\",\n        \"dailyNoteTemplatePath\": \"\"\n      },\n      \"name\": \"Test Notebook\"\n    }\n  }\n  ```\n\n### Save notebook configuration\n\n* `/api/notebook/setNotebookConf`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210817205410-2kvfpfn\",\n    \"conf\": {\n        \"name\": \"Test Notebook\",\n        \"closed\": false,\n        \"refCreateSavePath\": \"\",\n        \"createDocNameTemplate\": \"\",\n        \"dailyNoteSavePath\": \"/daily note/{{now | date \\\"2006/01\\\"}}/{{now | date \\\"2006-01-02\\\"}}\",\n        \"dailyNoteTemplatePath\": \"\"\n      }\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"name\": \"Test Notebook\",\n      \"closed\": false,\n      \"refCreateSavePath\": \"\",\n      \"createDocNameTemplate\": \"\",\n      \"dailyNoteSavePath\": \"/daily note/{{now | date \\\"2006/01\\\"}}/{{now | date \\\"2006-01-02\\\"}}\",\n      \"dailyNoteTemplatePath\": \"\"\n    }\n  }\n  ```\n\n## Documents\n\n### Create a document with Markdown\n\n* `/api/filetree/createDocWithMd`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210817205410-2kvfpfn\",\n    \"path\": \"/foo/bar\",\n    \"markdown\": \"\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n    * `path`: Document path, which needs to start with `/` and separate levels with `/` (corresponds to the database `hpath` field)\n        * `/` is a hierarchy separator and cannot represent a literal slash in a document title; missing parent documents are created automatically\n        * For example, `/Notes/Programming in C/C++` creates a document titled `C++` under `Programming in C` under `Notes`\n        * Importers should sanitize each title before joining titles into a path, for example by replacing ASCII `/` with full-width `／` (U+FF0F): `/Notes/Programming in C／C++` creates a document titled `Programming in C／C++` under `Notes`. This replacement changes the title text\n    * `markdown`: GFM Markdown content\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"20210914223645-oj2vnx2\"\n  }\n  ```\n\n    * `data`: Created document ID\n    * If you use the same `path` to call this interface repeatedly, the existing document will not be overwritten\n\n### Rename a document\n\n* `/api/filetree/renameDoc`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\",\n    \"path\": \"/20210902210113-0avi12f.sy\",\n    \"title\": \"New document title\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n    * `path`: Document path\n    * `title`: New document title\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nRename a document by `id`:\n\n* `/api/filetree/renameDocByID`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210902210113-0avi12f\",\n    \"title\": \"New document title\"\n  }\n  ```\n\n    * `id`: Document ID\n    * `title`: New document title\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Remove a document\n\n* `/api/filetree/removeDoc`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\",\n    \"path\": \"/20210902210113-0avi12f.sy\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n    * `path`: Document path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nRemove a document by `id`:\n\n* `/api/filetree/removeDocByID`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210902210113-0avi12f\"\n  }\n  ```\n\n    * `id`: Document ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Move documents\n\n* `/api/filetree/moveDocs`\n* Parameters\n\n  ```json\n  {\n    \"fromPaths\": [\"/20210917220056-yxtyl7i.sy\"],\n    \"toNotebook\": \"20210817205410-2kvfpfn\",\n    \"toPath\": \"/\"\n  }\n  ```\n\n    * `fromPaths`: Source paths\n    * `toNotebook`: Target notebook ID\n    * `toPath`: Target path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nMove documents by `id`:\n\n* `/api/filetree/moveDocsByID`\n* Parameters\n\n  ```json\n  {\n    \"fromIDs\": [\"20210917220056-yxtyl7i\"],\n    \"toID\": \"20210817205410-2kvfpfn\"\n  }\n  ```\n\n    * `fromIDs`: Source docs' IDs\n    * `toID`: Target parent doc's ID or notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Reorder documents relative to a sibling\n\n* `/api/filetree/reorderDocs`\n* Parameters\n\n  ```json\n  {\n    \"sourceIDs\": [\"20210917220056-yxtyl7i\"],\n    \"targetID\": \"20210917220057-abcdefg\",\n    \"position\": \"before\"\n  }\n  ```\n\n    * `sourceIDs`: Source document IDs inserted in array order\n    * `targetID`: Target sibling document ID\n    * `position`: `before` or `after`\n    * After moving, every source document must have the same notebook and parent as the target; sorting uses the complete sibling list, including hidden and unlisted documents\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"changed\": true,\n      \"notebook\": \"20210817205410-2kvfpfn\",\n      \"parentPath\": \"/\"\n    }\n  }\n  ```\n\n### Reorder notebooks relative to another notebook\n\n* `/api/notebook/reorder`\n* Parameters\n\n  ```json\n  {\n    \"sourceIDs\": [\"20210817205410-2kvfpfn\"],\n    \"targetID\": \"20210817205411-abcdefg\",\n    \"position\": \"after\"\n  }\n  ```\n\n    * `sourceIDs`: Source notebook IDs inserted in array order\n    * `targetID`: Target notebook ID\n    * `position`: `before` or `after`\n    * Sorting uses the complete notebook list, including closed notebooks\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"changed\": true\n    }\n  }\n  ```\n\n### Set notebook and document sort values\n\n* `/api/filetree/setSort`\n* Parameters\n\n  ```json\n  {\n    \"notebookSorts\": [\n      {\n        \"id\": \"20210817205410-2kvfpfn\",\n        \"sort\": -10\n      }\n    ],\n    \"docSorts\": [\n      {\n        \"id\": \"20210917220056-yxtyl7i\",\n        \"sort\": -8\n      }\n    ]\n  }\n  ```\n\n    * `notebookSorts`: Notebook IDs and their sort values, optional\n    * `docSorts`: Document IDs and their sort values, optional\n    * Documents in `docSorts` must belong to opened and unlocked notebooks; notebook root document IDs are not accepted\n    * At least one of `notebookSorts` and `docSorts` must be non-empty. Array order does not affect sorting; each `sort` value is stored directly\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"notebookIDs\": [\"20210817205410-2kvfpfn\"],\n      \"docIDs\": [\"20210917220056-yxtyl7i\"]\n    }\n  }\n  ```\n\n### Set a document's child document sort mode\n\n* `/api/filetree/setDocSortMode`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210917220056-yxtyl7i\",\n    \"sortMode\": 4\n  }\n  ```\n\n    * `id`: ID of the regular document whose child documents use this sort mode; notebook root document IDs are not accepted\n    * `sortMode`: Integer from `0` through `14`; `null` clears the document's explicit setting and inherits the nearest parent document, notebook, or global document tree sort rule, in that order\n    * Values: `0`/`1` file name ascending/descending; `2`/`3` update time ascending/descending; `4`/`5` natural file name ascending/descending; `6` custom; `7`/`8` reference count ascending/descending; `9`/`10` creation time ascending/descending; `11`/`12` size ascending/descending; `13`/`14` child document count ascending/descending\n    * The declared sort mode is inherited by deeper descendants until another document declares its own sort mode\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"box\": \"20210817205410-2kvfpfn\",\n      \"id\": \"20210917220056-yxtyl7i\",\n      \"path\": \"/20210917220056-yxtyl7i.sy\",\n      \"sortMode\": 4,\n      \"effectiveSortMode\": 4\n    }\n  }\n  ```\n\n    * `sortMode` is the explicit setting (`null` when inheriting), while `effectiveSortMode` is the actual sort mode after inheritance is resolved\n\n### Get human-readable path based on path\n\n* `/api/filetree/getHPathByPath`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\",\n    \"path\": \"/20210917220500-sz588nq/20210917220056-yxtyl7i.sy\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n    * `path`: Document path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"/foo/bar\"\n  }\n  ```\n\n### Get human-readable path based on ID\n\n* `/api/filetree/getHPathByID`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210917220056-yxtyl7i\"\n  }\n  ```\n\n    * `id`: Block ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"/foo/bar\"\n  }\n  ```\n\n### Get storage path based on ID\n\n* `/api/filetree/getPathByID`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210808180320-fqgskfj\"\n  }\n  ```\n\n    * `id`: Block ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n    \"notebook\": \"20210808180117-czj9bvb\",\n    \"path\": \"/20200812220555-lj3enxa/20210808180320-fqgskfj.sy\"\n    }\n  }\n  ```\n\n### Get IDs based on human-readable path\n\n* `/api/filetree/getIDsByHPath`\n* Parameters\n\n  ```json\n  {\n    \"path\": \"/foo/bar\",\n    \"notebook\": \"20210808180117-czj9bvb\"\n  }\n  ```\n\n    * `path`: Human-readable path\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n        \"20200813004931-q4cu8na\"\n    ]\n  }\n  ```\n\n## Assets\n\n### Upload assets\n\n* `/api/asset/upload`\n* The parameter is an HTTP Multipart form\n\n    * `assetsDirPath`: The folder path where assets are stored, with the data folder as the root path, for example:\n        * `\"/assets/\"`: workspace/data/assets/ folder\n        * `\"/assets/sub/\"`: workspace/data/assets/sub/ folder\n\n      Under normal circumstances, it is recommended to use the first method, which is stored in the assets folder of the\n      workspace, since putting in a subdirectory has some side effects, please refer to the assets chapter of the user guide.\n    * `file[]`: Uploaded file list\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"disk full\",\n    \"data\": {\n      \"errFiles\": [\"bar.png\"],\n      \"failedFiles\": [\n        {\n          \"index\": 1,\n          \"name\": \"bar.png\",\n          \"error\": \"disk full\"\n        }\n      ],\n      \"succFiles\": [\n        {\n          \"index\": 0,\n          \"name\": \"foo.png\",\n          \"path\": \"assets/foo-20210719092549-9j5y79r.png\"\n        }\n      ],\n      \"succMap\": {\n        \"foo.png\": \"assets/foo-20210719092549-9j5y79r.png\"\n      }\n    }\n  }\n  ```\n\n    * `errFiles`: List of filenames with errors in upload processing\n    * `failedFiles`: Files explicitly reported as failed. `index` is the file's index in `file[]`, `name` is its upload filename, and `error` is the failure message. This field may omit files that were not attempted or not reported individually; use `succFiles` when each input item must be identified unambiguously\n    * `succFiles`: Successfully processed files in input order. `index` is the file's index in `file[]`, `name` is its upload filename, and `path` is the uploaded asset path. Use this field when a batch can contain duplicate filenames\n    * `succMap`: Compatibility mapping for existing callers. The key is the upload filename and the value is assets/foo-id.png. However, when a batch contains duplicate filenames, only the last item with a given key remains in this map\n\n## Blocks\n\n### Insert blocks\n\n* `/api/block/insertBlock`\n* Parameters\n\n  ```json\n  {\n    \"dataType\": \"markdown\",\n    \"data\": \"foo**bar**{: style=\\\"color: var(--b3-font-color8);\\\"}baz\",\n    \"nextID\": \"\",\n    \"previousID\": \"20211229114650-vrek5x6\",\n    \"parentID\": \"\"\n  }\n  ```\n\n    * `dataType`: The data type to be inserted, the value can be `markdown` or `dom`\n    * `data`: Data to be inserted\n    * `nextID`: The ID of the next block, used to anchor the insertion position\n    * `previousID`: The ID of the previous block, used to anchor the insertion position\n    * `parentID`: The ID of the parent block, used to anchor the insertion position\n\n  `nextID`, `previousID`, and `parentID` must have at least one value, using priority: `nextID` > `previousID` >\n  `parentID`\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"insert\",\n            \"data\": \"<div data-node-id=\\\"20211230115020-g02dfx0\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo<strong style=\\\"color: var(--b3-font-color8);\\\">bar</strong>baz</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\"></div></div>\",\n            \"id\": \"20211230115020-g02dfx0\",\n            \"parentID\": \"\",\n            \"previousID\": \"20211229114650-vrek5x6\",\n            \"retData\": null\n          }\n        ],\n        \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n    * `action.data`: DOM generated by the newly inserted block\n    * `action.id`: ID of the first inserted top-level block. When inserting multiple top-level blocks, send one block per request if each ID is needed\n\n### Prepend blocks\n\n* `/api/block/prependBlock`\n* Parameters\n\n  ```json\n  {\n    \"data\": \"foo**bar**{: style=\\\"color: var(--b3-font-color8);\\\"}baz\",\n    \"dataType\": \"markdown\",\n    \"parentID\": \"20220107173950-7f9m1nb\"\n  }\n  ```\n\n    * `dataType`: The data type to be inserted, the value can be `markdown` or `dom`\n    * `data`: Data to be inserted\n    * `parentID`: The ID of the parent block, used to anchor the insertion position\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"insert\",\n            \"data\": \"<div data-node-id=\\\"20220108003710-hm0x9sc\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo<strong style=\\\"color: var(--b3-font-color8);\\\">bar</strong>baz</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\"></div></div>\",\n            \"id\": \"20220108003710-hm0x9sc\",\n            \"parentID\": \"20220107173950-7f9m1nb\",\n            \"previousID\": \"\",\n            \"retData\": null\n          }\n        ],\n        \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n    * `action.data`: DOM generated by the newly inserted block\n    * `action.id`: ID of the newly inserted block\n\n### Append blocks\n\n* `/api/block/appendBlock`\n* Parameters\n\n  ```json\n  {\n    \"data\": \"foo**bar**{: style=\\\"color: var(--b3-font-color8);\\\"}baz\",\n    \"dataType\": \"markdown\",\n    \"parentID\": \"20220107173950-7f9m1nb\"\n  }\n  ```\n\n    * `dataType`: The data type to be inserted, the value can be `markdown` or `dom`\n    * `data`: Data to be inserted\n    * `parentID`: The ID of the parent block, used to anchor the insertion position\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"insert\",\n            \"data\": \"<div data-node-id=\\\"20220108003642-y2wmpcv\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo<strong style=\\\"color: var(--b3-font-color8);\\\">bar</strong>baz</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\"></div></div>\",\n            \"id\": \"20220108003642-y2wmpcv\",\n            \"parentID\": \"20220107173950-7f9m1nb\",\n            \"previousID\": \"20220108003615-7rk41t1\",\n            \"retData\": null\n          }\n        ],\n        \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n    * `action.data`: DOM generated by the newly inserted block\n    * `action.id`: ID of the first appended top-level block. When appending multiple top-level blocks, send one block per request if each ID is needed\n\n### Update a block\n\n* `/api/block/updateBlock`\n* Parameters\n\n  ```json\n  {\n    \"dataType\": \"markdown\",\n    \"data\": \"foobarbaz\",\n    \"id\": \"20211230161520-querkps\",\n    \"lockType\": false\n  }\n  ```\n\n    * `dataType`: The data type to be updated, the value can be `markdown` or `dom`\n    * `data`: Data to be updated\n    * `id`: ID of the block to be updated\n    * `lockType`: Whether to reject the update when the parsed block type differs from the existing block type; invalid parent-child structures are always rejected, while an empty paragraph can be converted to any valid block type; defaults to `false`\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"update\",\n            \"data\": \"<div data-node-id=\\\"20211230161520-querkps\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo<strong>bar</strong>baz</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\"></div></div>\",\n            \"id\": \"20211230161520-querkps\",\n            \"parentID\": \"\",\n            \"previousID\": \"\",\n            \"retData\": null\n            }\n          ],\n        \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n    * `action.data`: DOM generated by the updated block\n\n### Delete a block\n\n* `/api/block/deleteBlock`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20211230161520-querkps\"\n  }\n  ```\n\n    * `id`: ID of the block to be deleted\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"delete\",\n            \"data\": null,\n            \"id\": \"20211230162439-vtm09qo\",\n            \"parentID\": \"\",\n            \"previousID\": \"\",\n            \"retData\": null\n          }\n        ],\n       \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n### Move a block\n\n* `/api/block/moveBlock`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20230406180530-3o1rqkc\",\n    \"previousID\": \"20230406152734-if5kyx6\",\n    \"parentID\": \"20230404183855-woe52ko\"\n  }\n  ```\n\n    * `id`: Block ID to move\n    * `previousID`: The ID of the previous block, used to anchor the insertion position\n    * `parentID`: The ID of the parent block, used to anchor the insertion position, `previousID` and `parentID` cannot\n      be empty at the same time, if they exist at the same time, `previousID` will be used first\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n        {\n            \"doOperations\": [\n                {\n                    \"action\": \"move\",\n                    \"data\": null,\n                    \"id\": \"20230406180530-3o1rqkc\",\n                    \"parentID\": \"20230404183855-woe52ko\",\n                    \"previousID\": \"20230406152734-if5kyx6\",\n                    \"nextID\": \"\",\n                    \"retData\": null,\n                    \"srcIDs\": null,\n                    \"name\": \"\",\n                    \"type\": \"\"\n                }\n            ],\n            \"undoOperations\": null\n        }\n    ]\n  }\n  ```\n\n### Fold a block\n\n* `/api/block/foldBlock`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20231224160424-2f5680o\"\n  }\n  ```\n\n    * `id`: Block ID to fold\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Unfold a block\n\n* `/api/block/unfoldBlock`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20231224160424-2f5680o\"\n  }\n  ```\n\n    * `id`: Block ID to unfold\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Get a block kramdown\n\n* `/api/block/getBlockKramdown`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20201225220954-dlgzk1o\"\n  }\n  ```\n\n    * `id`: ID of the block to be got\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"id\": \"20201225220954-dlgzk1o\",\n      \"kramdown\": \"* {: id=\\\"20201225220954-e913snx\\\"}Create a new notebook, create a new document under the notebook\\n  {: id=\\\"20210131161940-kfs31q6\\\"}\\n* {: id=\\\"20201225220954-ygz217h\\\"}Enter <kbd>/</kbd> in the editor to trigger the function menu\\n  {: id=\\\"20210131161940-eo0riwq\\\"}\\n* {: id=\\\"20201225220954-875yybt\\\"}((20200924101200-gss5vee \\\"Navigate in the content block\\\")) and ((20200924100906-0u4zfq3 \\\"Window and tab\\\"))\\n  {: id=\\\"20210131161940-b5uow2h\\\"}\"\n    }\n  }\n  ```\n\n* Determinism: The returned Kramdown canonicalizes block-level IAL attribute ordering; the order remains stable while the block content and attributes are unchanged\n\n### Get child blocks\n\n* `/api/block/getChildBlocks`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20230506212712-vt9ajwj\"\n  }\n  ```\n\n    * `id`: Parent block ID\n    * The blocks below a heading are also counted as child blocks\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"id\": \"20230512083858-mjdwkbn\",\n        \"type\": \"h\",\n        \"subType\": \"h1\"\n      },\n      {\n        \"id\": \"20230513213727-thswvfd\",\n        \"type\": \"s\"\n      },\n      {\n        \"id\": \"20230513213633-9lsj4ew\",\n        \"type\": \"l\",\n        \"subType\": \"u\"\n      }\n    ]\n  }\n  ```\n\n### Transfer block ref\n\n* `/api/block/transferBlockRef`\n* Parameters\n\n  ```json\n  {\n    \"fromID\": \"20230612160235-mv6rrh1\",\n    \"toID\": \"20230613093045-uwcomng\",\n    \"refIDs\": [\"20230613092230-cpyimmd\"]\n  }\n  ```\n\n    * `fromID`: Def block ID\n    * `toID`: Target block ID\n    * `refIDs`: Ref block IDs point to def block ID, optional, if not specified, all ref block IDs will be transferred\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n## Attributes\n\n### Set block attributes\n\n* `/api/attr/setBlockAttrs`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210912214605-uhi5gco\",\n    \"attrs\": {\n      \"custom-attr1\": \"line1\\nline2\"\n    }\n  }\n  ```\n\n    * `id`: Block ID\n    * `attrs`: Block attributes, custom attributes must be prefixed with `custom-`\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Get block attributes\n\n* `/api/attr/getBlockAttrs`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210912214605-uhi5gco\"\n  }\n  ```\n\n    * `id`: Block ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"custom-attr1\": \"line1\\nline2\",\n      \"id\": \"20210912214605-uhi5gco\",\n      \"title\": \"PDF Annotation Demo\",\n      \"type\": \"doc\",\n      \"updated\": \"20210916120715\"\n    }\n  }\n  ```\n\n## SQL\n\n### Execute SQL query\n\n* `/api/query/sql`\n* Parameters\n\n  ```json\n  {\n    \"stmt\": \"SELECT * FROM blocks WHERE content LIKE'%content%' LIMIT 7\"\n  }\n  ```\n\n    * `stmt`: SQL statement\n\nWithout an explicit outer `LIMIT`, results default to at most `search.limit` rows (the configured search result limit). Use explicit `LIMIT` and `OFFSET` clauses to paginate, with a stable, unique ordering such as `ORDER BY hpath, id`. However, an explicit outer `LIMIT` overrides the default, including values larger than `search.limit`.\n\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      { \"col\": \"val\" }\n    ],\n    \"limit\": 0,\n    \"truncated\": false\n  }\n  ```\n\nOn success, `data` remains an array. `limit` is the server default limit applied to this query, or `0` when the SQL supplies an explicit outer `LIMIT`; it is not the value of that explicit clause. `truncated` is `true` only when the server default limit omitted at least one result row. Exactly meeting the limit does not imply truncation. For the example above, `LIMIT 7` is explicit, so `limit` is `0` and `truncated` is `false`. These fields are omitted on errors.\n\nNote: To ensure data security, access to this interface is prohibited in Publish Mode.\n\n### Flush transaction\n\n* `/api/sqlite/flushTransaction`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nBlock-writing APIs can return after their block-tree transaction is committed but before asynchronous SQL indexing has caught up. If a subsequent `/api/query/sql` call must observe that write, call `POST /api/sqlite/flushTransaction` after the write and wait for `code: 0` before querying SQL. For example, `getBlockKramdown` reads the block tree and can show newly written content before the SQL index does. Kramdown, DOM, and SQL columns are different representations and are not normalized plain text.\n\n## Templates\n\n### Render a template\n\n* `/api/template/render`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20220724223548-j6g0o87\",\n    \"path\": \"F:\\\\SiYuan\\\\data\\\\templates\\\\foo.md\",\n    \"mode\": \"editorInsert\"\n  }\n  ```\n\n    * `id`: The ID of the document where the rendering is called\n    * `path`: Template file absolute path\n    * `mode`: Optional rendering mode. Currently supports `\"preview\"` and `\"editorInsert\"` only. Preview mode produces a document tree plan without writing files; editor-insert mode produces a plan that can be confirmed and applied with the corresponding editor transaction\n    * When `mode` is omitted, the legacy `preview` Boolean parameter remains supported: `preview: true` is equivalent to `mode: \"preview\"`; otherwise the template is rendered as ordinary content and `createDocTree` is disabled\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"content\": \"<div data-node-id=\\\"20220729234848-dlgsah7\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\" updated=\\\"20220729234840\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\">​</div></div>\",\n      \"path\": \"F:\\\\SiYuan\\\\data\\\\templates\\\\foo.md\",\n      \"docTreePlan\": {\n        \"id\": \"template-plan-token\",\n        \"count\": 2,\n        \"nodes\": [\n          {\n            \"id\": \"20260830150000-abc1234\",\n            \"title\": \"Materials\",\n            \"parentID\": \"20220724223548-j6g0o87\",\n            \"hPath\": \"/Parent/Materials\",\n            \"depth\": 1\n          },\n          {\n            \"id\": \"20260830150001-def5678\",\n            \"title\": \"Review\",\n            \"parentID\": \"20260830150000-abc1234\",\n            \"hPath\": \"/Parent/Materials/Review\",\n            \"depth\": 2\n          }\n        ]\n      }\n    }\n  }\n  ```\n\n    * `docTreePlan`: Present when the template declares a child document tree with `createDocTree`\n        * `id`: Empty in preview mode, which never writes files. In editor-insert mode, this is a short-lived, one-time plan token; after confirmation, submit it as the top-level `templateDocTreePlanID` field of the corresponding transaction object\n        * `count`: Total number of child documents in the plan\n        * `nodes`: Static descriptions of the planned documents\n            * `id`: Planned document ID\n            * `title`: Planned document title\n            * `parentID`: Planned parent document ID\n            * `hPath`: Planned human-readable document path\n            * `depth`: Depth relative to the document where the template is inserted\n        * A single plan can contain at most 128 documents, and its declared child document tree can be at most 16 levels deep. The resulting absolute file tree depth remains subject to the setting that controls whether sub-documents deeper than 7 levels may be created\n\n### Save a document as a template\n\n* `/api/template/docSaveAsTemplate`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20220724223548-j6g0o87\",\n    \"name\": \"Project\",\n    \"overwrite\": false,\n    \"databaseMode\": \"copy\"\n  }\n  ```\n\n    * `id`: Source document ID\n    * `name`: Template name. The kernel sanitizes the name and adds the `.md` extension\n    * `overwrite`: Whether to replace an existing template with the same name. When `false` and the template exists, the response `code` is `1`\n    * `databaseMode`: Optional handling for all database blocks in the document. `copy` (the default) creates independent databases whenever the template is used and clears their block-level context filters; `reference` keeps the existing database IDs and context filters, so rendered blocks are mirrors that share data and view settings with the source databases. Rendering a reference-mode template fails if a source database is unavailable in the target document's encryption boundary\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Render Sprig\n\n* `/api/template/renderSprig`\n* Parameters\n\n  ```json\n  {\n    \"template\": \"/daily note/{{now | date \\\"2006/01\\\"}}/{{now | date \\\"2006-01-02\\\"}}\"\n  }\n  ```\n    * `template`: template content\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"/daily note/2023/03/2023-03-24\"\n  }\n  ```\n\n## File\n\n### Get file\n\n* `/api/file/getFile`\n* Parameters\n\n  ``json {\n  \"path\": \"/data/20210808180117-6v0mkxr/20200923234011-ieuun1p.sy\"\n  }\n  ``\n    * `path`: the file path under the workspace path\n* Return value\n\n    * Response status code `200`: File content\n    * Response status code `202`: Exception information\n\n      ```json\n      {\n        \"code\": 404,\n        \"msg\": \"\",\n        \"data\": null\n      }\n      ```\n\n        * `code`: non-zero for exceptions\n\n            * `-1`: Parameter parsing error\n            * `403`: Permission denied (file is not in the workspace)\n            * `404`: Not Found (file doesn't exist)\n            * `405`: Method Not Allowed (it's a directory)\n            * `500`: Server Error (stat file failed / read file failed)\n        * `msg`: a piece of text describing the error\n\n### Put file\n\n* `/api/file/putFile`\n* The parameter is an HTTP Multipart form\n\n    * `path`: the file path under the workspace path\n    * `isDir`: whether to create a folder, when `true` only create a folder, ignore `file`\n    * `modTime`: last access and modification time, Unix time\n    * `file`: the uploaded file\n* Return value\n\n   ```json\n   {\n     \"code\": 0,\n     \"msg\": \"\",\n     \"data\": null\n   }\n   ```\n\n### Remove file\n\n* `/api/file/removeFile`\n* Parameters\n\n  ```json\n  {\n    \"path\": \"/data/20210808180117-6v0mkxr/20200923234011-ieuun1p.sy\"\n  }\n  ```\n    * `path`: the file path under the workspace path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Rename file\n\n* `/api/file/renameFile`\n* Parameters\n\n  ```json\n  {\n    \"path\": \"/data/assets/image-20230523085812-k3o9t32.png\",\n    \"newPath\": \"/data/assets/test-20230523085812-k3o9t32.png\"\n  }\n  ```\n    * `path`: the file path under the workspace path\n    * `newPath`: the new file path under the workspace path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### List files\n\n* `/api/file/readDir`\n* Parameters\n\n  ```json\n  {\n    \"path\": \"/data/20210808180117-6v0mkxr/20200923234011-ieuun1p\"\n  }\n  ```\n    * `path`: the dir path under the workspace path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"isDir\": true,\n        \"isSymlink\": false,\n        \"name\": \"20210808180303-6yi0dv5\",\n        \"updated\": 1691467624\n      },\n      {\n        \"isDir\": false,\n        \"isSymlink\": false,\n        \"name\": \"20210808180303-6yi0dv5.sy\",\n        \"updated\": 1663298365\n      }\n    ]\n  }\n  ```\n\n## Export\n\n### Export Markdown\n\n* `/api/export/exportMdContent`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"\"\n  }\n  ```\n\n    * `id`: ID of the doc block to export\n    * `preserveTaskMarkers`: Optional boolean for Markdown copying; `true` retains task markers such as `[/]` and `[-]`. Omitted, `null`, or `false` keeps standard Markdown normalization to `[X]` for nonstandard markers\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"hPath\": \"/Please Start Here\",\n      \"content\": \"## 🍫 Content Block\\n\\nIn SiYuan, the only important core concept is...\"\n    }\n  }\n  ```\n\n    * `hPath`: human-readable path\n    * `content`: Markdown content, including automatic heading numbers when enabled for the document (the document setting overrides the editor default); uses the editor numbering format and preserves source-document numbers for partial exports\n\n### Export files and folders\n\n* `/api/export/exportResources`\n* Parameters\n\n  ```json\n  {\n    \"paths\": [\n      \"/conf/appearance/boot\",\n      \"/conf/appearance/langs\",\n      \"/conf/appearance/emojis/conf.json\",\n      \"/conf/appearance/icons/index.html\"\n    ],\n    \"name\": \"zip-file-name\"\n  }\n  ```\n\n    * `paths`: A list of file or folder paths to be exported, the same filename/folder name will be overwritten\n    * `name`: (Optional) The exported file name, which defaults to `export-YYYY-MM-DD_hh-mm-ss.zip` when not set\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"path\": \"temp/export/zip-file-name.zip\"\n    }\n  }\n  ```\n\n    * `path`: The path of `*.zip` file created\n        * The directory structure in `zip-file-name.zip` is as follows:\n            * `zip-file-name`\n                * `boot`\n                * `langs`\n                * `conf.json`\n                * `index.html`\n\n## Conversion\n\n### Pandoc\n\n* `/api/convert/pandoc`\n* Working directory\n    * Executing the pandoc command will set the working directory to `workspace/temp/convert/pandoc/${dir}`\n    * API [`Put file`](#put-file) can be used to write the file to be converted to this directory first\n    * Then call the API for conversion, and the converted file will also be written to this directory\n    * Finally, call the API [`Get file`](#get-file) to get the converted file\n        * Or call the API [Create a document with Markdown](#Create-a-document-with-Markdown)\n        * Or call the internal API `importStdMd` to import the converted folder directly\n* Parameters\n\n  ```json\n  {\n    \"dir\": \"test\",\n    \"args\": [\n      \"--to\", \"markdown_strict-raw_html\",\n      \"foo.epub\",\n      \"-o\", \"foo.md\"\n   ]\n  }\n  ```\n\n    * `args`: Pandoc command line parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n       \"path\": \"/temp/convert/pandoc/test\"\n    }\n  }\n  ```\n    * `path`: the path under the workspace\n\n## Notification\n\n### Push message\n\n* `/api/notification/pushMsg`\n* Parameters\n\n  ```json\n  {\n    \"msg\": \"test\",\n    \"timeout\": 7000\n  }\n  ```\n    * `timeout`: The duration of the message display in milliseconds. This field can be omitted, the default is 7000\n      milliseconds\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n        \"id\": \"62jtmqi\"\n    }\n  }\n  ```\n    * `id`: Message ID\n\n### Push error message\n\n* `/api/notification/pushErrMsg`\n* Parameters\n\n  ```json\n  {\n    \"msg\": \"test\",\n    \"timeout\": 7000\n  }\n  ```\n    * `timeout`: The duration of the message display in milliseconds. This field can be omitted, the default is 7000\n      milliseconds\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n        \"id\": \"qc9znut\"\n    }\n  }\n  ```\n    * `id`: Message ID\n\n## Network\n\n### Forward proxy\n\n#### JSON forward proxy\n\n* `/api/network/forwardProxy`\n* Parameters\n\n  ```json\n  {\n    \"url\": \"https://b3log.org/siyuan/\",\n    \"method\": \"GET\",\n    \"timeout\": 7000,\n    \"contentType\": \"text/html\",\n    \"headers\": [\n        {\n            \"Cookie\": \"\"\n        }\n    ],\n    \"redirect\": true,\n    \"payload\": {},\n    \"payloadEncoding\": \"json\",\n    \"responseEncoding\": \"text\"\n  }\n  ```\n\n    * `url`: URL to forward\n    * `method`: HTTP method, default is `POST`\n    * `timeout`: timeout in milliseconds, default is `7000`\n    * `contentType`: Content-Type, default is `application/json`\n    * `headers`: HTTP request header array; each key-value pair in the objects is set as a request header\n    * `redirect`: Whether to follow redirects, default is `true`, following up to 2 redirects; set it to `false` to disable redirects\n    * `payload`: HTTP payload, object or string\n    * `payloadEncoding`: The encoding scheme used by `payload`, default is `json`; `json` sends `payload` directly, and binary payloads can use the following encoded strings\n\n        * `json`\n        * `base64` | `base64-std`\n        * `base64-url`\n        * `base32` | `base32-std`\n        * `base32-hex`\n        * `hex`\n    * `responseEncoding`: The encoding scheme used by `body` in response data, default is `text`, optional values are as follows\n\n        * `text`\n        * `base64` | `base64-std`\n        * `base64-url`\n        * `base32` | `base32-std`\n        * `base32-hex`\n        * `hex`\n\n      `text` preserves the existing behavior and converts the character set to UTF-8 when applicable. The binary encodings encode the response body before character-set conversion; existing HTTP content decoding behavior, such as gzip decompression, is unchanged.\n\n      The response body is limited to 32 MiB after HTTP content decoding. If the limit is exceeded, the API returns error code `10` without a partial body. Therefore, use `/api/network/proxy` for large files or streaming responses.\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"body\": \"\",\n      \"bodyEncoding\": \"text\",\n      \"contentType\": \"text/html\",\n      \"elapsed\": 1976,\n      \"headers\": {\n      },\n      \"status\": 200,\n      \"url\": \"https://b3log.org/siyuan/\"\n    }\n  }\n  ```\n\n    * `body`: Response body\n    * `bodyEncoding`: The encoding scheme used by `body`; it is consistent with the `responseEncoding` field in the request, default is `text`, optional values are as follows\n\n        * `text`\n        * `base64` | `base64-std`\n        * `base64-url`\n        * `base32` | `base32-std`\n        * `base32-hex`\n        * `hex`\n    * `contentType`: Response header `Content-Type`\n    * `elapsed`: Request duration in milliseconds\n    * `headers`: Response headers returned by the target service\n    * `status`: HTTP status code returned by the target service\n    * `url`: Forwarded URL\n\n#### HTTP forward proxy\n\n* `/api/network/proxy`\n* Request method: any HTTP method\n* Query parameters\n\n    * `u`: Required, target `http` or `https` URL encoded with Go `base64.RawURLEncoding`, which is URL-safe Base64 without `=` padding\n    * `h`: Optional, request header JSON encoded in the same way; the JSON type is `map[string][]string`, for example `{\"Authorization\":[\"Bearer token\"]}`\n    * `t`: Optional, connection timeout in Go `time.ParseDuration` format, for example `30s` or `1500ms`\n* Request body: Forwards the current request body as-is, and forwards the current request's full `Content-Type` header to the target request\n* Return value: Directly returns the target service HTTP status code and response body without wrapping them in `code`, `msg`, or `data`; target response headers are returned with the `Siyuan-Proxy-` prefix, for example `Content-Type` is returned as `Siyuan-Proxy-Content-Type`\n\n#### WebSocket forward proxy\n\n* `/ws/network/proxy`\n* Request method: `GET`\n* Query parameters\n\n    * `u`: Required, target `ws` or `wss` URL encoded with Go `base64.RawURLEncoding`\n    * `h`: Optional, handshake request header JSON encoded in the same way; the JSON type is `map[string][]string`\n    * `t`: Optional, handshake timeout in Go `time.ParseDuration` format, for example `30s` or `1500ms`\n* Return value: Upgrades to WebSocket and then forwards messages bidirectionally; target handshake response headers are returned with the `Siyuan-Proxy-` prefix\n\n#### EventSource forward proxy\n\n* `/es/network/proxy`\n* Request method: `GET`\n* Query parameters\n\n    * `u`: Required, target `http` or `https` URL encoded with Go `base64.RawURLEncoding`\n    * `h`: Optional, request header JSON encoded in the same way; the JSON type is `map[string][]string`\n    * `t`: Optional, connection timeout in Go `time.ParseDuration` format, for example `30s` or `1500ms`\n* Return value: Directly streams the target service HTTP status code and response body without wrapping them in `code`, `msg`, or `data`; if the request headers do not include `Accept`, `text/event-stream` is used automatically; target response headers are returned with the `Siyuan-Proxy-` prefix\n\n## System\n\n### Get boot progress\n\n* `/api/system/bootProgress`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"details\": \"Finishing boot...\",\n      \"progress\": 100\n    }\n  }\n  ```\n\n### Get system version\n\n* `/api/system/version`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"1.3.5\"\n  }\n  ```\n\n### Get the current time of the system\n\n* `/api/system/currentTime`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": 1631850968131\n  }\n  ```\n\n    * `data`: Precision in milliseconds\n\n## Database\n\nA database (internally an \"attribute view\") stores structured data as fields (columns) and items (rows). Each database is identified by an `avID` and can be embedded into a document through one or more database blocks (`blockID`). A single database may contain multiple views (`viewID`) of different layout types: `table`, `list`, `gallery`, and `kanban`","isInternal":false,"tokens":13947,"sizeBytes":50020},{"name":"development.md","path":"kernel/util/builtin_skills/siyuan-plugin-development/references/development.md","rawUrl":"https://raw.githubusercontent.com/siyuan-note/siyuan/HEAD/kernel/util/builtin_skills/siyuan-plugin-development/references/development.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Development References and Boundaries\n\n## Official References\n\n- Plugin sample: https://github.com/siyuan-note/plugin-sample\n- Plugin API declarations: https://github.com/siyuan-note/petal\n- Implementation of the target SiYuan version: https://github.com/siyuan-note/siyuan\n\nVerify the currently running version and target frontend, then use the public APIs for that version. The host loads the frontend plugin entry point index.js; CommonJS uses require(\"siyuan\") and module.exports. Do not assume the host exports an API simply because its type exists in an npm package. Take target frontend values from the current manifest specification instead of blindly reusing an all-platform array.\n\n## Minimum Files and Builds\n\nA minimal frontend package requires plugin.json and a real index.js, together with any i18n, README, index.css, and other resources actually used. The manifest's name, version, minimum host version, and frontend declarations must comply with the current native installer's parsing rules. Publishing metadata requires a real identity; do not invent an author or repository for a local prototype.\n\nexamples/index.js and examples/en_US.json are original, small, dependency-free examples that demonstrate only public entry points and lifecycle handling. They are not a completed plugin for the user; do not treat example text as user requirements. Verify addTopBar, showMessage, and language-file keys against the target version. Register layout-related entry points in onLayoutReady and avoid duplicate registration.\n\nTypeScript produces an installable index.js only through an actually available build toolchain; changing a .ts extension is not a build. package_local only freezes and packages files; it does not build or execute arbitrary project scripts. If build capabilities are unavailable, accurately deliver the source code and blocker details, or switch to plain JavaScript with the user's agreement.\n\n## Imports and Reconfirmation\n\nBefore importing, inspect the candidate source with project_status(taskId, sourcePath, sourceFiles). sourceFiles is an exact allowlist of relative file paths and must match the plan's workflow.files; sourceRevision binds the selected existing safe files, not the entire repository. The host only lists excluded paths without reading contents such as .git, node_modules, or config. An optional path on the allowlist that does not yet exist may be created later; do not treat it as an imported file.\n\nAfter reconfirming a plan for an existing managed project, obtain the current sourceRevision through project_status(taskId), then establish a new checkpoint with prepare_project(taskId, expectedSourceRevision). Do not overwrite the current source code or reimport the previous source. If revisions do not match, first inspect the changes read-only, then handle their actual effects.\n\nPackaging freezes only existing safe files on the confirmed allowlist. Missing optional files may be omitted from the package, but plugin.json, index.js, and resources declared by the manifest must be complete. Do not use optional allowlist entries to bypass entry-point or resource validation.\n\n## Data and Lifecycle\n\nRead and modify notes through public kernel APIs, and check code before reporting success. Do not directly modify .sy files, databases, or the actual data directory. Define the target and scope of effects clearly, access encrypted notebooks under existing permissions, and do not bypass locks or denials.\n\nPrevent duplicate clicks during long-running writes, and retain request and instance state. In onunload, clean up the plugin's own events, timers, and observers, and cancel reads that can be canceled; after an asynchronous response, check that the instance is still valid. Canceling a request or unloading does not guarantee that a server-side write has not already executed. If the result is unknown, verify it read-only first.\n\n## Installation\n\nGenerating a local ZIP does not grant permission to install or activate it. Install through the native local-package interface, bound to the package hash and expected installation-target revision; keep the immutable installation snapshot separate from development source code. Explain the effects of overwriting an enabled plugin before obtaining authorization for an action that may execute code. If the user approves only file delivery, stop at source code and artifacts.\n\nRecover installation data through the existing repo snapshot and recovery tools, respecting the user's sync-ignore rules. Snapshots cover non-ignored code under data/plugins and data under data/storage/petal; they do not include the workspace conf directory or managed-project source code. Managed source code is still restored through restore_project. The agent creates a snapshot before the first local write in each conversation round, rather than creating a separate recovery point for every installation; the installer does not keep a separate plugin-code backup either. A full-repository checkout rolls back all data included in the snapshot, disables sync, and reloads the interface. Single-file recovery overwrites only the selected files and does not delete files added later. Restoring petals.json affects the entire plugin configuration. Verify snapshot contents and recovery scope first, follow the existing tool confirmation requirements, do not automatically check out the entire repository, and do not promise to stop plugin code that is already running.\n","isInternal":false,"tokens":1019,"sizeBytes":5462},{"name":"verification.md","path":"kernel/util/builtin_skills/siyuan-plugin-development/references/verification.md","rawUrl":"https://raw.githubusercontent.com/siyuan-note/siyuan/HEAD/kernel/util/builtin_skills/siyuan-plugin-development/references/verification.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Verification Record\n\nReport only actual evidence for each item: passed, failed, unverified, or not applicable. Completed source code and ZIP files do not replace runtime evidence.\n\n- Source code: implementation matches the confirmed plan, frontend and resource declarations are accurate, and there are no unapproved data effects\n- Static checks: syntax, type, or lint checks actually run and their results; mark as unverified if no parser is available\n- Build: the actual TypeScript command, exit status, and artifacts; not applicable for plain JavaScript\n- ZIP: package path, hash, source revision, file list, and validation results returned by package_local\n- Simulated tests: identify SDK substitutes, DOM environments, or standalone Electron environments; do not describe them as an actual SiYuan instance\n- Installation: authorized target, package hash, revisions before and after installation, and actual native installation results\n- Activation: actual configuration state; successful installation does not imply activation\n- Frontend loading: check entry points and errors separately in desktop Electron and the desktop browser; neither substitutes for the other\n- Functional execution: main operation, error feedback, repeated clicks, unloading after partial initialization, and late asynchronous responses\n- Data writes: target, count, and actual returned status; verify unknown results read-only first to avoid duplicate submissions\n\nIf an actual SiYuan instance, target frontend, or necessary permission is unavailable, explicitly leave the corresponding stages unverified. Do not compensate for a missing test environment by installing into the user's actual environment. Rolling back code does not roll back user data.\n\nRecovery is not guaranteed to handle every process crash automatically. If a temporary file is only partially written, or a conditional create has published the target but has not yet cleaned up the temporary hard link, the tool preserves both names, their bytes, and the logs, and returns result_unknown; manual inspection may be necessary. Do not delete files whose state is uncertain, weaken hard-link checks, or blindly retry to claim that recovery is complete.\n","isInternal":false,"tokens":407,"sizeBytes":2202},{"name":"SKILL.md","path":"kernel/util/builtin_skills/siyuan-plugin-development/SKILL.md","rawUrl":"https://raw.githubusercontent.com/siyuan-note/siyuan/HEAD/kernel/util/builtin_skills/siyuan-plugin-development/SKILL.md","title":"siyuan-plugin-development","category":"anthropic-skill","format":"markdown","content":"---\nname: siyuan-plugin-development\ndescription: Official frontend plugin development workflow. After the user explicitly opts in, clarify missing requirements, confirm a short plan, implement in a managed copy, verify, and package. Installation and activation require separate authorization.\n---\n\n# SiYuan Frontend Plugin Development\n\nThis is a read-only official skill shipped with SiYuan. Its current scope is creating and modifying frontend plugins, using CommonJS JavaScript without third-party runtime dependencies by default. Full development, verification, and recovery of kernel.js are outside this workflow's scope.\n\nReplace example placeholders such as ${PLUGIN_NAME} according to the confirmed plan. Do not treat user-defined variable settings as official content.\n\n## Obtain an Explicit Choice, Then Confirm the Plan\n\n- Use this skill only when the user explicitly asks to develop or modify a plugin. Do not automatically turn ordinary template, document, or theme requests into plugins\n- Initiate the official choice with workflow.action=\"choose\" in the question tool; questions may be an empty array. The server displays a fixed, localized yes/no question. Do not treat ordinary questions, chat text, the model's own consent, or the user's previous answers as permission for this task\n- After the user chooses yes, load the body through skill with action=\"load\", source=\"builtin\", and name=\"siyuan-plugin-development\". Continue only after the server confirms successful loading. Do not read the official body if the user declines or cancels, or if the skill is disabled; user-requested custom development may still proceed under its original permissions\n- Extract known requirements from the current conversation, and ask only about gaps that would change the implementation: functionality, entry points, triggers, target frontend, data-read and data-write effects, and deliverables. Do not repeat questions already answered, guess a notebook, or expand the scope to every platform\n- Submit a short plan through question with workflow.action=\"plan\", bound to the taskId returned by the choice. The full plan must clearly state the plugin name, frontend, functionality and entry points, data changes, delivery scope, import source, and files allowed in the package. The server freezes and displays the entire plan; do not write source code before actual acceptance is confirmed\n- Update the plan and obtain new confirmation when changing a confirmed goal, source-code origin, data effects, or packaging scope. Do not fabricate a taskId, plan digest, or confirmed state; previous questions and versions do not authorize a new task\n\nExample choice call: {\"questions\":[],\"workflow\":{\"action\":\"choose\"}}. Use newTask=true only when the user explicitly requests another plugin; reconsider=true may be used when the user explicitly changes the current workflow choice. Ordinary repeated calls reuse the current choice without asking again.\n\nPlan fields: taskId, proposal (at most 2000 characters), packageName, frontend (one of desktop, desktop-window, browser-desktop, mobile, or browser-mobile), dataEffects and deliverables (at most 1000 characters each), and files (at most 200 exact relative file paths). Before importing an existing plugin, call bazaar's project_status with taskId, sourcePath, and exact sourceFiles; sourceFiles must match the workflow.files awaiting confirmation. The host returns a sourceRevision for the selected existing safe files; include that digest and sourcePath in the plan. Do not implicitly read or hash the entire repository. List excluded paths such as .git, node_modules, and config without reading their contents. Omit source fields when creating a new project. The technical file list is also shown to the user; do not add unapproved files after confirmation. The allowlist may contain optional files that do not yet exist, but this does not permit omitting the entry point, manifest, or declared resources.\n\n## Verify Tools and APIs First\n\nRead references/development.md first. Read every resource through skill with source=\"builtin\" and name=\"siyuan-plugin-development/relative-path\". Check the tools currently available and the target SiYuan version; consult the official plugin-sample, petal, and source code for that version as needed. Do not treat type declarations, outdated README versions, or unreleased APIs as evidence of support in the current host.\n\nUse CommonJS JavaScript by default. Use TypeScript only when the required dependencies and build tools actually exist and are authorized for use; source code is not equivalent to a runnable index.js. Native agents are not guaranteed to have a shell, dependency-installation tools, or arbitrary code execution. If a tool is missing, report the exact blocked step. Do not invent execution results or bypass a tool denial. If the user specifies TypeScript, confirm an alternative approach before switching to JavaScript.\n\n## Implement in a Managed Copy\n\n1. After plan confirmation, call bazaar's prepare_project with taskId so the host creates a source-code copy and baseline checkpoint; do not specify an absolute write directory yourself. For imports, first perform a read-only inspection of the exact sourceFiles, then import a copy after approval of the source and the selected files' baseline digest; leave the original directory unchanged. After reconfirming a plan for an existing managed project, obtain the current source revision with project_status(taskId), then call prepare_project(taskId, expectedSourceRevision) to establish a new checkpoint. This preserves the current source code without reimporting or overwriting it\n2. Use file.write with ifAbsent=true for new files. Before editing existing text, use file.read with withMetadata=true; continue reading with the same expectedRevision and nextOffsetByte until you have fully read the original content that needs changing. truncated=true does not represent a complete file\n3. Prefer file.edit for partial changes, providing expectedRevision and unique, nonoverlapping oldText/newText replacements. Use file.write with expectedRevision only when full replacement is necessary, and only after reading the entire file at that same revision. On a conflict, reread and verify first. Do not blindly retry writes or fall back to overwriting the entire file when a match is missing or ambiguous\n4. Use the paths and project revisions returned by the host; verify the current source code, plan, and checkpoint through bazaar's project_status. Stop writing if a checkpoint cannot be established, and report partial failures accurately. When recovery is needed, use restore_project and respect the current revision and tool confirmation requirements\n5. Do not bypass conditional writes for managed source code through copy, unzip, directory renaming, or HTTP file APIs. Do not touch host-controlled, backup, or artifact directories, or directly overwrite the actual plugin installation directory\n\nCanceling the workflow does not automatically roll back changes. When cancellation, an unknown write result, or partial preparation prevents the ordinary plan from continuing, request recovery-only authorization through question with questions=[], workflow.action=\"recover\", and taskId. The server displays and binds the most recently actually approved plan and its digest.\nAfter confirmation, you may call restore_project, or retry prepare_project only for the same frozen, interrupted preparation. Recovery does not roll back notes, runtime data, or other configuration, and does not authorize further source-code changes; subsequent ordinary source changes still require normal plan confirmation.\n\nUse the minimal entry-point pattern in examples/index.js as a reference and adapt it to the actual functionality. Create entry points through the public Plugin API and modify notes through kernel APIs; never use fs or Electron to modify data files directly. Put UI text in i18n. Handle return codes, missing targets, closed notebooks, repeated clicks, and late responses. onunload must handle partial initialization and clean up the plugin's own listeners, timers, observers, and requests; unloading cannot undo write requests that have already been sent.\n\n## Verify, Package, and Deliver\n\nRead references/verification.md first. Declare only frontends that are actually implemented and verified; do not declare kernels without kernel.js. Do not copy the example's author, repository, funding information, or data permissions, or invent a publishing identity for a local prototype.\n\nRequest a deterministic local ZIP through bazaar's package_local with taskId and the latest expectedSourceRevision. The host freezes only existing safe files on the allowlist. Optional paths may be absent, but plugin.json, index.js, and resources declared by the manifest must exist. It does not execute project scripts or download dependencies. If the entry point or declared resources are missing, or a secret file is detected, first correct the source code or reconfirm the scope. Retain the returned packageHash, sourceRevision, and validation results. Successful packaging does not mean JavaScript syntax, actual installation, frontend loading, or functional execution has passed verification.\n\nReport separate results and evidence for source code, static checks, builds, ZIP packaging, simulated tests, actual installation, activation configuration, frontend loading, and functional execution. Mark builds for plain JavaScript as \"not applicable\"; mark steps that were not run as \"unverified\" and explain why. Tests using SDK substitutes or a standalone Electron DOM environment are not SiYuan integration tests; verify desktop Electron and the desktop browser separately.\n\nInstallation and activation each require the user's explicit authorization for the corresponding action and target. Overwriting an enabled plugin may execute code immediately; a disable notification does not confirm unloading in every frontend, so do not claim it automatically guarantees execution has stopped. Once authorized, use only the native installation interface, bound to the package digest and target revision returned by the host; do not install through generic file writes. Rolling back code does not roll back the user's notes or configuration.\n","frontmatter":{"name":"siyuan-plugin-development","description":"Official frontend plugin development workflow. After the user explicitly opts in, clarify missing requirements, confirm a short plan, implement in a managed copy, verify, and package. Installation and activation require separate authorization."},"isInternal":false,"tokens":1917,"sizeBytes":10284}],"systemPromptSnippet":"<yakaai_skills repo=\"siyuan-note/siyuan\">\n<!-- File: AGENTS.md (Tokens: ~10118 | Category: root-instruction) -->\n# AGENTS.md\n\nSiYuan repository guide. Module path `github.com/siyuan-note/siyuan`, license AGPL-3.0.\n\n---\n\n## 1. Non-negotiable constraints\n\n### Verify the code baseline before repository tasks\n\n- Before each new repository task, verify the repository, current commit, branch, and working-tree state. For work targeting the latest branch, select the baseline source branch using section 2's Git rules and fetch the relevant remote refs before analysis or editing. Respect explicitly requested commits, tags, or pull requests; for a fixed-revision review or historical investigation, inspect that revision without advancing it or synchronizing the working branch. Fetch only if needed to resolve the requested revision. If fetching is unavailable, report the known commit and freshness limit; read-only analysis may continue, but do not claim to have verified the latest remote baseline\n- Follow the repository-specific Git rules for working branches and authorized push destinations. If the user explicitly requests a task branch or the repository's own rules require one, create it from the verified task baseline. Using an isolated worktree does not change the default working branch or authorize pushing to a task branch instead. When synchronizing a development branch, automatically fast-forward it only when its working tree is clean and it is merely behind. Rebase is permitted when synchronizing divergent development branches, provided existing work is preserved; this does not authorize force-pushing rewritten history. Read-only analysis does not itself authorize branch synchronization. If synchronization is blocked, preserve existing work and report the limitation; do not discard changes, automatically stash, create merge commits, or silently substitute another push destination\n- Reuse the verified baseline for follow-ups in the same task; recheck after a substantial pause, a repository or branch change, or when newer commits could affect the task. Unrelated conversation needs no fetch\n\n### Requirement evaluation\n\nBefore implementing a requirement, evaluate its premise and approach with effort proportional to its complexity and risk\n\n1. **Check compliance first:** For major features, review applicable compliance requirements, licensing terms, privacy risks, and costs before technical design\n2. **Check the premise and existing capabilities:** Inspect the relevant implementation, settings, shortcuts, APIs, or plugin capabilities, and reproduce reported behavior when feasible. Check applicable repository rules, encrypted notebook compatibility, the `.sy` format, and API contracts. Distinguish the user's goal and acceptance criteria from a suggested implementation; evaluate whether that implementation addresses the underlying problem. State any limits to verification\n3. **Choose an appropriate approach:** Prefer the smallest sufficient change consistent with existing practices and compatibility requirements. Consider whether an existing capability or a documentation correction already satisfies the need, including the option of making no code change\n4. **Briefly report before editing:** State the conclusion, key evidence, and remaining uncertainty in a progress update before the first change. Discuss alternatives only when they involve meaningful tradeoffs; simple tasks need only a short explanation\n5. **Ask only when a decision is needed:** If the premise is false or implementation is unwarranted, explain the evidence and ask how to proceed unless the user's existing instructions already resolve the next step. Ask before changing the agreed goal or scope, or making an unresolved product, priority, or compatibility tradeoff. Do not silently narrow or broaden the task\n6. **Otherwise proceed directly:** When the requirement is reasonable, implement it without another confirmation round. Choosing a different implementation that meets the agreed goal and acceptance criteria does not itself require approval\n\nUnambiguous mechanical edits with no behavioral or compatibility impact need no separate evaluation round. Continue to follow applicable repository checks; a rename, version bump, or documentation update is not automatically exempt\n\n### Do not hand-edit\n\n- `app/stage/protyle/js/lute/lute.min.js` (built from upstream `88250/lute`)\n- `app/stage/build/**`, `app/src/types/dist/**`\n- `app/changelogs/**` (generated by separate tooling)\n- `app/kernel/SiYuan-Kernel*`, `*.syso`, `kernel/kernel.aar`\n- `app/pandoc/*`\n- Other files marked as generated: edit their source and regenerate them instead. API declarations and schemas come from `kernel/apicontract/`; attribute-view capability declarations come from `kernel/av/capabilities.go`. Before changing either, read the generation instructions and output inventory in [docs/API-CONTRACTS.md](docs/API-CONTRACTS.md#generation-and-verification), including Petal synchronization\n\n### Verification and prohibited operations\n\n1. **Frontend verification:** Do not use `npx webpack` or `pnpm dev` to verify changes. After frontend code changes, run `pnpm run lint` from `app/`; it includes type checks, ESLint automatic fixes, and CSS, menu, and startup-language checks, but not behavioral tests. Review the resulting diff and keep fixes within the task scope without overwriting existing work. In read-only tasks, use applicable non-mutating checks instead, such as `pnpm run typecheck` and ESLint without `--fix` or `--cache`; do not run the auto-fixing lint command\n2. **Frontend build:** Do NOT run `pnpm build` — the developer runs `pnpm dev` manually, and `pnpm build` will conflict with it, producing broken bundles\n3. **Kernel development:** After modifying Go code, run `gofmt`, but do not compile the kernel binary or restart a running kernel; the developer handles both manually. Isolated regression tests and contract generation are allowed; they must not replace the product binary or restart the running instance\n4. **Git:** **NEVER** run `git commit` / `git push` unless explicitly asked — no exceptions\n5. **UI verification in a browser:** Open `/stage/build/desktop/` directly instead of `/`. The kernel selects the frontend bundle by User-Agent in `kernel/server/serve.go`, and a UA containing `Electron` is redirected to the Electron-only `/stage/build/app/`, which fails in a plain browser with `require is not defined`; VSCode's built-in browser sends such a UA. The Electron main window does not expose a remote debugging port by default\n6. **Verification scope:** Run the checks and regression tests applicable to the changed behavior, including non-API Go code, frontend interactions, and scripts. Select scope according to impact and risk; a full suite is not required for every task, and lint or formatting alone does not establish behavioral correctness. Use `app/scripts/test.mjs` and the relevant maintenance guides to identify test discovery and commands. For documentation-only changes, check accuracy, links, language alignment, and the diff rather than running auto-fixing code checks. Report passed, failed, and unrun checks with their limits; do not infer that tests passed solely from a successful CI job or release\n7. **Test synchronization:** Whenever code or files change, handle the corresponding tests and fixtures in the same change rather than leaving them for later\n   - Rename, move, merge, split, or delete a test file together with its source, and keep the file name reflecting the code it covers; a test file whose source no longer exists or whose name no longer matches is stale and must be repaired or removed\n   - When behavior changes intentionally, update the affected assertions, case names, and comments to the new contract instead of dropping the coverage; when behavior is removed, delete only the cases that cover the removed behavior and keep adjacent assertions\n   - Keep test doubles aligned with the code under test: remove mocks of exports, constants, flags, and helpers that no longer exist, and do not preserve assertions that only pass because a stale stub keeps them alive\n   - Do not use a test that already fails for unrelated reasons as permission to leave a new mismatch; state such failures separately and verify whether the task changed their outcome\n   - Merge test files only when they cover the same subject and merging removes duplication without losing cases or discoverability; otherwise keep the current split\n   - Run the affected tests after the synchronization, and name the files and cases that were updated, merged, or deleted\n\n### Encrypted notebook compatibility\n\n- Encrypted notebook development is complete. Maintain it as a released feature\n- Existing encrypted data is the compatibility baseline. Upgrades must preserve access and recovery; do not treat it as disposable development data or require users to delete or recreate it\n- Changes to document, asset, attribute-view, key-envelope, backup, history, or sync formats must retain authenticated reads of supported existing formats or provide a recoverable migration before removing a reader\n- Changes to envelopes, AAD semantics, or key derivation must use an explicit format version and a compatibility plan. Migration must authenticate the source, preserve recovery material, and remain recoverable after failure or interruption; never regenerate MasterSalt or discard keys to work around incompatibility\n- Unknown formats, corruption, and authentication failures must preserve the original data and return an error; compatibility must not bypass authentication or fall back to plaintext. Derived indexes may be rebuilt after the source ciphertext authenticates\n- Format changes require regression coverage using fixtures from the supported previous format, including the affected read, export, history, backup, and recovery paths. See `docs/ENCRYPTED-NOTEBOOK.md` and `docs/ENCRYPTED-NOTEBOOK.zh-CN.md`; keep their compatibility policies aligned\n\n---\n\n## 2. Project-specific rules\n\n1. **i18n:**\n   - New keys go at the **top** of each `langs/*.json` object; add to every language file (reference `en.json`)\n   - Indent `langs/*.json` with tabs, using one tab per nesting level; do not use spaces for indentation\n   - Exception: inside the `_kernel` object, append new entries at the **end** using the next incremental numeric key\n   - Each language must be properly translated — do NOT copy the same text across all language files\n   - Use three ASCII periods (`...`) for ellipses in all localized strings; do not use Unicode ellipsis characters (`…` or `……`)\n   - Setting description tip strings must not end with a period or equivalent sentence-ending mark (for example `.`, `。`, or `।`)\n   - User-facing error messages must not end with a period or its language-specific equivalent (for example `.`, `。`, or `।`); preserve punctuation between sentences and apply this rule consistently across all languages, including `_kernel` messages\n   - Domains: `ld246.com` only in `zh-CN.json`; use `liuyun.io` in all other languages\n   - In `zh-TW` localization and the Traditional Chinese user guide, consistently translate the content-model term Block as `區塊`, including compounds (e.g. `子區塊`, `程式碼區塊`, `區塊 ID`); never abbreviate it as `塊`, and count blocks with `個區塊`\n   - Translate Block Reference as `區塊引用` and Blockquote as `引述區塊`; preserve non-content-block terms such as `分塊` (data chunks) and `覈取方塊` (Checkbox)\n   - Preserve each `_kernel` message's Go format argument positions and verbs from `en.json`; when translation requires a different order, use explicit argument indexes such as `%[4]s` and `%[3]d`. Preserve `%%` for literal percent signs\n   - After modifying i18n files, run `python scripts/check-lang-keys.py` to verify key completeness and kernel format argument compatibility across all language files\n2. **Cross-platform scripting:**\n   - Do not assume the current shell is Bash, zsh, or PowerShell. Confirm the shell before using shell-specific syntax; otherwise avoid constructs such as `&&`, heredocs, and `/dev/null`\n   - For simple sequences, use separate command calls and set the command working directory instead of chaining `cd` with another command\n   - For multi-step logic, write and run a temporary Node.js or Python script. On Windows, avoid PowerShell unless necessary\n   - Do not pass non-ASCII text through shell pipelines, PowerShell here-strings, `python -c`, or `node -e`; write the text to a UTF-8 file with a file-editing tool and consume that file instead\n3. **Icons:** Do not hand-write SVG; use existing icons from `app/appearance/icons/litheness/icon.js` when possible\n   - If no existing icon is suitable, source one from the official [Lucide icon library](https://lucide.dev/icons/) and adapt only attributes such as stroke width to match the established icon style; preserve the upstream path data\n   - When adding an icon to `app/appearance/icons/litheness/icon.js`, add its preview entry to `app/appearance/icons/index.html` in the same change and keep the order aligned\n4. **User guide:** When editing the user guide, follow `docs/SY-FORMAT.md`\n   - User-guide changes must be synchronized across all four bundled languages: English (`app/guide/20210808180117-6v0mkxr`), Simplified Chinese (`app/guide/20210808180117-czj9bvb`), Traditional Chinese (`app/guide/20211226090932-5lcq56f`), and Japanese (`app/guide/20240530133126-axarxgx`). Do not omit Japanese or discover corresponding pages only by Chinese/English title matching\n   - Before finishing a guide change, enumerate the language directories under `app/guide/` and verify the corresponding section in each language. Translate the content, preserve existing document/block IDs, generate fresh IDs for new blocks, and validate the changed `.sy` JSON. `docs/` developer documentation does not replace the bundled user guide\n   - When a feature adds or changes shortcuts, update the shortcut documentation in the user guide in the same change; if the appropriate section is unclear, ask the user where it should be placed\n   - Outside list items, complete prose paragraphs must end with appropriate punctuation for their language (for example `.`, `。`, `?`, or `！`); use a colon when introducing following content. Headings, navigation/reference directories, short labels, standalone links, and code, formulas, or technical syntax are exceptions and must not receive punctuation mechanically\n   - List item text must not end with a period or equivalent sentence-ending mark (for example `.`, `。`, or `।`)\n   - Represent in-app UI navigation paths as segmented `kbd` text marks: use one `NodeTextMark` with `TextMarkType: \"kbd\"` per navigation level, and place a plain `NodeText` containing ` - ` between adjacent levels\n   - In every language, separate a `kbd` path from adjacent prose with exactly one ASCII space on each side, except at block boundaries, after full-width punctuation, or before any punctuation. Half-width punctuation before the path still requires a space; keep internal ` - ` separators unchanged\n5. **Git:**\n   - Branch policy is repository-specific: in `siyuan-note/siyuan`, use `dev` as the baseline source branch and default working branch. If `dev` is unavailable, use the repository's verified default branch. Do not commit on or push to `master` while `dev` is available; when `dev` is unavailable and `master` is the verified default branch, it is permitted\n   - Select the working branch from the user's requested outcome. For direct commits and explicitly authorized pushes, use the selected baseline as the working branch, commit branch, and push destination unless the user explicitly requests a task branch. When the user explicitly requests an ordinary development pull request, create a task branch from the verified baseline before the first change and use it as the PR head, including when the authorized push destination is a fork; a shared baseline branch such as `dev`, or a fork mirror of it, must not be used as the PR head except for the official release workflow below. GitHub does not support changing an existing PR's head branch, so verify the head before creating the PR. Creating a branch does not authorize committing or pushing without the user's authorization. Do not create or push a task branch merely to isolate changes or because the selected working branch has uncommitted work or is behind the remote\n   - **Official releases:** Before release work, read and follow [docs/RELEASE-VERIFICATION.zh-CN.md](docs/RELEASE-VERIFICATION.zh-CN.md) for preparation, build verification, integration, and history synchronization. Build from a verified `dev` commit and keep it fixed until the release PR is merged; if it advances, stop and reconcile the build inputs. With authorized release integration, use `dev` as the PR head and `master` as the base, the sole exception to the shared-branch PR rule. Use a merge commit; if repository settings prohibit it, report the limitation instead of substituting squash or rebase. Verify the resulting `master` tree against the package build commit before creating a release tag\n   - **Release history and authorization:** Before further development, merge the released `master` commit back into `dev`, preserving both histories and verifying an unchanged released code tree when no subsequent code changes have occurred. Perform this synchronization only with authorization for the merge and explicit authorization for its push. The release exception does not authorize direct commits or pushes to `master`, or unrequested PR merges, tags, uploads, or publication\n   - In other repositories, follow their own branch rules for baseline selection and work; otherwise prefer `dev` when it exists locally or on the remote, and use the repository's actual default branch when `dev` does not exist. Do not extend SiYuan's `master` prohibition to other repositories or assume their default branch is named `master` or `main`\n   - If the intended branch cannot be determined confidently from the user's instructions, the repository's rules, and local or remote branch information, ask the user which branch to use and wait for their answer before editing, committing, or pushing; do not guess or silently choose a fallback\n   - Before editing, verify the repository and current branch. Before every explicitly authorized commit or push, verify the current branch again and, for a push, the destination remote and branch; never rely only on the upstream configuration. If SiYuan is checked out on a branch other than the selected working branch, switch to the selected working branch before making changes, or create and switch to a task branch based on the selected baseline when the user explicitly requests a pull request or task branch; preserve existing work and do not reset or discard it to switch branches\n   - When explicitly asked to commit, follow the style of recent commits (gitmoji prefix + subject, in English)\n   - Append the full issue/PR URL to the end of the commit title (e.g. `https://github.com/siyuan-note/siyuan/issues/<NNN>`, not the `#NNN` short form — it is clickable) only when a related issue exists; never put the URL in the commit body, and do not fabricate one\n   - When creating a pull request, write an English title without a gitmoji shortcode or emoji prefix. Describe the final change in the PR title instead of copying a commit title with its gitmoji prefix or appended issue URL\n6. **GitHub:** Prefer the GitHub CLI (`gh`) for all GitHub operations, including reading issues, comments, pull requests, commits, statuses, and metadata. If `gh` is unavailable or does not support the operation, fall back to the GitHub API or web interface\n   - When creating an issue in this repository, use an English title and a Chinese body whose first paragraph is the corresponding Chinese title; do not use this repository's own issue templates or reproduce their form fields, and write a task-specific body directly. Preserve the relevant details from the discussion, including the problem, reproduction steps, expected and actual behavior, evidence, agreed approach, constraints, compatibility concerns, alternatives and tradeoffs, acceptance criteria, and unresolved questions, as applicable. Remove repetition and unrelated material, but do not omit information needed to understand, evaluate, or implement the issue merely to make it shorter\n   - Before filing an issue or pull request in any other repository, read that repository's own templates first and follow the one matching the task: check `.github/ISSUE_TEMPLATE/` (issue forms and Markdown templates) and the PR template, then fill every section it marks as required. The conventions above — an English title, a Chinese body, and bypassing the templates — apply only to this repository and must not be carried over. Confirm separately whether and where the project accepts issues and pull requests; do not infer whether pull requests are accepted from mirror status or disabled issues. Identify the correct issue tracker (for example, `golang/go` carries gopls issues, not `golang/tools`) and code contribution destination before submitting\n   - When changing the title of an existing issue, keep the replaced title as the first paragraph of the body, separated from the remaining content by a blank line, and keep the rest of the body unchanged\n   - When replying to an issue or pull request, explain the conclusion and its basis with detail proportional to the task. Include relevant implementation decisions, behavior changes, compatibility concerns, and unresolved issues. Report only verification and tests actually performed and the conclusions they support; do not list unperformed tests. Briefly disclose any verification gap that materially limits the conclusion, and never imply broader coverage than was verified. Avoid routine logs and unrelated commands. For implemented changes, invite the user to test the new version and provide feedback. If the issue or pull request body is in Chinese, reply in Chinese; otherwise, reply bilingually in the language of the body and Chinese.\n   - When the selected endpoint supports labels, they may be included in the same create or update payload. Verify the issue or pull request identity and the requested non-label changes, including title, body, state, or base branch as applicable. The exception applies only to labels: do not check whether they were applied or send a follow-up request solely to apply them, and do not claim they were verified; GitHub may silently drop label changes when the actor lacks push access\n   - For GitHub write operations containing non-ASCII text on Windows or when shell encoding is uncertain, use this file-based workflow. Do not use this workflow for ASCII-only requests:\n     1. Create the request payload as UTF-8 JSON with a file-editing tool, not an inline shell command\n     2. Store it in the operating system's temporary directory with a unique name such as `siyuan-gh-<operation>-<timestamp>.json`; do not leave temporary payloads in the repository\n     3. Call the appropriate endpoint with `gh api --method <method> \"<endpoint>\" --input \"<absolute-json-path>\"`\n     4. Inspect the returned resource and read it back with `gh api`. Prefer parsing JSON and comparing its fields with the submitted payload to verify the published text exactly, including line breaks and non-ASCII characters. Normalize CRLF to LF if needed, but preserve meaningful leading/trailing whitespace and blank lines. For `--jq` text output, query one field per call (for example `.title` and `.body`) and remove only the single output line terminator added by the command\n     5. Keep the submitted payload until comparison passes; then delete temporary payload and read-back files and confirm their removal\n   - Prefer capturing `gh` stdout directly in a Node.js or Python script and decoding it as UTF-8. When using PowerShell to save output, explicitly use `Out-File -Encoding utf8`; Windows PowerShell 5.1 defaults to UTF-16LE for `>` and `Out-File`, and its UTF-8 output includes a BOM, so readers must handle that BOM. Explicit file encoding cannot repair text already corrupted by pipeline decoding; use direct process capture in that case\n   - `gh api` applies `--jq` locally after sending the request, so an invalid or failing expression can cause a non-zero exit even when the write succeeded. Omit `--jq` on write commands and use separate read-back calls to verify individual fields. When a write command exits non-zero, confirm the remote state before deciding whether to retry; the write may already have succeeded, and retrying can create duplicate issues or comments or repeat other side effects\n7. **Issue titles:** Whenever the user asks to generate an issue title, provide it in English regardless of the wording of the request, and do not start it with `Fix`. These rules choose title wording from the issue's nature; they are not an instruction to apply GitHub labels\n   - For a bug, objectively describe the problem or symptom instead of writing from a bug-fix perspective\n   - For an improvement to existing functionality, write the title from an improvement perspective and prefer `Improve ...`\n   - For a capability that did not previously exist, write the title from a support perspective and prefer `Support ...`\n   - If the nature is unclear, infer the perspective from the issue content\n8. **LD246:**\n   - When accessing `ld246.com`, set the HTTP `User-Agent` header to `SiYuan-Coding-Agent`\n   - Read post details with `GET https://ld246.com/api/v2/article/{articleId}?p=1`; `data.article` contains the post, and `p` paginates replies. For Markdown, use `GET https://ld246.com/article/raw/{articleId}`\n   - Authenticate both endpoints with `Authorization: token <LD_TOKEN>` using the environment variable; on Windows, check process, user, then machine scope. Do not expose the Token or rely on UA exemptions\n   - Check HTTP status and JSON `code` (`0` means success); report missing credentials or authentication errors\n9. **Configurable entries:**\n   - Treat configurable desktop menu `data-id` values and stable dock entry keys as persisted configuration identifiers. Resolve dock keys through `getDockEntryKey` in `app/src/config/entryVisibility/catalog.ts`: it prefers `data-entry-id`, maps plugin runtime types to stable keys derived from plugin name and dock ID, and falls back to the built-in `data-type`. Do not rename or reuse persisted keys unless the same change migrates existing visibility and order configuration\n   - When adding, removing, renaming, or moving a configurable desktop menu item or dock entry, or changing its identifier mapping, update `app/src/config/entryVisibility/catalog.ts` in the same change, including its type, hierarchy, label, Simple profile default, and default position, and update the related tests\n   - Give every configurable desktop menu separator a stable `data-id` and register it in the catalog as a `separator`. Keep the catalog order aligned with the actual menu declaration order because it defines the built-in order and where new entries are merged into existing custom profiles\n   - Keep parent and child paths aligned with the actual menu hierarchy. Dock entries support visibility, ordering, and movement across the six scopes in `dockOrder.ts`; use that mechanism rather than treating dock entries as ordinary menu siblings. Preserve existing placement, hidden entries, unavailable plugin slots, and compatibility with profiles that have no dock orders\n   - Cover catalog consistency, separator placement, order migration, and plugin-slot preservation in the related tests. Configured menus must not produce leading, trailing, or consecutive separators\n   - The menu `ignore` option controls conditional rendering and must not be used to opt an entry out of visibility or order configuration\n10. **API contracts:**\n    - Follow [docs/API-CONTRACTS.md](docs/API-CONTRACTS.md) when adding or changing kernel HTTP APIs. Define new endpoints in `kernel/apicontract/` and bind their handlers through `contractHandler`; keep contracts synchronized when changing existing endpoints\n    - Treat `docs/API-CONTRACTS.md` and its localized versions as maintenance guides. For routine endpoint or feature changes, do not append endpoint-specific behavior, feature summaries, verification results, or focused test commands. Update these guides only when the contract mechanism, compatibility policy, generation workflow, or test discovery/verification rules change, or when the user explicitly requests a documentation update; keep localized versions aligned\n    - Document endpoint behavior, defaults, constraints, and compatibility in comments on the corresponding contract source declarations. Synchronize plugin-facing explanations in the corresponding maintained declarations in `petal`, following the Petal documentation rule below. Put user-facing feature instructions in the bundled user guide\n    - Do not automatically add newly implemented endpoints to `docs/API.md` or its localized versions (`docs/API.zh-CN.md`, `docs/API.ja.md`); add such documentation only when explicitly requested by the user. Continue maintaining API contracts, generated declarations, and required regression tests. These documentation scope rules take precedence over broader documentation instructions in the maintenance guides\n    - After changing an existing endpoint's request, response, or documented behavior, check its existing reference entries in `docs/API.md`, `docs/API.zh-CN.md`, and `docs/API.ja.md`. Update affected entries together; do not assume the language versions cover identical endpoints. Newly added endpoints remain documented only on request\n    - Preserve existing input compatibility, response variants, authorization, and encrypted notebook lease behavior; cover affected behavior with regression tests\n    - Every route must have a typed contract; remove both the registration and contract when deleting a route, and never bypass contract checks with `any` or type assertions\n    - After contract changes, run `pnpm run api:generate --petal ../../petal` and `pnpm run api:check --petal ../../petal` from `app/`; verify the Petal checkout first using the cross-repo rules below. The example path is relative to the generator's `kernel/` working directory and assumes a sibling `petal` repository; substitute the verified path when different. Synchronize related public declarations in `petal` and do not hand-edit generated declarations or schemas\n    - Run `pnpm run lint` from `app/`, `go test ./apicontract/...` from `kernel/`, and the applicable API compatibility and route coverage tests described in the maintenance document. Verify that existing CI selections and documented commands discover new regression cases; automatic discovery or coverage by an existing full-suite command satisfies this requirement without a documentation edit. Update CI selections and documented commands only when they would otherwise miss the new tests. Report task-specific verification commands and results in the task response or PR description\n\n11. **Desktop and mobile parity:**\n    - When changing functionality shared by desktop and mobile, inspect the corresponding mobile implementation under `app/src/mobile/` and update it in the same task. This includes menus, toolbars, dialogs, interactions, and their event handlers; do not assume that desktop changes automatically apply to mobile\n    - Before finishing, verify both implementations and their shared dependencies, preserving platform-appropriate interactions. If no corresponding mobile feature exists or the change is desktop-only, state why mobile changes are not needed in the task response\n\n12. **UI copy and user guides:** Change wording only when needed, following nearby style and keeping necessary guidance and warnings. Put specific instructions beside the relevant control or in the corresponding guide section; do not overload general descriptions with feature lists or implementation details\n\n13. **Configuration defaults:** Put a new setting's default value in its constructor (`NewAppearance`, `NewEditor`, `NewFileTree`, etc.) and let the shared normalization functions fill missing containers and values. `InitConf` only projects runtime state and runs versioned migrations; do not add a startup function that patches a single default\n    - `conf.json` written by an older version has no key for a new field, and configuration sections unmarshal into zero values, so a plain `bool` is only correct when the default is `false`\n    - A default of `true` requires `*bool` (and a defaulted non-zero enumeration requires `*int`): initialize it in the constructor, keep the `nil` branch in normalization, and preserve an explicitly saved `false`. See `FileTree.CreateDocAtTop`, `Editor.CheckBlockRef`, and `Search.HanSensitive`\n    - Keep a default-`true` plain `bool` only when existing workspaces must stay `false` while new workspaces start `true`, as with `Appearance.MenuMouseScroll`: the constructor supplies the new-workspace value and the zero value supplies the existing-workspace one\n    - `POST /api/setting/set<Section>` merges the request into the current section: omitted fields keep their current value and `null` restores the default; declare `Merge` on `SettingWrite` instead of adding per-field preservation branches. Decoding starts from the section constructor defaults so `null` never falls back to a zero value\n    - Keep `*bool` on the API request and response declaration when the wire format must distinguish `null`, even if the persisted configuration uses a plain `bool`; see `apicontract.SettingFileTree`\n    - When an existing workspace must retain a legacy default that is not the zero value (for example `OCR.Auto`), resolve it in the load path that knows whether `conf.json` existed; do not read that fact from a global inside a constructor or normalization helper. Resolve a segment that exists but lacks the decisive field the same way as a missing segment\n\n---\n\n## 3. Coding conventions\n\n1. **Comments:** Write in Chinese, wrap at 120 characters, and describe what the code does without referencing the old implementation\n2. **Punctuation:** Use language-appropriate punctuation in UI text, comments, user guides, and documentation (e.g. Chinese punctuation ，。：；！？「」 for Chinese). Keep UI text and its punctuation in i18n language files rather than hard-coding them in code\n3. **UI paths:** In all contexts, including code comments, UI text, i18n, user guides, documentation, issue/PR content, and responses, separate navigation levels with a hyphen surrounded by spaces (for example, `设置 - 快捷键 - 通用`); do not use arrow symbols such as `→`\n4. **Markdown:** Do not hand-wrap; keep each line (paragraphs, table rows, list items, etc.) on a single line\n5. **TypeScript/JavaScript:** Semicolons required, use double quotes, indent with spaces\n   - When moving or extracting a symbol into another module, update all affected imports to reference its defining module directly. Do not leave forwarding re-exports in the original module merely to avoid updating callers\n6. **CSS:** Do not use the `:has()` selector because of its performance impact, and prefer a comma-separated selector list over `:is()`, whose extra matching layer adds measurable cost\n   - Expand selector groups instead of grouping them with `:is()` or `:where()`; use `:where()` only to drop specificity from a single selector\n   - Before adding or styling a basic control, inspect and reuse the existing component, markup pattern, and shared styles in `app/src/assets/scss/component/` (for example, `b3-button`, `b3-select`, and `b3-text-field`), including existing modifiers; use `block__icon` for established icon-button patterns\n   - Apply the same reuse-first rule to menus, dialogs, tooltips, and drag interactions: inspect existing implementations and APIs before adding a feature-specific replacement\n   - For single-choice enumerations in menus, use submenu items with the current option checked rather than embedding a select control. Show a right-arrow indicator on the parent item, open its submenu on hover on desktop, and support tapping on mobile. Reuse the existing menu implementation and positioning behavior\n   - Feature styles should describe layout (such as placement, width constraints, gaps, and wrapping), rather than duplicate or override basic control appearance (such as height, padding, typography, colors, borders, shadows, and hover/focus/disabled states). Do not use deeper selectors, inline styles, or `!important` merely to restyle a shared control\n   - If existing controls cannot meet a requirement, explain the concrete need and prefer extending a shared component or modifier when the need is reusable. Keep necessary feature-specific exceptions narrowly scoped; use theme variables for appearance and retain keyboard focus and disabled feedback\n   - Reuse components according to their purpose; do not borrow an unrelated component (for example, menu items for a form) and then cancel its styles. Preserve useful feature classes as theme hooks when switching to shared controls\n   - When reviewing control-style changes, check consistency with shared controls, theme overrides through shared classes, light/dark themes, narrow layouts, and large editor fonts. Preserve necessary layout and touch-target constraints; do not remove all feature styles indiscriminately\n7. **CSS positioning and scrolling:** When changing `position`, `transform`, `contain`, or `overflow` on a shared container, check the effects on descendant positioning reference frames, overlay coverage, and clipping. Prefer a dedicated container when a local control needs a positioning reference. For settings dialog changes, verify detail overlays, the top drag area, and scrollbar placement at different window widths\n\n8. **Custom attributes:**\n   - HTML custom attributes used only for DOM/UI state must use the `data-` prefix without an extra `sy` namespace, for example `data-ui-density`\n   - Custom attributes owned by built-in features and persisted in notebook, document, or block data must use the `custom-sy-` prefix. Preserve existing persisted attribute names and their compatibility; DOM/UI naming changes must not rename these data attributes\n   - Define custom attribute name constants in `app/src/constants.ts`, alongside similar constants in `Constants`, rather than in individual feature modules\n\n9. **Frontend preference storage:** Do not use browser `localStorage` directly, including `window.localStorage` and `globalThis.localStorage`\n   - New user-facing preferences, including settings switches and explicit reader or panel preferences, default to `conf.json`; define them in the corresponding configuration namespace, normalize them in the kernel, and write through `createConfigNamespaceApi().patch`\n   - Reserve `local.json` for growing mappings keyed by document, view, or transaction IDs; transient or session state such as scroll positions, dialog geometry, and restored tabs; usage history and reading positions; and plugin runtime state\n   - Writing frequency does not determine storage: `uiLayout` is already a frequently updated configuration namespace with silent notifications. Neither `conf.json` nor `local.json` participates in data sync; configuration export includes `conf.json`\n   - Read local state from `window.siyuan.storage`; when changing it, update its in-memory value and persist it with `setStorageVal` from `app/src/protyle/util/compatibility.ts`. SiYuan persists these values in the workspace's `data/storage/local.json`; do not use browser `storage` events to observe changes to this store\n\n10. **Frontend platform identification:**\n   - Use the mutually exclusive `body` class names written by `applyPlatformClasses()` in `app/src/protyle/util/compatibility.ts`: `body--mac`, `body--windows`, `body--linux`, `body--ios`, `body--android`, and `body--harmony`\n   - `body--win32` and `body--win32-browser` are retained only for existing third-party themes; use neither in new code, and do not add consumers when changing related styles\n   - Do not derive the frontend platform from the kernel reported container or from `data-backend`: a remote kernel reports its own host, so both differ from the platform of the current frontend\n   - `data-frontend` on the root element reports the frontend form (`desktop`, `desktop-window`, `mobile`, `browser-desktop`, `browser-mobile`) and may be used by built-in styles and third-party theme CSS; `data-backend` on the root element is for third-party JavaScript only and must not appear in built-in CSS\n\n---\n\n## 4. Required toolchain\n\n| Tool | Version | Source of truth |\n|---|---|---|\n| Go | see `go` directive | `kernel/go.mod` |\n| Node (+ pnpm) | see CI matrix | `.github/workflows/cd.yml`, `app/package.json` (`packageManager` field) |\n\n---\n\n## 5. Repository layout\n\n**Architecture:** Go kernel (`kernel/`) + TypeScript frontend (`app/`). Read versions from `kernel/go.mod`, `app/package.json`, `kernel/util/working.go`.\n\nTop level (repo root):\n\n| Path | Contents |\n|---|---|\n| `kernel/` | Go backend — server, data engine, API, all domain logic |\n| `app/` | TypeScript frontend (Electron/web), built by webpack into `app/stage/build/` |\n| `app/appearance/` | Themes, icons, **i18n** (`appearance/langs/*.json`) |\n| `app/stage/` | Build output served by the kernel |\n| `app/changelogs/` | Per-version changelog markdown |\n| `.github/` | Contribution and security policies, issue/PR templates, CI workflows |\n| `scripts/` | Release packaging, changelog generation, language-key checks |\n\n### Major `kernel/` packages (under `kernel/`)\n\n| Package | Responsibility |\n|---|---|\n| `main.go` (`//go:build !mobile`) | Desktop entry point, delegates to `cli/cmd` |\n| `cli/cmd/` | Cobra CLI subcommands |\n| `model/` | Core domain: blocks, transactions, indexing, search, attribute views, export, history, sync, flashcards, AI, CalDAV/CardDAV, auth |\n| `treenode/` | In-memory tree over the Lute AST + `blocktree.db` |\n| `av/` | **Attribute View** (database) engine: values, filters, sorts, layouts (table/kanban/gallery) |\n| `sql/` | **Embedded SQLite** (`siyuan.db`, `history.db`, `asset_content.db`) + FTS5; async index queues |\n| `search/` | FTS tokenizer helpers, CJK conversion (`hanconv.go`) |\n| `bazaar/` | Marketplace: plugins/widgets/themes/icons/templates |\n| `filesys/` | Read/write `.sy` files on disk (via `filelock`) |\n| `server/` | Gin server bootstrap (`serve.go`): middleware, TLS/cmux, WebDAV/CalDAV/CardDAV, WebSocket, MCP |\n| `api/` | HTTP route registration (`router.go::ServeAPI`) + per-area handlers |\n| `conf/` | Configuration structs |\n| `util/` | Cross-cutting: `working.go` (workspace, `Boot()`), `lute.go`, `i18n.go`, `websocket.go` (melody push), `result.go` (API envelope) |\n| `plugin/` | Plugin subsystem (kernel side) |\n| `mcp/` | MCP (Model Context Protocol) server |\n| `agent/` | AI agent runtime |\n| `mobile/`, `harmony/` | `//go:build mobile` gomobile bindings for Android/iOS/HarmonyOS |\n\n### Frontend (`app/src/`) highlights\n\n| Dir | Purpose |\n|---|---|\n| `index.ts` | Main `App` class — boots SPA, opens main WebSocket, handles WS push events |\n| `window/` | Detached Electron window variant |\n| `protyle/` | Block editor; rich-content rendering in `render/`, attribute-view rendering in `render/av/` |\n| `editor/`, `layout/`, `menus/`, `dialog/`, `config/`, `mobile/`, `ai/`, `sync/`, `history/`, `search/`, `card/` | Feature modules |\n| `util/fetch.ts` | `fetchGet`/`fetchPost` — all kernel calls |\n| `layout/Model.ts` | WebSocket client all UI binds to |\n| `constants.ts` | Global constants (version, IDs, storage keys) |\n\nFour webpack configs emit bundles to `app/stage/build/{app,desktop,mobile,export}/`; the kernel's `serveAppearance` selects the app bundle by User-Agent. The `export` bundle is a rendering library (global `Protyle`, entry `app/src/protyle/method.ts`) for code, math, and diagrams. Pages assembled by `app/src/protyle/export/index.ts` load it for desktop PDF preview and standalone exported HTML.\n\n---\n\n## 6. Related repositories (navigation)\n\n| Repo | Role / what to know |\n|---|---|\n| `siyuan-android` / `siyuan-ios` / `siyuan-harmony` | Native apps wrapping the gomobile kernel; read each project's README for platform-specific build, kernel-binding, and integration steps |\n| `siyuan-chrome` | Independent TypeScript browser extension (web clipper); interacts with the running kernel only through the HTTP API in `docs/API.md` |\n| `siyuan-testing` | Playwright end-to-end tests for a running SiYuan instance; test data belongs in the `SiYuan Testing` notebook — see that repository's `AGENTS.md` |\n| `petal` | SiYuan Plugin API declaration (the plugin system is named \"petal\"); consumed by plugins, not a kernel Go dependency |\n| `lute` | Markdown/Kramdown AST engine for the editor and `.sy` format; source of `lute.min.js`. The maintainer's default checkout is `$GOPATH/src/github.com/88250/lute`, not a sibling repo; verify the actual checkout before use |\n| `dejavu` | Data repo / sync engine (encrypted snapshots) |\n| `riff` | Spaced-repetition (SRS) flashcard scheduler |\n| `gulu` / `eventbus` / `filelock` / `httpclient` / `logging` | General utilities, in-process events, file locking, HTTP client, and logging |\n| `go-sqlite3` / `pdfcpu` | Maintainer's forks, pulled in via permanent `replace` in `kernel/go.mod` (keep those) |\n| `epub` / `clipboard` / `go-humanize` / `vitess-sqlparser` / `dataparser` / `encryption` | Smaller Go libraries (export / clipboard / formatting / SQL parse / data parse / crypto) |\n\nAll Go libraries above are dependencies in `kernel/go.mod`. GitHub org: `siyuan-note/*` for the `siyuan-` apps and most libs; `88250/*` for lute, gulu, and the forks (go-sqlite3 / pdfcpu).\n\n### Cross-repo notes\n\n- **Checkout availability:** Before cross-repo work, verify each checkout's path, identity, branch, baseline, and existing changes under its own rules. If a required checkout is unavailable, complete independent work where possible and report the exact synchronization or verification blocker; do not claim the task is fully synchronized. These rules do not authorize automatically creating or downloading another repository\n- **Editing Go dependencies:** To test a local change, add a temporary `replace` in `kernel/go.mod` pointing at your local checkout; **never commit that temporary `replace`**.\n- **Rebuilding `lute.min.js`:** Change `lute`, rebuild with GopherJS, and copy the artifact into `app/stage/protyle/js/lute/`.\n- **Type declarations:** when changing files under `app/src/types/` or other TypeScript declarations and constants exposed to plugins, synchronize the corresponding declarations and constants in the `petal` repository in the same task.\n- **Petal changelog:** Whenever changing files in the `petal` repository, update `petal/CHANGELOG.md` in the same task. Add a concise entry under the current unreleased version before committing the Petal change.\n- **Petal documentation:** Keep `petal/README.md` limited to the project title and the `plugin-sample` link. Do not add feature descriptions, API usage explanations, or code examples to the README. Document API behavior, constraints, and lifecycle in comments on the corresponding API declarations in `petal`; put executable usage examples in the actual source code of `plugin-sample`.\n\n---\n\n## 7. Response style\n\n1. **Language:** Match the user's language; do not mix languages mid-sentence (keep proper nouns / identifiers in their original form)\n\n<!-- File: docs/API.md (Tokens: ~13947 | Category: project-spec) -->\n**English**\n| [中文](API.zh-CN.md)\n| [日本語](API.ja.md)\n\nPlugin resource declarations, data authorization, and publishing APIs are documented in [Plugin publishing](PLUGIN-PUBLISH.md).\n\n* [Specification](#Specification)\n    * [Parameters and return values](#Parameters-and-return-values)\n    * [TypeScript contracts](#TypeScript-contracts)\n    * [Behavior semantics](#Behavior-semantics)\n    * [Authentication](#Authentication)\n* [Notebooks](#Notebooks)\n    * [List notebooks](#List-notebooks)\n    * [Open a notebook](#Open-a-notebook)\n    * [Close a notebook](#Close-a-notebook)\n    * [Rename a notebook](#Rename-a-notebook)\n    * [Create a notebook](#Create-a-notebook)\n    * [Remove a notebook](#Remove-a-notebook)\n    * [Get notebook configuration](#Get-notebook-configuration)\n    * [Save notebook configuration](#Save-notebook-configuration)\n* [Documents](#Documents)\n    * [Create a document with Markdown](#Create-a-document-with-Markdown)\n    * [Rename a document](#Rename-a-document)\n    * [Remove a document](#Remove-a-document)\n    * [Move documents](#Move-documents)\n    * [Set notebook and document sort values](#Set-notebook-and-document-sort-values)\n    * [Set a document's child document sort mode](#Set-a-documents-child-document-sort-mode)\n    * [Get human-readable path based on path](#Get-human-readable-path-based-on-path)\n    * [Get human-readable path based on ID](#Get-human-readable-path-based-on-ID)\n    * [Get storage path based on ID](#Get-storage-path-based-on-ID)\n    * [Get IDs based on human-readable path](#Get-IDs-based-on-human-readable-path)\n* [Assets](#Assets)\n    * [Upload assets](#Upload-assets)\n* [Blocks](#Blocks)\n    * [Insert blocks](#Insert-blocks)\n    * [Prepend blocks](#Prepend-blocks)\n    * [Append blocks](#Append-blocks)\n    * [Update a block](#Update-a-block)\n    * [Delete a block](#Delete-a-block)\n    * [Move a block](#Move-a-block)\n    * [Fold a block](#Fold-a-block)\n    * [Unfold a block](#Unfold-a-block)\n    * [Get a block kramdown](#Get-a-block-kramdown)\n    * [Get child blocks](#get-child-blocks)\n    * [Transfer block ref](#transfer-block-ref)\n* [Attributes](#Attributes)\n    * [Set block attributes](#Set-block-attributes)\n    * [Get block attributes](#Get-block-attributes)\n* [Database](#Database)\n    * [Render](#Render)\n    * [Get](#Get)\n    * [Get primary key values](#Get-primary-key-values)\n    * [Search](#Search)\n    * [Set a cell value](#Set-a-cell-value)\n    * [Add items](#Add-items)\n    * [Remove items](#Remove-items)\n    * [Change layout](#Change-layout)\n    * [Set grouping](#Set-grouping)\n    * [Get filter and sort](#Get-filter-and-sort)\n    * [Set filter](#Set-filter)\n    * [Set sort](#Set-sort)\n    * [Add a field](#Add-a-field)\n    * [Remove a field](#Remove-a-field)\n    * [Set global field sort](#Set-global-field-sort)\n    * [Set per-view field sort](#Set-per-view-field-sort)\n* [Search](#Search)\n    * [Get saved search criteria](#Get-saved-search-criteria)\n    * [Save a search criterion](#Save-a-search-criterion)\n    * [Remove a search criterion](#Remove-a-search-criterion)\n* [SQL](#SQL)\n    * [Execute SQL query](#Execute-SQL-query)\n    * [Flush transaction](#Flush-transaction)\n* [Templates](#Templates)\n    * [Render a template](#Render-a-template)\n    * [Render Sprig](#Render-Sprig)\n* [File](#File)\n    * [Get file](#Get-file)\n    * [Put file](#Put-file)\n    * [Remove file](#Remove-file)\n    * [Rename file](#Rename-file)\n    * [List files](#List-files)\n* [Export](#Export)\n    * [Export Markdown](#Export-Markdown)\n    * [Export Files and Folders](#Export-files-and-folders)\n* [Conversion](#Conversion)\n    * [Pandoc](#Pandoc)\n* [Notification](#Notification)\n    * [Push message](#Push-message)\n    * [Push error message](#Push-error-message)\n* [Network](#Network)\n    * [Forward proxy](#Forward-proxy)\n        * [JSON forward proxy](#JSON-forward-proxy)\n        * [HTTP forward proxy](#HTTP-forward-proxy)\n        * [WebSocket forward proxy](#WebSocket-forward-proxy)\n        * [EventSource forward proxy](#EventSource-forward-proxy)\n* [System](#System)\n    * [Get boot progress](#Get-boot-progress)\n    * [Get system version](#Get-system-version)\n    * [Get the current time of the system](#Get-the-current-time-of-the-system)\n\n---\n\n## Specification\n\n### Parameters and return values\n\n* Endpoint: `http://127.0.0.1:6806`\n* Unless otherwise stated, API interfaces use the POST method\n* For interfaces that take JSON parameters, the parameter is a JSON string placed in the body, and the header Content-Type is `application/json`\n* Return value\n\n   ````json\n   {\n     \"code\": 0,\n     \"msg\": \"\",\n     \"data\": {}\n   }\n   ````\n\n    * `code`: non-zero for exceptions\n    * `msg`: an empty string under normal circumstances, an error text will be returned under abnormal conditions\n    * `data`: may be `{}`, `[]` or `NULL`, depending on the interface\n\n### TypeScript contracts\n\nThe plugin `fetchPost`, `fetchSyncPost`, and `fetchGet` declarations infer request and response types for migrated API paths from generated kernel contracts. Coverage is expanding and includes system utilities, batch block attributes, tag and bookmark operations, selected block queries, notebook listing, history search, and snapshot operations. Existing untyped endpoints and dynamic URLs remain supported. Check the response code before reading successful data from asynchronous calls, and handle nullable fields explicitly.\n\n```typescript\nimport {fetchSyncPost} from \"siyuan\";\n\nconst response = await fetchSyncPost(\"/api/attr/getBlockAttrs\", {id: blockID});\nif (response.code === 0 && response.data) {\n    const value = response.data[\"custom-value\"];\n}\n```\n\nSee the [generated route declarations](../app/src/types/api/index.d.ts) for exact coverage and the [contract maintenance guide](API-CONTRACTS.md) for generation and compatibility rules. Type declarations do not validate JSON at runtime.\n\n### Behavior semantics\n\n* Only interfaces with dedicated interface sections in this document are public APIs. Other kernel routes and `/api/transactions` operations are internal implementations and provide no compatibility or behavioral stability guarantees unless otherwise stated\n* `code: 0` means the interface reported no error while handling the request. It guarantees only the outcome explicitly documented for that interface; it does not mean that related indexes, caches, WebSocket broadcasts, or sync state have all been updated\n* The meanings of omitted fields, `null`, empty objects, and empty arrays are interface-specific. Whether an object or array replaces, merges with, or partially updates existing state, and whether its order is significant, are also defined by each interface\n* An interface may trim, ignore, complete, or transform input. When its documentation states that a normalized result is returned, callers should use the returned `data` as the actual accepted result\n* Do not infer that an operation is read-only from its name. Persistent side effects and their scope are described by each interface when applicable\n* Repeating the same request is idempotent or safe to retry only when explicitly documented. If a response is interrupted or otherwise indeterminate, read the current state before retrying whenever possible\n\n### Authentication\n\nView the API token in <kbd>Settings - Authentication - API token</kbd>. Use it in the request header: `Authorization: Token xxx`\n\n## Notebooks\n\n### List notebooks\n\n* `/api/notebook/lsNotebooks`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"notebooks\": [\n        {\n          \"id\": \"20210817205410-2kvfpfn\", \n          \"name\": \"Test Notebook\",\n          \"icon\": \"1f41b\",\n          \"sort\": 0,\n          \"closed\": false\n        },\n        {\n          \"id\": \"20210808180117-czj9bvb\",\n          \"name\": \"SiYuan User Guide\",\n          \"icon\": \"1f4d4\",\n          \"sort\": 1,\n          \"closed\": false\n        }\n      ]\n    }\n  }\n  ```\n\n### Open a notebook\n\n* `/api/notebook/openNotebook`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Close a notebook\n\n* `/api/notebook/closeNotebook`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nThe close endpoint validates the notebook ID without trimming whitespace. Typed request and response declarations are generated in `app/src/types/api/index.d.ts` and synchronized to `petal`.\n\n### Rename a notebook\n\n* `/api/notebook/renameNotebook`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\",\n    \"name\": \"New name for notebook\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Create a notebook\n\n* `/api/notebook/createNotebook`\n* Parameters\n\n  ```json\n  {\n    \"name\": \"Notebook name\"\n  }\n  ```\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"notebook\": {\n        \"id\": \"20220126215949-r1wvoch\",\n        \"name\": \"Notebook name\",\n        \"icon\": \"\",\n        \"sort\": 0,\n        \"closed\": false\n      }\n    }\n  }\n  ```\n\n### Remove a notebook\n\n* `/api/notebook/removeNotebook`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Get notebook configuration\n\n* `/api/notebook/getNotebookConf`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210817205410-2kvfpfn\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"box\": \"20210817205410-2kvfpfn\",\n      \"conf\": {\n        \"name\": \"Test Notebook\",\n        \"closed\": false,\n        \"refCreateSavePath\": \"\",\n        \"createDocNameTemplate\": \"\",\n        \"dailyNoteSavePath\": \"/daily note/{{now | date \\\"2006/01\\\"}}/{{now | date \\\"2006-01-02\\\"}}\",\n        \"dailyNoteTemplatePath\": \"\"\n      },\n      \"name\": \"Test Notebook\"\n    }\n  }\n  ```\n\n### Save notebook configuration\n\n* `/api/notebook/setNotebookConf`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210817205410-2kvfpfn\",\n    \"conf\": {\n        \"name\": \"Test Notebook\",\n        \"closed\": false,\n        \"refCreateSavePath\": \"\",\n        \"createDocNameTemplate\": \"\",\n        \"dailyNoteSavePath\": \"/daily note/{{now | date \\\"2006/01\\\"}}/{{now | date \\\"2006-01-02\\\"}}\",\n        \"dailyNoteTemplatePath\": \"\"\n      }\n  }\n  ```\n\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"name\": \"Test Notebook\",\n      \"closed\": false,\n      \"refCreateSavePath\": \"\",\n      \"createDocNameTemplate\": \"\",\n      \"dailyNoteSavePath\": \"/daily note/{{now | date \\\"2006/01\\\"}}/{{now | date \\\"2006-01-02\\\"}}\",\n      \"dailyNoteTemplatePath\": \"\"\n    }\n  }\n  ```\n\n## Documents\n\n### Create a document with Markdown\n\n* `/api/filetree/createDocWithMd`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210817205410-2kvfpfn\",\n    \"path\": \"/foo/bar\",\n    \"markdown\": \"\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n    * `path`: Document path, which needs to start with `/` and separate levels with `/` (corresponds to the database `hpath` field)\n        * `/` is a hierarchy separator and cannot represent a literal slash in a document title; missing parent documents are created automatically\n        * For example, `/Notes/Programming in C/C++` creates a document titled `C++` under `Programming in C` under `Notes`\n        * Importers should sanitize each title before joining titles into a path, for example by replacing ASCII `/` with full-width `／` (U+FF0F): `/Notes/Programming in C／C++` creates a document titled `Programming in C／C++` under `Notes`. This replacement changes the title text\n    * `markdown`: GFM Markdown content\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"20210914223645-oj2vnx2\"\n  }\n  ```\n\n    * `data`: Created document ID\n    * If you use the same `path` to call this interface repeatedly, the existing document will not be overwritten\n\n### Rename a document\n\n* `/api/filetree/renameDoc`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\",\n    \"path\": \"/20210902210113-0avi12f.sy\",\n    \"title\": \"New document title\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n    * `path`: Document path\n    * `title`: New document title\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nRename a document by `id`:\n\n* `/api/filetree/renameDocByID`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210902210113-0avi12f\",\n    \"title\": \"New document title\"\n  }\n  ```\n\n    * `id`: Document ID\n    * `title`: New document title\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Remove a document\n\n* `/api/filetree/removeDoc`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\",\n    \"path\": \"/20210902210113-0avi12f.sy\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n    * `path`: Document path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nRemove a document by `id`:\n\n* `/api/filetree/removeDocByID`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210902210113-0avi12f\"\n  }\n  ```\n\n    * `id`: Document ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Move documents\n\n* `/api/filetree/moveDocs`\n* Parameters\n\n  ```json\n  {\n    \"fromPaths\": [\"/20210917220056-yxtyl7i.sy\"],\n    \"toNotebook\": \"20210817205410-2kvfpfn\",\n    \"toPath\": \"/\"\n  }\n  ```\n\n    * `fromPaths`: Source paths\n    * `toNotebook`: Target notebook ID\n    * `toPath`: Target path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nMove documents by `id`:\n\n* `/api/filetree/moveDocsByID`\n* Parameters\n\n  ```json\n  {\n    \"fromIDs\": [\"20210917220056-yxtyl7i\"],\n    \"toID\": \"20210817205410-2kvfpfn\"\n  }\n  ```\n\n    * `fromIDs`: Source docs' IDs\n    * `toID`: Target parent doc's ID or notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Reorder documents relative to a sibling\n\n* `/api/filetree/reorderDocs`\n* Parameters\n\n  ```json\n  {\n    \"sourceIDs\": [\"20210917220056-yxtyl7i\"],\n    \"targetID\": \"20210917220057-abcdefg\",\n    \"position\": \"before\"\n  }\n  ```\n\n    * `sourceIDs`: Source document IDs inserted in array order\n    * `targetID`: Target sibling document ID\n    * `position`: `before` or `after`\n    * After moving, every source document must have the same notebook and parent as the target; sorting uses the complete sibling list, including hidden and unlisted documents\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"changed\": true,\n      \"notebook\": \"20210817205410-2kvfpfn\",\n      \"parentPath\": \"/\"\n    }\n  }\n  ```\n\n### Reorder notebooks relative to another notebook\n\n* `/api/notebook/reorder`\n* Parameters\n\n  ```json\n  {\n    \"sourceIDs\": [\"20210817205410-2kvfpfn\"],\n    \"targetID\": \"20210817205411-abcdefg\",\n    \"position\": \"after\"\n  }\n  ```\n\n    * `sourceIDs`: Source notebook IDs inserted in array order\n    * `targetID`: Target notebook ID\n    * `position`: `before` or `after`\n    * Sorting uses the complete notebook list, including closed notebooks\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"changed\": true\n    }\n  }\n  ```\n\n### Set notebook and document sort values\n\n* `/api/filetree/setSort`\n* Parameters\n\n  ```json\n  {\n    \"notebookSorts\": [\n      {\n        \"id\": \"20210817205410-2kvfpfn\",\n        \"sort\": -10\n      }\n    ],\n    \"docSorts\": [\n      {\n        \"id\": \"20210917220056-yxtyl7i\",\n        \"sort\": -8\n      }\n    ]\n  }\n  ```\n\n    * `notebookSorts`: Notebook IDs and their sort values, optional\n    * `docSorts`: Document IDs and their sort values, optional\n    * Documents in `docSorts` must belong to opened and unlocked notebooks; notebook root document IDs are not accepted\n    * At least one of `notebookSorts` and `docSorts` must be non-empty. Array order does not affect sorting; each `sort` value is stored directly\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"notebookIDs\": [\"20210817205410-2kvfpfn\"],\n      \"docIDs\": [\"20210917220056-yxtyl7i\"]\n    }\n  }\n  ```\n\n### Set a document's child document sort mode\n\n* `/api/filetree/setDocSortMode`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210917220056-yxtyl7i\",\n    \"sortMode\": 4\n  }\n  ```\n\n    * `id`: ID of the regular document whose child documents use this sort mode; notebook root document IDs are not accepted\n    * `sortMode`: Integer from `0` through `14`; `null` clears the document's explicit setting and inherits the nearest parent document, notebook, or global document tree sort rule, in that order\n    * Values: `0`/`1` file name ascending/descending; `2`/`3` update time ascending/descending; `4`/`5` natural file name ascending/descending; `6` custom; `7`/`8` reference count ascending/descending; `9`/`10` creation time ascending/descending; `11`/`12` size ascending/descending; `13`/`14` child document count ascending/descending\n    * The declared sort mode is inherited by deeper descendants until another document declares its own sort mode\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"box\": \"20210817205410-2kvfpfn\",\n      \"id\": \"20210917220056-yxtyl7i\",\n      \"path\": \"/20210917220056-yxtyl7i.sy\",\n      \"sortMode\": 4,\n      \"effectiveSortMode\": 4\n    }\n  }\n  ```\n\n    * `sortMode` is the explicit setting (`null` when inheriting), while `effectiveSortMode` is the actual sort mode after inheritance is resolved\n\n### Get human-readable path based on path\n\n* `/api/filetree/getHPathByPath`\n* Parameters\n\n  ```json\n  {\n    \"notebook\": \"20210831090520-7dvbdv0\",\n    \"path\": \"/20210917220500-sz588nq/20210917220056-yxtyl7i.sy\"\n  }\n  ```\n\n    * `notebook`: Notebook ID\n    * `path`: Document path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"/foo/bar\"\n  }\n  ```\n\n### Get human-readable path based on ID\n\n* `/api/filetree/getHPathByID`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210917220056-yxtyl7i\"\n  }\n  ```\n\n    * `id`: Block ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"/foo/bar\"\n  }\n  ```\n\n### Get storage path based on ID\n\n* `/api/filetree/getPathByID`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210808180320-fqgskfj\"\n  }\n  ```\n\n    * `id`: Block ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n    \"notebook\": \"20210808180117-czj9bvb\",\n    \"path\": \"/20200812220555-lj3enxa/20210808180320-fqgskfj.sy\"\n    }\n  }\n  ```\n\n### Get IDs based on human-readable path\n\n* `/api/filetree/getIDsByHPath`\n* Parameters\n\n  ```json\n  {\n    \"path\": \"/foo/bar\",\n    \"notebook\": \"20210808180117-czj9bvb\"\n  }\n  ```\n\n    * `path`: Human-readable path\n    * `notebook`: Notebook ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n        \"20200813004931-q4cu8na\"\n    ]\n  }\n  ```\n\n## Assets\n\n### Upload assets\n\n* `/api/asset/upload`\n* The parameter is an HTTP Multipart form\n\n    * `assetsDirPath`: The folder path where assets are stored, with the data folder as the root path, for example:\n        * `\"/assets/\"`: workspace/data/assets/ folder\n        * `\"/assets/sub/\"`: workspace/data/assets/sub/ folder\n\n      Under normal circumstances, it is recommended to use the first method, which is stored in the assets folder of the\n      workspace, since putting in a subdirectory has some side effects, please refer to the assets chapter of the user guide.\n    * `file[]`: Uploaded file list\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"disk full\",\n    \"data\": {\n      \"errFiles\": [\"bar.png\"],\n      \"failedFiles\": [\n        {\n          \"index\": 1,\n          \"name\": \"bar.png\",\n          \"error\": \"disk full\"\n        }\n      ],\n      \"succFiles\": [\n        {\n          \"index\": 0,\n          \"name\": \"foo.png\",\n          \"path\": \"assets/foo-20210719092549-9j5y79r.png\"\n        }\n      ],\n      \"succMap\": {\n        \"foo.png\": \"assets/foo-20210719092549-9j5y79r.png\"\n      }\n    }\n  }\n  ```\n\n    * `errFiles`: List of filenames with errors in upload processing\n    * `failedFiles`: Files explicitly reported as failed. `index` is the file's index in `file[]`, `name` is its upload filename, and `error` is the failure message. This field may omit files that were not attempted or not reported individually; use `succFiles` when each input item must be identified unambiguously\n    * `succFiles`: Successfully processed files in input order. `index` is the file's index in `file[]`, `name` is its upload filename, and `path` is the uploaded asset path. Use this field when a batch can contain duplicate filenames\n    * `succMap`: Compatibility mapping for existing callers. The key is the upload filename and the value is assets/foo-id.png. However, when a batch contains duplicate filenames, only the last item with a given key remains in this map\n\n## Blocks\n\n### Insert blocks\n\n* `/api/block/insertBlock`\n* Parameters\n\n  ```json\n  {\n    \"dataType\": \"markdown\",\n    \"data\": \"foo**bar**{: style=\\\"color: var(--b3-font-color8);\\\"}baz\",\n    \"nextID\": \"\",\n    \"previousID\": \"20211229114650-vrek5x6\",\n    \"parentID\": \"\"\n  }\n  ```\n\n    * `dataType`: The data type to be inserted, the value can be `markdown` or `dom`\n    * `data`: Data to be inserted\n    * `nextID`: The ID of the next block, used to anchor the insertion position\n    * `previousID`: The ID of the previous block, used to anchor the insertion position\n    * `parentID`: The ID of the parent block, used to anchor the insertion position\n\n  `nextID`, `previousID`, and `parentID` must have at least one value, using priority: `nextID` > `previousID` >\n  `parentID`\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"insert\",\n            \"data\": \"<div data-node-id=\\\"20211230115020-g02dfx0\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo<strong style=\\\"color: var(--b3-font-color8);\\\">bar</strong>baz</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\"></div></div>\",\n            \"id\": \"20211230115020-g02dfx0\",\n            \"parentID\": \"\",\n            \"previousID\": \"20211229114650-vrek5x6\",\n            \"retData\": null\n          }\n        ],\n        \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n    * `action.data`: DOM generated by the newly inserted block\n    * `action.id`: ID of the first inserted top-level block. When inserting multiple top-level blocks, send one block per request if each ID is needed\n\n### Prepend blocks\n\n* `/api/block/prependBlock`\n* Parameters\n\n  ```json\n  {\n    \"data\": \"foo**bar**{: style=\\\"color: var(--b3-font-color8);\\\"}baz\",\n    \"dataType\": \"markdown\",\n    \"parentID\": \"20220107173950-7f9m1nb\"\n  }\n  ```\n\n    * `dataType`: The data type to be inserted, the value can be `markdown` or `dom`\n    * `data`: Data to be inserted\n    * `parentID`: The ID of the parent block, used to anchor the insertion position\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"insert\",\n            \"data\": \"<div data-node-id=\\\"20220108003710-hm0x9sc\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo<strong style=\\\"color: var(--b3-font-color8);\\\">bar</strong>baz</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\"></div></div>\",\n            \"id\": \"20220108003710-hm0x9sc\",\n            \"parentID\": \"20220107173950-7f9m1nb\",\n            \"previousID\": \"\",\n            \"retData\": null\n          }\n        ],\n        \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n    * `action.data`: DOM generated by the newly inserted block\n    * `action.id`: ID of the newly inserted block\n\n### Append blocks\n\n* `/api/block/appendBlock`\n* Parameters\n\n  ```json\n  {\n    \"data\": \"foo**bar**{: style=\\\"color: var(--b3-font-color8);\\\"}baz\",\n    \"dataType\": \"markdown\",\n    \"parentID\": \"20220107173950-7f9m1nb\"\n  }\n  ```\n\n    * `dataType`: The data type to be inserted, the value can be `markdown` or `dom`\n    * `data`: Data to be inserted\n    * `parentID`: The ID of the parent block, used to anchor the insertion position\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"insert\",\n            \"data\": \"<div data-node-id=\\\"20220108003642-y2wmpcv\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo<strong style=\\\"color: var(--b3-font-color8);\\\">bar</strong>baz</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\"></div></div>\",\n            \"id\": \"20220108003642-y2wmpcv\",\n            \"parentID\": \"20220107173950-7f9m1nb\",\n            \"previousID\": \"20220108003615-7rk41t1\",\n            \"retData\": null\n          }\n        ],\n        \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n    * `action.data`: DOM generated by the newly inserted block\n    * `action.id`: ID of the first appended top-level block. When appending multiple top-level blocks, send one block per request if each ID is needed\n\n### Update a block\n\n* `/api/block/updateBlock`\n* Parameters\n\n  ```json\n  {\n    \"dataType\": \"markdown\",\n    \"data\": \"foobarbaz\",\n    \"id\": \"20211230161520-querkps\",\n    \"lockType\": false\n  }\n  ```\n\n    * `dataType`: The data type to be updated, the value can be `markdown` or `dom`\n    * `data`: Data to be updated\n    * `id`: ID of the block to be updated\n    * `lockType`: Whether to reject the update when the parsed block type differs from the existing block type; invalid parent-child structures are always rejected, while an empty paragraph can be converted to any valid block type; defaults to `false`\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"update\",\n            \"data\": \"<div data-node-id=\\\"20211230161520-querkps\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo<strong>bar</strong>baz</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\"></div></div>\",\n            \"id\": \"20211230161520-querkps\",\n            \"parentID\": \"\",\n            \"previousID\": \"\",\n            \"retData\": null\n            }\n          ],\n        \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n    * `action.data`: DOM generated by the updated block\n\n### Delete a block\n\n* `/api/block/deleteBlock`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20211230161520-querkps\"\n  }\n  ```\n\n    * `id`: ID of the block to be deleted\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"doOperations\": [\n          {\n            \"action\": \"delete\",\n            \"data\": null,\n            \"id\": \"20211230162439-vtm09qo\",\n            \"parentID\": \"\",\n            \"previousID\": \"\",\n            \"retData\": null\n          }\n        ],\n       \"undoOperations\": null\n      }\n    ]\n  }\n  ```\n\n### Move a block\n\n* `/api/block/moveBlock`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20230406180530-3o1rqkc\",\n    \"previousID\": \"20230406152734-if5kyx6\",\n    \"parentID\": \"20230404183855-woe52ko\"\n  }\n  ```\n\n    * `id`: Block ID to move\n    * `previousID`: The ID of the previous block, used to anchor the insertion position\n    * `parentID`: The ID of the parent block, used to anchor the insertion position, `previousID` and `parentID` cannot\n      be empty at the same time, if they exist at the same time, `previousID` will be used first\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n        {\n            \"doOperations\": [\n                {\n                    \"action\": \"move\",\n                    \"data\": null,\n                    \"id\": \"20230406180530-3o1rqkc\",\n                    \"parentID\": \"20230404183855-woe52ko\",\n                    \"previousID\": \"20230406152734-if5kyx6\",\n                    \"nextID\": \"\",\n                    \"retData\": null,\n                    \"srcIDs\": null,\n                    \"name\": \"\",\n                    \"type\": \"\"\n                }\n            ],\n            \"undoOperations\": null\n        }\n    ]\n  }\n  ```\n\n### Fold a block\n\n* `/api/block/foldBlock`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20231224160424-2f5680o\"\n  }\n  ```\n\n    * `id`: Block ID to fold\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Unfold a block\n\n* `/api/block/unfoldBlock`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20231224160424-2f5680o\"\n  }\n  ```\n\n    * `id`: Block ID to unfold\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Get a block kramdown\n\n* `/api/block/getBlockKramdown`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20201225220954-dlgzk1o\"\n  }\n  ```\n\n    * `id`: ID of the block to be got\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"id\": \"20201225220954-dlgzk1o\",\n      \"kramdown\": \"* {: id=\\\"20201225220954-e913snx\\\"}Create a new notebook, create a new document under the notebook\\n  {: id=\\\"20210131161940-kfs31q6\\\"}\\n* {: id=\\\"20201225220954-ygz217h\\\"}Enter <kbd>/</kbd> in the editor to trigger the function menu\\n  {: id=\\\"20210131161940-eo0riwq\\\"}\\n* {: id=\\\"20201225220954-875yybt\\\"}((20200924101200-gss5vee \\\"Navigate in the content block\\\")) and ((20200924100906-0u4zfq3 \\\"Window and tab\\\"))\\n  {: id=\\\"20210131161940-b5uow2h\\\"}\"\n    }\n  }\n  ```\n\n* Determinism: The returned Kramdown canonicalizes block-level IAL attribute ordering; the order remains stable while the block content and attributes are unchanged\n\n### Get child blocks\n\n* `/api/block/getChildBlocks`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20230506212712-vt9ajwj\"\n  }\n  ```\n\n    * `id`: Parent block ID\n    * The blocks below a heading are also counted as child blocks\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"id\": \"20230512083858-mjdwkbn\",\n        \"type\": \"h\",\n        \"subType\": \"h1\"\n      },\n      {\n        \"id\": \"20230513213727-thswvfd\",\n        \"type\": \"s\"\n      },\n      {\n        \"id\": \"20230513213633-9lsj4ew\",\n        \"type\": \"l\",\n        \"subType\": \"u\"\n      }\n    ]\n  }\n  ```\n\n### Transfer block ref\n\n* `/api/block/transferBlockRef`\n* Parameters\n\n  ```json\n  {\n    \"fromID\": \"20230612160235-mv6rrh1\",\n    \"toID\": \"20230613093045-uwcomng\",\n    \"refIDs\": [\"20230613092230-cpyimmd\"]\n  }\n  ```\n\n    * `fromID`: Def block ID\n    * `toID`: Target block ID\n    * `refIDs`: Ref block IDs point to def block ID, optional, if not specified, all ref block IDs will be transferred\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n## Attributes\n\n### Set block attributes\n\n* `/api/attr/setBlockAttrs`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210912214605-uhi5gco\",\n    \"attrs\": {\n      \"custom-attr1\": \"line1\\nline2\"\n    }\n  }\n  ```\n\n    * `id`: Block ID\n    * `attrs`: Block attributes, custom attributes must be prefixed with `custom-`\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Get block attributes\n\n* `/api/attr/getBlockAttrs`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20210912214605-uhi5gco\"\n  }\n  ```\n\n    * `id`: Block ID\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"custom-attr1\": \"line1\\nline2\",\n      \"id\": \"20210912214605-uhi5gco\",\n      \"title\": \"PDF Annotation Demo\",\n      \"type\": \"doc\",\n      \"updated\": \"20210916120715\"\n    }\n  }\n  ```\n\n## SQL\n\n### Execute SQL query\n\n* `/api/query/sql`\n* Parameters\n\n  ```json\n  {\n    \"stmt\": \"SELECT * FROM blocks WHERE content LIKE'%content%' LIMIT 7\"\n  }\n  ```\n\n    * `stmt`: SQL statement\n\nWithout an explicit outer `LIMIT`, results default to at most `search.limit` rows (the configured search result limit). Use explicit `LIMIT` and `OFFSET` clauses to paginate, with a stable, unique ordering such as `ORDER BY hpath, id`. However, an explicit outer `LIMIT` overrides the default, including values larger than `search.limit`.\n\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      { \"col\": \"val\" }\n    ],\n    \"limit\": 0,\n    \"truncated\": false\n  }\n  ```\n\nOn success, `data` remains an array. `limit` is the server default limit applied to this query, or `0` when the SQL supplies an explicit outer `LIMIT`; it is not the value of that explicit clause. `truncated` is `true` only when the server default limit omitted at least one result row. Exactly meeting the limit does not imply truncation. For the example above, `LIMIT 7` is explicit, so `limit` is `0` and `truncated` is `false`. These fields are omitted on errors.\n\nNote: To ensure data security, access to this interface is prohibited in Publish Mode.\n\n### Flush transaction\n\n* `/api/sqlite/flushTransaction`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\nBlock-writing APIs can return after their block-tree transaction is committed but before asynchronous SQL indexing has caught up. If a subsequent `/api/query/sql` call must observe that write, call `POST /api/sqlite/flushTransaction` after the write and wait for `code: 0` before querying SQL. For example, `getBlockKramdown` reads the block tree and can show newly written content before the SQL index does. Kramdown, DOM, and SQL columns are different representations and are not normalized plain text.\n\n## Templates\n\n### Render a template\n\n* `/api/template/render`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20220724223548-j6g0o87\",\n    \"path\": \"F:\\\\SiYuan\\\\data\\\\templates\\\\foo.md\",\n    \"mode\": \"editorInsert\"\n  }\n  ```\n\n    * `id`: The ID of the document where the rendering is called\n    * `path`: Template file absolute path\n    * `mode`: Optional rendering mode. Currently supports `\"preview\"` and `\"editorInsert\"` only. Preview mode produces a document tree plan without writing files; editor-insert mode produces a plan that can be confirmed and applied with the corresponding editor transaction\n    * When `mode` is omitted, the legacy `preview` Boolean parameter remains supported: `preview: true` is equivalent to `mode: \"preview\"`; otherwise the template is rendered as ordinary content and `createDocTree` is disabled\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"content\": \"<div data-node-id=\\\"20220729234848-dlgsah7\\\" data-node-index=\\\"1\\\" data-type=\\\"NodeParagraph\\\" class=\\\"p\\\" updated=\\\"20220729234840\\\"><div contenteditable=\\\"true\\\" spellcheck=\\\"false\\\">foo</div><div class=\\\"protyle-attr\\\" contenteditable=\\\"false\\\">​</div></div>\",\n      \"path\": \"F:\\\\SiYuan\\\\data\\\\templates\\\\foo.md\",\n      \"docTreePlan\": {\n        \"id\": \"template-plan-token\",\n        \"count\": 2,\n        \"nodes\": [\n          {\n            \"id\": \"20260830150000-abc1234\",\n            \"title\": \"Materials\",\n            \"parentID\": \"20220724223548-j6g0o87\",\n            \"hPath\": \"/Parent/Materials\",\n            \"depth\": 1\n          },\n          {\n            \"id\": \"20260830150001-def5678\",\n            \"title\": \"Review\",\n            \"parentID\": \"20260830150000-abc1234\",\n            \"hPath\": \"/Parent/Materials/Review\",\n            \"depth\": 2\n          }\n        ]\n      }\n    }\n  }\n  ```\n\n    * `docTreePlan`: Present when the template declares a child document tree with `createDocTree`\n        * `id`: Empty in preview mode, which never writes files. In editor-insert mode, this is a short-lived, one-time plan token; after confirmation, submit it as the top-level `templateDocTreePlanID` field of the corresponding transaction object\n        * `count`: Total number of child documents in the plan\n        * `nodes`: Static descriptions of the planned documents\n            * `id`: Planned document ID\n            * `title`: Planned document title\n            * `parentID`: Planned parent document ID\n            * `hPath`: Planned human-readable document path\n            * `depth`: Depth relative to the document where the template is inserted\n        * A single plan can contain at most 128 documents, and its declared child document tree can be at most 16 levels deep. The resulting absolute file tree depth remains subject to the setting that controls whether sub-documents deeper than 7 levels may be created\n\n### Save a document as a template\n\n* `/api/template/docSaveAsTemplate`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"20220724223548-j6g0o87\",\n    \"name\": \"Project\",\n    \"overwrite\": false,\n    \"databaseMode\": \"copy\"\n  }\n  ```\n\n    * `id`: Source document ID\n    * `name`: Template name. The kernel sanitizes the name and adds the `.md` extension\n    * `overwrite`: Whether to replace an existing template with the same name. When `false` and the template exists, the response `code` is `1`\n    * `databaseMode`: Optional handling for all database blocks in the document. `copy` (the default) creates independent databases whenever the template is used and clears their block-level context filters; `reference` keeps the existing database IDs and context filters, so rendered blocks are mirrors that share data and view settings with the source databases. Rendering a reference-mode template fails if a source database is unavailable in the target document's encryption boundary\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Render Sprig\n\n* `/api/template/renderSprig`\n* Parameters\n\n  ```json\n  {\n    \"template\": \"/daily note/{{now | date \\\"2006/01\\\"}}/{{now | date \\\"2006-01-02\\\"}}\"\n  }\n  ```\n    * `template`: template content\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"/daily note/2023/03/2023-03-24\"\n  }\n  ```\n\n## File\n\n### Get file\n\n* `/api/file/getFile`\n* Parameters\n\n  ``json {\n  \"path\": \"/data/20210808180117-6v0mkxr/20200923234011-ieuun1p.sy\"\n  }\n  ``\n    * `path`: the file path under the workspace path\n* Return value\n\n    * Response status code `200`: File content\n    * Response status code `202`: Exception information\n\n      ```json\n      {\n        \"code\": 404,\n        \"msg\": \"\",\n        \"data\": null\n      }\n      ```\n\n        * `code`: non-zero for exceptions\n\n            * `-1`: Parameter parsing error\n            * `403`: Permission denied (file is not in the workspace)\n            * `404`: Not Found (file doesn't exist)\n            * `405`: Method Not Allowed (it's a directory)\n            * `500`: Server Error (stat file failed / read file failed)\n        * `msg`: a piece of text describing the error\n\n### Put file\n\n* `/api/file/putFile`\n* The parameter is an HTTP Multipart form\n\n    * `path`: the file path under the workspace path\n    * `isDir`: whether to create a folder, when `true` only create a folder, ignore `file`\n    * `modTime`: last access and modification time, Unix time\n    * `file`: the uploaded file\n* Return value\n\n   ```json\n   {\n     \"code\": 0,\n     \"msg\": \"\",\n     \"data\": null\n   }\n   ```\n\n### Remove file\n\n* `/api/file/removeFile`\n* Parameters\n\n  ```json\n  {\n    \"path\": \"/data/20210808180117-6v0mkxr/20200923234011-ieuun1p.sy\"\n  }\n  ```\n    * `path`: the file path under the workspace path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### Rename file\n\n* `/api/file/renameFile`\n* Parameters\n\n  ```json\n  {\n    \"path\": \"/data/assets/image-20230523085812-k3o9t32.png\",\n    \"newPath\": \"/data/assets/test-20230523085812-k3o9t32.png\"\n  }\n  ```\n    * `path`: the file path under the workspace path\n    * `newPath`: the new file path under the workspace path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": null\n  }\n  ```\n\n### List files\n\n* `/api/file/readDir`\n* Parameters\n\n  ```json\n  {\n    \"path\": \"/data/20210808180117-6v0mkxr/20200923234011-ieuun1p\"\n  }\n  ```\n    * `path`: the dir path under the workspace path\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": [\n      {\n        \"isDir\": true,\n        \"isSymlink\": false,\n        \"name\": \"20210808180303-6yi0dv5\",\n        \"updated\": 1691467624\n      },\n      {\n        \"isDir\": false,\n        \"isSymlink\": false,\n        \"name\": \"20210808180303-6yi0dv5.sy\",\n        \"updated\": 1663298365\n      }\n    ]\n  }\n  ```\n\n## Export\n\n### Export Markdown\n\n* `/api/export/exportMdContent`\n* Parameters\n\n  ```json\n  {\n    \"id\": \"\"\n  }\n  ```\n\n    * `id`: ID of the doc block to export\n    * `preserveTaskMarkers`: Optional boolean for Markdown copying; `true` retains task markers such as `[/]` and `[-]`. Omitted, `null`, or `false` keeps standard Markdown normalization to `[X]` for nonstandard markers\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"hPath\": \"/Please Start Here\",\n      \"content\": \"## 🍫 Content Block\\n\\nIn SiYuan, the only important core concept is...\"\n    }\n  }\n  ```\n\n    * `hPath`: human-readable path\n    * `content`: Markdown content, including automatic heading numbers when enabled for the document (the document setting overrides the editor default); uses the editor numbering format and preserves source-document numbers for partial exports\n\n### Export files and folders\n\n* `/api/export/exportResources`\n* Parameters\n\n  ```json\n  {\n    \"paths\": [\n      \"/conf/appearance/boot\",\n      \"/conf/appearance/langs\",\n      \"/conf/appearance/emojis/conf.json\",\n      \"/conf/appearance/icons/index.html\"\n    ],\n    \"name\": \"zip-file-name\"\n  }\n  ```\n\n    * `paths`: A list of file or folder paths to be exported, the same filename/folder name will be overwritten\n    * `name`: (Optional) The exported file name, which defaults to `export-YYYY-MM-DD_hh-mm-ss.zip` when not set\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"path\": \"temp/export/zip-file-name.zip\"\n    }\n  }\n  ```\n\n    * `path`: The path of `*.zip` file created\n        * The directory structure in `zip-file-name.zip` is as follows:\n            * `zip-file-name`\n                * `boot`\n                * `langs`\n                * `conf.json`\n                * `index.html`\n\n## Conversion\n\n### Pandoc\n\n* `/api/convert/pandoc`\n* Working directory\n    * Executing the pandoc command will set the working directory to `workspace/temp/convert/pandoc/${dir}`\n    * API [`Put file`](#put-file) can be used to write the file to be converted to this directory first\n    * Then call the API for conversion, and the converted file will also be written to this directory\n    * Finally, call the API [`Get file`](#get-file) to get the converted file\n        * Or call the API [Create a document with Markdown](#Create-a-document-with-Markdown)\n        * Or call the internal API `importStdMd` to import the converted folder directly\n* Parameters\n\n  ```json\n  {\n    \"dir\": \"test\",\n    \"args\": [\n      \"--to\", \"markdown_strict-raw_html\",\n      \"foo.epub\",\n      \"-o\", \"foo.md\"\n   ]\n  }\n  ```\n\n    * `args`: Pandoc command line parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n       \"path\": \"/temp/convert/pandoc/test\"\n    }\n  }\n  ```\n    * `path`: the path under the workspace\n\n## Notification\n\n### Push message\n\n* `/api/notification/pushMsg`\n* Parameters\n\n  ```json\n  {\n    \"msg\": \"test\",\n    \"timeout\": 7000\n  }\n  ```\n    * `timeout`: The duration of the message display in milliseconds. This field can be omitted, the default is 7000\n      milliseconds\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n        \"id\": \"62jtmqi\"\n    }\n  }\n  ```\n    * `id`: Message ID\n\n### Push error message\n\n* `/api/notification/pushErrMsg`\n* Parameters\n\n  ```json\n  {\n    \"msg\": \"test\",\n    \"timeout\": 7000\n  }\n  ```\n    * `timeout`: The duration of the message display in milliseconds. This field can be omitted, the default is 7000\n      milliseconds\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n        \"id\": \"qc9znut\"\n    }\n  }\n  ```\n    * `id`: Message ID\n\n## Network\n\n### Forward proxy\n\n#### JSON forward proxy\n\n* `/api/network/forwardProxy`\n* Parameters\n\n  ```json\n  {\n    \"url\": \"https://b3log.org/siyuan/\",\n    \"method\": \"GET\",\n    \"timeout\": 7000,\n    \"contentType\": \"text/html\",\n    \"headers\": [\n        {\n            \"Cookie\": \"\"\n        }\n    ],\n    \"redirect\": true,\n    \"payload\": {},\n    \"payloadEncoding\": \"json\",\n    \"responseEncoding\": \"text\"\n  }\n  ```\n\n    * `url`: URL to forward\n    * `method`: HTTP method, default is `POST`\n    * `timeout`: timeout in milliseconds, default is `7000`\n    * `contentType`: Content-Type, default is `application/json`\n    * `headers`: HTTP request header array; each key-value pair in the objects is set as a request header\n    * `redirect`: Whether to follow redirects, default is `true`, following up to 2 redirects; set it to `false` to disable redirects\n    * `payload`: HTTP payload, object or string\n    * `payloadEncoding`: The encoding scheme used by `payload`, default is `json`; `json` sends `payload` directly, and binary payloads can use the following encoded strings\n\n        * `json`\n        * `base64` | `base64-std`\n        * `base64-url`\n        * `base32` | `base32-std`\n        * `base32-hex`\n        * `hex`\n    * `responseEncoding`: The encoding scheme used by `body` in response data, default is `text`, optional values are as follows\n\n        * `text`\n        * `base64` | `base64-std`\n        * `base64-url`\n        * `base32` | `base32-std`\n        * `base32-hex`\n        * `hex`\n\n      `text` preserves the existing behavior and converts the character set to UTF-8 when applicable. The binary encodings encode the response body before character-set conversion; existing HTTP content decoding behavior, such as gzip decompression, is unchanged.\n\n      The response body is limited to 32 MiB after HTTP content decoding. If the limit is exceeded, the API returns error code `10` without a partial body. Therefore, use `/api/network/proxy` for large files or streaming responses.\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"body\": \"\",\n      \"bodyEncoding\": \"text\",\n      \"contentType\": \"text/html\",\n      \"elapsed\": 1976,\n      \"headers\": {\n      },\n      \"status\": 200,\n      \"url\": \"https://b3log.org/siyuan/\"\n    }\n  }\n  ```\n\n    * `body`: Response body\n    * `bodyEncoding`: The encoding scheme used by `body`; it is consistent with the `responseEncoding` field in the request, default is `text`, optional values are as follows\n\n        * `text`\n        * `base64` | `base64-std`\n        * `base64-url`\n        * `base32` | `base32-std`\n        * `base32-hex`\n        * `hex`\n    * `contentType`: Response header `Content-Type`\n    * `elapsed`: Request duration in milliseconds\n    * `headers`: Response headers returned by the target service\n    * `status`: HTTP status code returned by the target service\n    * `url`: Forwarded URL\n\n#### HTTP forward proxy\n\n* `/api/network/proxy`\n* Request method: any HTTP method\n* Query parameters\n\n    * `u`: Required, target `http` or `https` URL encoded with Go `base64.RawURLEncoding`, which is URL-safe Base64 without `=` padding\n    * `h`: Optional, request header JSON encoded in the same way; the JSON type is `map[string][]string`, for example `{\"Authorization\":[\"Bearer token\"]}`\n    * `t`: Optional, connection timeout in Go `time.ParseDuration` format, for example `30s` or `1500ms`\n* Request body: Forwards the current request body as-is, and forwards the current request's full `Content-Type` header to the target request\n* Return value: Directly returns the target service HTTP status code and response body without wrapping them in `code`, `msg`, or `data`; target response headers are returned with the `Siyuan-Proxy-` prefix, for example `Content-Type` is returned as `Siyuan-Proxy-Content-Type`\n\n#### WebSocket forward proxy\n\n* `/ws/network/proxy`\n* Request method: `GET`\n* Query parameters\n\n    * `u`: Required, target `ws` or `wss` URL encoded with Go `base64.RawURLEncoding`\n    * `h`: Optional, handshake request header JSON encoded in the same way; the JSON type is `map[string][]string`\n    * `t`: Optional, handshake timeout in Go `time.ParseDuration` format, for example `30s` or `1500ms`\n* Return value: Upgrades to WebSocket and then forwards messages bidirectionally; target handshake response headers are returned with the `Siyuan-Proxy-` prefix\n\n#### EventSource forward proxy\n\n* `/es/network/proxy`\n* Request method: `GET`\n* Query parameters\n\n    * `u`: Required, target `http` or `https` URL encoded with Go `base64.RawURLEncoding`\n    * `h`: Optional, request header JSON encoded in the same way; the JSON type is `map[string][]string`\n    * `t`: Optional, connection timeout in Go `time.ParseDuration` format, for example `30s` or `1500ms`\n* Return value: Directly streams the target service HTTP status code and response body without wrapping them in `code`, `msg`, or `data`; if the request headers do not include `Accept`, `text/event-stream` is used automatically; target response headers are returned with the `Siyuan-Proxy-` prefix\n\n## System\n\n### Get boot progress\n\n* `/api/system/bootProgress`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": {\n      \"details\": \"Finishing boot...\",\n      \"progress\": 100\n    }\n  }\n  ```\n\n### Get system version\n\n* `/api/system/version`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": \"1.3.5\"\n  }\n  ```\n\n### Get the current time of the system\n\n* `/api/system/currentTime`\n* No parameters\n* Return value\n\n  ```json\n  {\n    \"code\": 0,\n    \"msg\": \"\",\n    \"data\": 1631850968131\n  }\n  ```\n\n    * `data`: Precision in milliseconds\n\n## Database\n\nA database (internally an \"attribute view\") stores structured data as fields (columns) and items (rows). Each database is identified by an `avID` and can be embedded into a document through one or more database blocks (`blockID`). A single database may contain multiple views (`viewID`) of different layout types: `table`, `list`, `gallery`, and `kanban`\n\n<!-- File: kernel/util/builtin_skills/siyuan-plugin-development/references/development.md (Tokens: ~1019 | Category: anthropic-skill) -->\n# Development References and Boundaries\n\n## Official References\n\n- Plugin sample: https://github.com/siyuan-note/plugin-sample\n- Plugin API declarations: https://github.com/siyuan-note/petal\n- Implementation of the target SiYuan version: https://github.com/siyuan-note/siyuan\n\nVerify the currently running version and target frontend, then use the public APIs for that version. The host loads the frontend plugin entry point index.js; CommonJS uses require(\"siyuan\") and module.exports. Do not assume the host exports an API simply because its type exists in an npm package. Take target frontend values from the current manifest specification instead of blindly reusing an all-platform array.\n\n## Minimum Files and Builds\n\nA minimal frontend package requires plugin.json and a real index.js, together with any i18n, README, index.css, and other resources actually used. The manifest's name, version, minimum host version, and frontend declarations must comply with the current native installer's parsing rules. Publishing metadata requires a real identity; do not invent an author or repository for a local prototype.\n\nexamples/index.js and examples/en_US.json are original, small, dependency-free examples that demonstrate only public entry points and lifecycle handling. They are not a completed plugin for the user; do not treat example text as user requirements. Verify addTopBar, showMessage, and language-file keys against the target version. Register layout-related entry points in onLayoutReady and avoid duplicate registration.\n\nTypeScript produces an installable index.js only through an actually available build toolchain; changing a .ts extension is not a build. package_local only freezes and packages files; it does not build or execute arbitrary project scripts. If build capabilities are unavailable, accurately deliver the source code and blocker details, or switch to plain JavaScript with the user's agreement.\n\n## Imports and Reconfirmation\n\nBefore importing, inspect the candidate source with project_status(taskId, sourcePath, sourceFiles). sourceFiles is an exact allowlist of relative file paths and must match the plan's workflow.files; sourceRevision binds the selected existing safe files, not the entire repository. The host only lists excluded paths without reading contents such as .git, node_modules, or config. An optional path on the allowlist that does not yet exist may be created later; do not treat it as an imported file.\n\nAfter reconfirming a plan for an existing managed project, obtain the current sourceRevision through project_status(taskId), then establish a new checkpoint with prepare_project(taskId, expectedSourceRevision). Do not overwrite the current source code or reimport the previous source. If revisions do not match, first inspect the changes read-only, then handle their actual effects.\n\nPackaging freezes only existing safe files on the confirmed allowlist. Missing optional files may be omitted from the package, but plugin.json, index.js, and resources declared by the manifest must be complete. Do not use optional allowlist entries to bypass entry-point or resource validation.\n\n## Data and Lifecycle\n\nRead and modify notes through public kernel APIs, and check code before reporting success. Do not directly modify .sy files, databases, or the actual data directory. Define the target and scope of effects clearly, access encrypted notebooks under existing permissions, and do not bypass locks or denials.\n\nPrevent duplicate clicks during long-running writes, and retain request and instance state. In onunload, clean up the plugin's own events, timers, and observers, and cancel reads that can be canceled; after an asynchronous response, check that the instance is still valid. Canceling a request or unloading does not guarantee that a server-side write has not already executed. If the result is unknown, verify it read-only first.\n\n## Installation\n\nGenerating a local ZIP does not grant permission to install or activate it. Install through the native local-package interface, bound to the package hash and expected installation-target revision; keep the immutable installation snapshot separate from development source code. Explain the effects of overwriting an enabled plugin before obtaining authorization for an action that may execute code. If the user approves only file delivery, stop at source code and artifacts.\n\nRecover installation data through the existing repo snapshot and recovery tools, respecting the user's sync-ignore rules. Snapshots cover non-ignored code under data/plugins and data under data/storage/petal; they do not include the workspace conf directory or managed-project source code. Managed source code is still restored through restore_project. The agent creates a snapshot before the first local write in each conversation round, rather than creating a separate recovery point for every installation; the installer does not keep a separate plugin-code backup either. A full-repository checkout rolls back all data included in the snapshot, disables sync, and reloads the interface. Single-file recovery overwrites only the selected files and does not delete files added later. Restoring petals.json affects the entire plugin configuration. Verify snapshot contents and recovery scope first, follow the existing tool confirmation requirements, do not automatically check out the entire repository, and do not promise to stop plugin code that is already running.\n\n<!-- File: kernel/util/builtin_skills/siyuan-plugin-development/references/verification.md (Tokens: ~407 | Category: anthropic-skill) -->\n# Verification Record\n\nReport only actual evidence for each item: passed, failed, unverified, or not applicable. Completed source code and ZIP files do not replace runtime evidence.\n\n- Source code: implementation matches the confirmed plan, frontend and resource declarations are accurate, and there are no unapproved data effects\n- Static checks: syntax, type, or lint checks actually run and their results; mark as unverified if no parser is available\n- Build: the actual TypeScript command, exit status, and artifacts; not applicable for plain JavaScript\n- ZIP: package path, hash, source revision, file list, and validation results returned by package_local\n- Simulated tests: identify SDK substitutes, DOM environments, or standalone Electron environments; do not describe them as an actual SiYuan instance\n- Installation: authorized target, package hash, revisions before and after installation, and actual native installation results\n- Activation: actual configuration state; successful installation does not imply activation\n- Frontend loading: check entry points and errors separately in desktop Electron and the desktop browser; neither substitutes for the other\n- Functional execution: main operation, error feedback, repeated clicks, unloading after partial initialization, and late asynchronous responses\n- Data writes: target, count, and actual returned status; verify unknown results read-only first to avoid duplicate submissions\n\nIf an actual SiYuan instance, target frontend, or necessary permission is unavailable, explicitly leave the corresponding stages unverified. Do not compensate for a missing test environment by installing into the user's actual environment. Rolling back code does not roll back user data.\n\nRecovery is not guaranteed to handle every process crash automatically. If a temporary file is only partially written, or a conditional create has published the target but has not yet cleaned up the temporary hard link, the tool preserves both names, their bytes, and the logs, and returns result_unknown; manual inspection may be necessary. Do not delete files whose state is uncertain, weaken hard-link checks, or blindly retry to claim that recovery is complete.\n\n<!-- File: kernel/util/builtin_skills/siyuan-plugin-development/SKILL.md (Tokens: ~1917 | Category: anthropic-skill) -->\n---\nname: siyuan-plugin-development\ndescription: Official frontend plugin development workflow. After the user explicitly opts in, clarify missing requirements, confirm a short plan, implement in a managed copy, verify, and package. Installation and activation require separate authorization.\n---\n\n# SiYuan Frontend Plugin Development\n\nThis is a read-only official skill shipped with SiYuan. Its current scope is creating and modifying frontend plugins, using CommonJS JavaScript without third-party runtime dependencies by default. Full development, verification, and recovery of kernel.js are outside this workflow's scope.\n\nReplace example placeholders such as ${PLUGIN_NAME} according to the confirmed plan. Do not treat user-defined variable settings as official content.\n\n## Obtain an Explicit Choice, Then Confirm the Plan\n\n- Use this skill only when the user explicitly asks to develop or modify a plugin. Do not automatically turn ordinary template, document, or theme requests into plugins\n- Initiate the official choice with workflow.action=\"choose\" in the question tool; questions may be an empty array. The server displays a fixed, localized yes/no question. Do not treat ordinary questions, chat text, the model's own consent, or the user's previous answers as permission for this task\n- After the user chooses yes, load the body through skill with action=\"load\", source=\"builtin\", and name=\"siyuan-plugin-development\". Continue only after the server confirms successful loading. Do not read the official body if the user declines or cancels, or if the skill is disabled; user-requested custom development may still proceed under its original permissions\n- Extract known requirements from the current conversation, and ask only about gaps that would change the implementation: functionality, entry points, triggers, target frontend, data-read and data-write effects, and deliverables. Do not repeat questions already answered, guess a notebook, or expand the scope to every platform\n- Submit a short plan through question with workflow.action=\"plan\", bound to the taskId returned by the choice. The full plan must clearly state the plugin name, frontend, functionality and entry points, data changes, delivery scope, import source, and files allowed in the package. The server freezes and displays the entire plan; do not write source code before actual acceptance is confirmed\n- Update the plan and obtain new confirmation when changing a confirmed goal, source-code origin, data effects, or packaging scope. Do not fabricate a taskId, plan digest, or confirmed state; previous questions and versions do not authorize a new task\n\nExample choice call: {\"questions\":[],\"workflow\":{\"action\":\"choose\"}}. Use newTask=true only when the user explicitly requests another plugin; reconsider=true may be used when the user explicitly changes the current workflow choice. Ordinary repeated calls reuse the current choice without asking again.\n\nPlan fields: taskId, proposal (at most 2000 characters), packageName, frontend (one of desktop, desktop-window, browser-desktop, mobile, or browser-mobile), dataEffects and deliverables (at most 1000 characters each), and files (at most 200 exact relative file paths). Before importing an existing plugin, call bazaar's project_status with taskId, sourcePath, and exact sourceFiles; sourceFiles must match the workflow.files awaiting confirmation. The host returns a sourceRevision for the selected existing safe files; include that digest and sourcePath in the plan. Do not implicitly read or hash the entire repository. List excluded paths such as .git, node_modules, and config without reading their contents. Omit source fields when creating a new project. The technical file list is also shown to the user; do not add unapproved files after confirmation. The allowlist may contain optional files that do not yet exist, but this does not permit omitting the entry point, manifest, or declared resources.\n\n## Verify Tools and APIs First\n\nRead references/development.md first. Read every resource through skill with source=\"builtin\" and name=\"siyuan-plugin-development/relative-path\". Check the tools currently available and the target SiYuan version; consult the official plugin-sample, petal, and source code for that version as needed. Do not treat type declarations, outdated README versions, or unreleased APIs as evidence of support in the current host.\n\nUse CommonJS JavaScript by default. Use TypeScript only when the required dependencies and build tools actually exist and are authorized for use; source code is not equivalent to a runnable index.js. Native agents are not guaranteed to have a shell, dependency-installation tools, or arbitrary code execution. If a tool is missing, report the exact blocked step. Do not invent execution results or bypass a tool denial. If the user specifies TypeScript, confirm an alternative approach before switching to JavaScript.\n\n## Implement in a Managed Copy\n\n1. After plan confirmation, call bazaar's prepare_project with taskId so the host creates a source-code copy and baseline checkpoint; do not specify an absolute write directory yourself. For imports, first perform a read-only inspection of the exact sourceFiles, then import a copy after approval of the source and the selected files' baseline digest; leave the original directory unchanged. After reconfirming a plan for an existing managed project, obtain the current source revision with project_status(taskId), then call prepare_project(taskId, expectedSourceRevision) to establish a new checkpoint. This preserves the current source code without reimporting or overwriting it\n2. Use file.write with ifAbsent=true for new files. Before editing existing text, use file.read with withMetadata=true; continue reading with the same expectedRevision and nextOffsetByte until you have fully read the original content that needs changing. truncated=true does not represent a complete file\n3. Prefer file.edit for partial changes, providing expectedRevision and unique, nonoverlapping oldText/newText replacements. Use file.write with expectedRevision only when full replacement is necessary, and only after reading the entire file at that same revision. On a conflict, reread and verify first. Do not blindly retry writes or fall back to overwriting the entire file when a match is missing or ambiguous\n4. Use the paths and project revisions returned by the host; verify the current source code, plan, and checkpoint through bazaar's project_status. Stop writing if a checkpoint cannot be established, and report partial failures accurately. When recovery is needed, use restore_project and respect the current revision and tool confirmation requirements\n5. Do not bypass conditional writes for managed source code through copy, unzip, directory renaming, or HTTP file APIs. Do not touch host-controlled, backup, or artifact directories, or directly overwrite the actual plugin installation directory\n\nCanceling the workflow does not automatically roll back changes. When cancellation, an unknown write result, or partial preparation prevents the ordinary plan from continuing, request recovery-only authorization through question with questions=[], workflow.action=\"recover\", and taskId. The server displays and binds the most recently actually approved plan and its digest.\nAfter confirmation, you may call restore_project, or retry prepare_project only for the same frozen, interrupted preparation. Recovery does not roll back notes, runtime data, or other configuration, and does not authorize further source-code changes; subsequent ordinary source changes still require normal plan confirmation.\n\nUse the minimal entry-point pattern in examples/index.js as a reference and adapt it to the actual functionality. Create entry points through the public Plugin API and modify notes through kernel APIs; never use fs or Electron to modify data files directly. Put UI text in i18n. Handle return codes, missing targets, closed notebooks, repeated clicks, and late responses. onunload must handle partial initialization and clean up the plugin's own listeners, timers, observers, and requests; unloading cannot undo write requests that have already been sent.\n\n## Verify, Package, and Deliver\n\nRead references/verification.md first. Declare only frontends that are actually implemented and verified; do not declare kernels without kernel.js. Do not copy the example's author, repository, funding information, or data permissions, or invent a publishing identity for a local prototype.\n\nRequest a deterministic local ZIP through bazaar's package_local with taskId and the latest expectedSourceRevision. The host freezes only existing safe files on the allowlist. Optional paths may be absent, but plugin.json, index.js, and resources declared by the manifest must exist. It does not execute project scripts or download dependencies. If the entry point or declared resources are missing, or a secret file is detected, first correct the source code or reconfirm the scope. Retain the returned packageHash, sourceRevision, and validation results. Successful packaging does not mean JavaScript syntax, actual installation, frontend loading, or functional execution has passed verification.\n\nReport separate results and evidence for source code, static checks, builds, ZIP packaging, simulated tests, actual installation, activation configuration, frontend loading, and functional execution. Mark builds for plain JavaScript as \"not applicable\"; mark steps that were not run as \"unverified\" and explain why. Tests using SDK substitutes or a standalone Electron DOM environment are not SiYuan integration tests; verify desktop Electron and the desktop browser separately.\n\nInstallation and activation each require the user's explicit authorization for the corresponding action and target. Overwriting an enabled plugin may execute code immediately; a disable notification does not confirm unloading in every frontend, so do not claim it automatically guarantees execution has stopped. Once authorized, use only the native installation interface, bound to the package digest and target revision returned by the host; do not install through generic file writes. Rolling back code does not roll back the user's notes or configuration.\n</yakaai_skills>"}