{
  "openapi": "3.1.0",
  "info": {
    "title": "Clip Notes API",
    "version": "1.2.0",
    "description": "供 AI 读取与调用的 Clip 笔记与 Geometry 画板规范。API 当前无鉴权；创建、修改和删除会直接写入公开 Clip 数据，其中删除不可恢复。执行写操作前应先读取目标笔记并确认用户意图。note_type 为 md 时，正文遵循下方 x-clip-markdown 中定义的 Clip Markdown 方言。"
  },
  "x-clip-markdown": {
    "purpose": "供 AI 生成可直接在 Clip 阅读器中渲染的 md 笔记。写入时将 note_type 设为 md，正文放入 text。",
    "base_markdown": [
      "支持 CommonMark 风格的标题、段落、引用、无序和有序列表、链接、图片、围栏代码块，以及 GFM 风格表格。",
      "普通换行会保留为阅读器中的换行；块级内容前后保留一个空行。",
      "不要用反引号包裹数学内容：反引号只用于代码。"
    ],
    "math": {
      "renderer": "KaTeX",
      "inline": ["$E=mc^2$", "\\(\\theta\\)"],
      "display": ["$$\\n\\boxed{D=\\sqrt{x^2+y^2}}\\n$$", "\\[\\n\\frac{a}{b}\\n\\]"],
      "authoring_rule": "优先使用成对且独占行的 $$ 作为块级公式；所有定界符必须成对，公式中命令使用单个反斜杠。"
    },
    "tikz": {
      "fence": "```tikz",
      "body_rule": "围栏内容必须是完整的 \\begin{tikzpicture}...\\end{tikzpicture}；Clip 会提供 documentclass、TikZ 和中文环境，因此不要写 \\documentclass、\\usepackage、\\begin{document} 或 \\end{document}。",
      "safety_rule": "禁止使用 \\input、\\include、读写文件、shell escape 或其他外部输入输出命令。TikZ 源最大 48 KiB，编译超时约 12 秒。",
      "example": "```tikz\\n\\begin{tikzpicture}\\n  \\draw (0,0) -- (2,1);\\n\\end{tikzpicture}\\n```"
    },
    "geometry_import": {
      "purpose": "将 Geometry 多个画板组成一篇 Clip 笔记时的唯一导入流程。",
      "workflow": [
        "先调用 GET /clip/api/geometry/boards 取得已提交画板列表及其 ID。",
        "对每个选定 ID 调用 GET /clip/api/geometry/boards/{id}/tikz。",
        "将返回的 tikz 字符串作为完整 Markdown 代码块原样插入目标笔记对应章节；不得重新生成、格式化、拆分、删除图内标签或将图内公式迁移到正文。",
        "只在代码块外添加 Markdown 标题、段落和章节衔接；PATCH 目标笔记时保留其 title、note_type 与用户未要求变动的内容。"
      ],
      "precondition": "画板必须先在 Geometry 中点击“提交”。接口返回的 TikZ 是该次提交时由 Geometry 原生导出的缓存。"
    },
    "images": {
      "scale": "![说明](https://example.com/image.png){%50}",
      "inline_row": "![图 A](https://example.com/a.png){%45 inline}\\n![图 B](https://example.com/b.png){%45 inline}",
      "rule": "缩放百分比为 1 到 100；连续的 inline 图片会排列成可换行的一行。"
    },
    "ai_checklist": [
      "写入前使用 GET 读取并保留目标笔记的 title、note_type 与不应改动的章节。",
      "数学使用 $...$ 或独占行 $$...$$，不要写成 `\\angle ABC`。",
      "TikZ 单独放在 tikz 围栏中，不与 Markdown 列表或公式定界符混排。",
      "PATCH 需要提交完整 text；修改既有笔记时先读取后再写回。"
    ]
  },
  "x-geometry-canvas": {
    "purpose": "供人和 AI 读取、修改 Geometry 画板，并由服务端导出、编译验证 TikZ。完整机器可读能力以 GET /clip/api/geometry/capabilities 为准。",
    "coordinate_system": "画布 1200×760，原点 (600,380)，x 向右、y 向下；TikZ 1 单位为 50 px。",
    "elements": ["point", "segment", "line", "ray", "path(line/cubic/arc)", "function-curve(native plot)", "polyline", "polygon", "rectangle", "ellipse", "circle(no fill)", "fill-circle(no stroke)", "node", "label", "angle"],
    "operations": ["multi-select", "move", "copy", "mirror-x", "mirror-y", "trim", "extend", "layer", "group", "ungroup", "delete"],
    "display": ["lineColor", "strokeColor", "fillColor", "textColor", "lineWidth", "stroke", "dashArray", "opacity", "fill", "fillOpacity", "lineCap", "lineJoin", "arrowStart", "arrowEnd", "arrowStartType", "arrowEndType", "arrowSizePt", "layer"],
    "ai_write_workflow": ["GET capabilities", "GET 最新 board", "保留未知字段与 {coord:n}/{shape:n} 引用", "PATCH 完整 board.workspace 并传 if_updated_at", "提示用户在 Geometry 点击拉取", "用户检查后点击提交以重新生成 TikZ"],
    "warning": "PATCH 画板会使已提交 TikZ 缓存失效；在 Geometry 重新提交之前，/tikz 返回 409，避免读取陈旧图像。"
  },
  "x-tikz-cache": {
    "key": "SHA-256(编译模板版本 + 完整 TikZ 源码)",
    "reuse": "相同源码自动返回持久 SVG 缓存；缓存跨请求、worker 和服务重启复用。",
    "refresh": "POST /clip/api/tikz/render 时传 refresh=true 强制重编译；失败保留旧缓存。源码变化会自动产生新 key，无需手动失效。",
    "headers": ["ETag", "X-TikZ-Cache: HIT|MISS|REFRESH|NOT_MODIFIED", "X-TikZ-Cache-Key"],
    "limits": "最多保留 512 个 SVG 或 128 MiB，按最近访问时间清理。"
  },
  "servers": [
    { "url": "https://vinesea.uk", "description": "公网 HTTPS" },
    { "url": "http://vine.sea", "description": "家庭网络 HTTP" }
  ],
  "paths": {
    "/clip/api/tikz/render": {
      "post": {
        "operationId": "renderTikzSvg",
        "summary": "编译或复用 TikZ SVG",
        "description": "相同源码按内容哈希复用持久缓存。源码变化自动换 key；refresh=true 强制重编译。响应头返回缓存状态、ETag 和可复用 key。",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TikzRenderWrite" } } } },
        "responses": {
          "200": { "description": "SVG；读取 ETag、X-TikZ-Cache 与 X-TikZ-Cache-Key", "content": { "image/svg+xml": { "schema": { "type": "string" } } } },
          "304": { "description": "If-None-Match 与当前内容 key 一致" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "422": { "description": "TikZ 校验或编译失败；已有缓存不变" }
        }
      }
    },
    "/clip/api/tikz/cache/{cache_key}": {
      "parameters": [
        { "name": "cache_key", "in": "path", "required": true, "description": "render 响应的 X-TikZ-Cache-Key", "schema": { "type": "string", "pattern": "^[0-9a-f]{64}$" } }
      ],
      "get": {
        "operationId": "getCachedTikzSvg",
        "summary": "按内容 key 直接复用已编译 SVG",
        "responses": {
          "200": { "description": "不可变 SVG 缓存", "content": { "image/svg+xml": { "schema": { "type": "string" } } } },
          "304": { "description": "If-None-Match 命中" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "description": "缓存不存在或已被 LRU 清理" }
        }
      }
    },
    "/clip/api/notes": {
      "get": {
        "operationId": "listNotes",
        "summary": "列出全部笔记",
        "responses": {
          "200": {
            "description": "笔记列表",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NoteListResponse" } } }
          }
        }
      },
      "post": {
        "operationId": "createNote",
        "summary": "创建笔记",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NoteWrite" } } } },
        "responses": {
          "201": { "description": "创建成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NoteResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/clip/api/notes/search": {
      "get": {
        "operationId": "searchNotesByTitle",
        "summary": "按名称模糊搜索笔记",
        "description": "只扫描名称并返回轻量摘要，不渲染正文、Markdown 或 TikZ。结果按精确匹配、前缀匹配、包含匹配排序。",
        "parameters": [
          { "name": "q", "in": "query", "required": true, "description": "名称查询文本；忽略首尾、重复空白与大小写", "schema": { "type": "string", "minLength": 1 } },
          { "name": "limit", "in": "query", "description": "最多返回的候选数", "schema": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 } }
        ],
        "responses": {
          "200": { "description": "匹配的笔记摘要", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NoteSearchResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/clip/api/notes/resolve": {
      "get": {
        "operationId": "resolveNoteByTitle",
        "summary": "用完整名称直接定位笔记",
        "description": "唯一匹配时直接返回笔记 ID、元数据与原始正文，不生成 html。无匹配返回 404；重名返回 409 和候选摘要，调用方必须使用候选 ID 消歧。",
        "parameters": [
          { "name": "title", "in": "query", "required": true, "description": "完整笔记名称；忽略首尾、重复空白与大小写", "schema": { "type": "string", "minLength": 1 } }
        ],
        "responses": {
          "200": { "description": "唯一匹配的笔记及原始正文", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NoteResolveResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "description": "没有同名笔记", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "409": { "description": "存在多个同名笔记；响应 matches 返回候选摘要", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NoteResolveConflict" } } } }
        }
      }
    },
    "/clip/api/notes/merge/prepare": {
      "post": {
        "operationId": "prepareNoteMerge",
        "summary": "批量读取待整理笔记及版本指纹",
        "description": "notes 可混用笔记 ID 与精确名称。一次返回全部原始正文和 SHA-256 revision，不渲染 Markdown 或 TikZ。AI 应基于这些正文生成整理后的完整 text。",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NoteMergePrepareWrite" } } } },
        "responses": {
          "200": { "description": "待合并笔记原文与版本", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NoteMergePrepareResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "description": "精确名称不唯一", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/clip/api/notes/merge": {
      "post": {
        "operationId": "commitNoteMerge",
        "summary": "校验版本后提交整理结果",
        "description": "以 prepare 返回的 revision 校验全部来源未变化，再更新 target_id。只有 delete_sources=true 才删除目标以外的来源。响应只含原文和元数据，不触发整篇预览编译。",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NoteMergeWrite" } } } },
        "responses": {
          "200": { "description": "合并成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NoteMergeResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "description": "至少一篇来源已变化，必须重新 prepare", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/clip/api/notes/{item_id}": {
      "parameters": [
        { "name": "item_id", "in": "path", "required": true, "description": "笔记 ID", "schema": { "type": "string" } }
      ],
      "get": {
        "operationId": "getNote",
        "summary": "读取一条笔记",
        "responses": {
          "200": { "description": "笔记", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NoteResponse" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "patch": {
        "operationId": "updateNote",
        "summary": "更新笔记正文或类型",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NoteWrite" } } } },
        "responses": {
          "200": { "description": "更新成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NoteResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "operationId": "deleteNote",
        "summary": "删除一条笔记（不可恢复）",
        "responses": {
          "200": { "description": "删除成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Success" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/clip/api/notes/{item_id}/tikz/{tikz_index}": {
      "parameters": [
        { "name": "item_id", "in": "path", "required": true, "description": "笔记 ID", "schema": { "type": "string" } },
        { "name": "tikz_index", "in": "path", "required": true, "description": "笔记中从 0 开始的 tikz 围栏序号", "schema": { "type": "integer", "minimum": 0 } }
      ],
      "get": {
        "operationId": "getNoteTikzBlock",
        "summary": "定点读取一条笔记的 TikZ 图块",
        "description": "只返回指定 ```tikz 围栏内部的原始 source，不返回整篇笔记。",
        "responses": {
          "200": { "description": "TikZ 图块", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TikzBlockResponse" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "patch": {
        "operationId": "updateNoteTikzBlock",
        "summary": "定点替换一条笔记的 TikZ 图块",
        "description": "请求体只传 source。服务端只编译并替换指定围栏，保留该笔记其余 Markdown、title 和 note_type；响应 render 给出该图块的缓存状态与复用地址。编译失败不会修改笔记。",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TikzBlockWrite" } } } },
        "responses": {
          "200": { "description": "替换成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TikzBlockResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": { "description": "目标 TikZ 图块编译失败，笔记保持不变", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/clip/api/notes/{item_id}/tikz/recompile": {
      "parameters": [
        { "name": "item_id", "in": "path", "required": true, "description": "笔记 ID", "schema": { "type": "string" } }
      ],
      "post": {
        "operationId": "recompileNoteTikzBlocks",
        "summary": "强制重编译笔记中的全部 TikZ 图块",
        "description": "逐块绕过缓存重编译，并返回含最新内嵌 SVG 的完整 note.html。渲染时会移除旧 Geometry 空裁剪域和固定全画布 bounding box，但不改写笔记原始 TikZ source。",
        "responses": {
          "200": { "description": "全部图块重编译成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TikzRecompileResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "description": "编译期间笔记内容发生变化", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "422": { "description": "其中一个 TikZ 图块编译失败", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/clip/api/geometry/capabilities": {
      "get": {
        "operationId": "getGeometryCapabilities",
        "summary": "获取 Geometry 基础图元、操作和显示字段能力",
        "description": "AI 修改画板前应先读取此接口；返回坐标引用编码、全部可编辑基础元素、操作语义、显示属性和安全工作流。",
        "responses": { "200": { "description": "Geometry 能力注册表", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GeometryCapabilitiesResponse" } } } } }
      }
    },
    "/clip/api/geometry/boards": {
      "get": {
        "operationId": "listGeometryBoards",
        "summary": "列出已提交 Geometry 画板",
        "responses": { "200": { "description": "画板列表", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GeometryBoardListResponse" } } } } }
      }
    },
    "/clip/api/geometry/boards/{board_id}": {
      "parameters": [{ "name": "board_id", "in": "path", "required": true, "description": "画板 ID", "schema": { "type": "string" } }],
      "get": {
        "operationId": "getGeometryBoard",
        "summary": "获取指定画板及其工作区数据",
        "responses": {
          "200": { "description": "画板", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GeometryBoardResponse" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "patch": {
        "operationId": "updateGeometryBoard",
        "summary": "由 AI 回写完整 Geometry 工作区",
        "description": "必须先 GET 最新画板并提交完整 workspace。设置 export_tikz=true 时，服务端生成并编译验证 TikZ，成功后原子保存并在响应中返回完整围栏；否则删除旧 tikz 缓存。",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GeometryBoardWrite" } } } },
        "responses": {
          "200": { "description": "画板草稿已更新", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GeometryBoardWriteResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "description": "if_updated_at 已过期，必须重新 GET 后修改" },
          "422": { "description": "结构化图元无法导出或生成的 TikZ 未通过编译；画板保持不变" }
        }
      }
    },
    "/clip/api/geometry/boards/{board_id}/tikz": {
      "parameters": [{ "name": "board_id", "in": "path", "required": true, "description": "画板 ID", "schema": { "type": "string" } }],
      "get": {
        "operationId": "getGeometryBoardTikz",
        "summary": "获取画板原始 TikZ 代码块",
        "description": "返回完整且未改写的 ```tikz 代码块；合并笔记时必须原样插入 Markdown。",
        "responses": {
          "200": { "description": "原始 TikZ", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GeometryTikzResponse" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "description": "画板尚未提交原始 TikZ", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Note": {
        "type": "object",
        "required": ["id", "kind", "title", "note_type", "text", "html", "size", "created_at"],
        "properties": {
          "id": { "type": "string" },
          "kind": { "type": "string", "enum": ["note", "text"] },
          "title": { "type": "string" },
          "note_type": { "type": "string", "enum": ["md", "cpp", "html"], "default": "md" },
          "text": { "type": "string", "description": "原始笔记正文" },
          "html": { "type": "string", "description": "服务端渲染的安全 HTML" },
          "size": { "type": "integer" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "NoteSummary": {
        "type": "object",
        "required": ["id", "title", "note_type", "size", "created_at"],
        "properties": {
          "id": { "type": "string" },
          "title": { "type": "string" },
          "note_type": { "type": "string", "enum": ["md", "cpp", "html"] },
          "size": { "type": "integer" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "NoteSource": {
        "allOf": [
          { "$ref": "#/components/schemas/NoteSummary" },
          { "type": "object", "required": ["text"], "properties": { "text": { "type": "string", "description": "未经渲染的完整原始正文" } } }
        ]
      },
      "NoteSearchResponse": {
        "allOf": [
          { "$ref": "#/components/schemas/Success" },
          { "type": "object", "required": ["query", "count", "notes"], "properties": { "query": { "type": "string" }, "count": { "type": "integer", "minimum": 0, "description": "截断前的匹配总数" }, "notes": { "type": "array", "items": { "$ref": "#/components/schemas/NoteSummary" } } } }
        ]
      },
      "NoteResolveResponse": {
        "allOf": [
          { "$ref": "#/components/schemas/Success" },
          { "type": "object", "required": ["note"], "properties": { "note": { "$ref": "#/components/schemas/NoteSource" } } }
        ]
      },
      "NoteResolveConflict": {
        "allOf": [
          { "$ref": "#/components/schemas/Error" },
          { "type": "object", "required": ["title", "matches"], "properties": { "title": { "type": "string" }, "matches": { "type": "array", "minItems": 2, "items": { "$ref": "#/components/schemas/NoteSummary" } } } }
        ]
      },
      "NoteWrite": {
        "type": "object",
        "required": ["text"],
        "properties": {
          "text": { "type": "string", "minLength": 1, "description": "非空笔记正文" },
          "note_type": { "type": "string", "enum": ["md", "cpp", "html"], "default": "md" }
        }
      },
      "NoteRevisionSource": {
        "allOf": [
          { "$ref": "#/components/schemas/NoteSource" },
          { "type": "object", "required": ["revision"], "properties": { "revision": { "type": "string", "pattern": "^[0-9a-f]{64}$" } } }
        ]
      },
      "NoteMergePrepareWrite": {
        "type": "object",
        "required": ["notes"],
        "properties": { "notes": { "type": "array", "minItems": 2, "maxItems": 20, "uniqueItems": true, "items": { "type": "string", "description": "笔记 ID 或精确名称" } } }
      },
      "NoteMergePrepareResponse": {
        "allOf": [
          { "$ref": "#/components/schemas/Success" },
          { "type": "object", "required": ["notes", "suggested_target_id"], "properties": { "notes": { "type": "array", "items": { "$ref": "#/components/schemas/NoteRevisionSource" } }, "suggested_target_id": { "type": "string", "description": "按请求顺序建议最后一篇为目标；调用方仍须明确提交 target_id" } } }
        ]
      },
      "NoteMergeWrite": {
        "type": "object",
        "required": ["target_id", "source_revisions", "text"],
        "properties": {
          "target_id": { "type": "string" },
          "source_revisions": { "type": "object", "minProperties": 2, "maxProperties": 20, "additionalProperties": { "type": "string", "pattern": "^[0-9a-f]{64}$" } },
          "title": { "type": "string", "maxLength": 160 },
          "note_type": { "type": "string", "enum": ["md", "cpp", "html"], "description": "若提供，必须与目标现有类型相同" },
          "text": { "type": "string", "minLength": 1, "description": "AI 整理后的完整目标正文" },
          "delete_sources": { "type": "boolean", "default": false, "description": "明确为 true 时删除 target_id 以外的来源笔记" }
        }
      },
      "NoteMergeResponse": {
        "allOf": [
          { "$ref": "#/components/schemas/Success" },
          { "type": "object", "required": ["note", "merged_source_ids", "deleted_source_ids"], "properties": { "note": { "$ref": "#/components/schemas/NoteRevisionSource" }, "merged_source_ids": { "type": "array", "items": { "type": "string" } }, "deleted_source_ids": { "type": "array", "items": { "type": "string" } } } }
        ]
      },
      "TikzRenderWrite": {
        "type": "object",
        "required": ["source"],
        "properties": {
          "source": { "type": "string", "minLength": 1, "description": "完整 tikzpicture 源码，不含 Markdown 围栏" },
          "refresh": { "type": "boolean", "default": false, "description": "为 true 时绕过现有缓存强制重编译；失败不会覆盖旧缓存" }
        }
      },
      "TikzBlockWrite": {
        "type": "object",
        "required": ["source"],
        "properties": {
          "source": { "type": "string", "minLength": 1, "description": "完整 tikzpicture 源码，不含 ```tikz 围栏" },
          "refresh": { "type": "boolean", "default": false, "description": "为 true 时即使源码未变也强制重编译目标图块" }
        }
      },
      "TikzBlockResponse": {
        "allOf": [ { "$ref": "#/components/schemas/Success" }, { "type": "object", "required": ["note_id", "tikz_block_index", "source", "title", "note_type"], "properties": { "note_id": { "type": "string" }, "tikz_block_index": { "type": "integer", "minimum": 0 }, "source": { "type": "string" }, "title": { "type": "string" }, "note_type": { "type": "string", "enum": ["md", "cpp", "html"] }, "render": { "$ref": "#/components/schemas/TikzBlockRender", "description": "PATCH 成功时返回；GET 不触发编译，因而省略。" } } } ]
      },
      "TikzBlockRender": {
        "type": "object",
        "required": ["cache_key", "cache_status", "etag", "svg_url"],
        "properties": {
          "cache_key": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
          "cache_status": { "type": "string", "enum": ["HIT", "MISS"] },
          "etag": { "type": "string" },
          "svg_url": { "type": "string", "description": "可直接读取此已编译 SVG 的站内路径" }
        }
      },
      "TikzRecompileResponse": {
        "allOf": [
          { "$ref": "#/components/schemas/Success" },
          {
            "type": "object",
            "required": ["note_id", "compiled", "renders", "note"],
            "properties": {
              "note_id": { "type": "string" },
              "compiled": { "type": "integer", "minimum": 1 },
              "renders": { "type": "array", "items": { "allOf": [ { "$ref": "#/components/schemas/TikzBlockRender" }, { "type": "object", "required": ["tikz_block_index"], "properties": { "tikz_block_index": { "type": "integer", "minimum": 0 } } } ] } },
              "note": { "$ref": "#/components/schemas/Note" }
            }
          }
        ]
      },
      "GeometryBoardSummary": {
        "type": "object",
        "required": ["id", "name"],
        "properties": { "id": { "type": "string" }, "name": { "type": "string" } }
      },
      "GeometryBoard": {
        "type": "object",
        "required": ["id", "name", "workspace"],
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "workspace": { "type": "object", "description": "Geometry 序列化工作区；按坐标与图元引用编码。" },
          "tikz": { "type": "string", "description": "提交时由 Geometry 原生导出的完整代码块；可能缺失于旧提交。" }
        }
      },
      "GeometryBoardListResponse": { "allOf": [ { "$ref": "#/components/schemas/Success" }, { "type": "object", "required": ["boards"], "properties": { "active_id": { "type": ["string", "null"] }, "updated_at": { "type": ["string", "null"], "format": "date-time" }, "boards": { "type": "array", "items": { "$ref": "#/components/schemas/GeometryBoardSummary" } } } } ] },
      "GeometryBoardResponse": { "allOf": [ { "$ref": "#/components/schemas/Success" }, { "type": "object", "required": ["board"], "properties": { "board": { "$ref": "#/components/schemas/GeometryBoard" }, "updated_at": { "type": ["string", "null"], "format": "date-time" } } } ] },
      "GeometryBoardWrite": { "type": "object", "required": ["workspace"], "properties": { "workspace": { "type": "object", "required": ["coords", "points", "controls", "shapes"], "properties": { "coords": { "type": "array", "items": { "type": "object" } }, "points": { "type": "array", "items": { "type": "integer" } }, "controls": { "type": "array", "items": { "type": "integer" } }, "shapes": { "type": "array", "items": { "type": "object" } } }, "additionalProperties": true }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "if_updated_at": { "type": "string", "format": "date-time", "description": "推荐传入最近一次 GET 返回的 updated_at；不一致时返回 409，避免覆盖他人修改。" }, "export_tikz": { "type": "boolean", "default": false, "description": "为 true 时服务端从 workspace 生成并编译验证 TikZ，在响应中直接返回完整代码围栏。" } } },
      "GeometryBoardWriteResponse": { "allOf": [ { "$ref": "#/components/schemas/GeometryBoardResponse" }, { "type": "object", "required": ["tikz_invalidated"], "properties": { "tikz_invalidated": { "type": "boolean", "description": "未请求服务端导出时为 true；成功导出时为 false。" }, "tikz": { "type": "string", "description": "export_tikz=true 且导出成功时返回完整 ```tikz 代码块。" } } } ] },
      "GeometryCapabilitiesResponse": { "allOf": [ { "$ref": "#/components/schemas/Success" }, { "type": "object", "required": ["version", "coordinate_system", "workspace_encoding", "elements", "operations", "display", "ai_workflow"], "properties": { "version": { "type": "string" }, "coordinate_system": { "type": "object" }, "workspace_encoding": { "type": "object" }, "elements": { "type": "object" }, "operations": { "type": "object" }, "display": { "type": "object" }, "ai_workflow": { "type": "array", "items": { "type": "string" } } } } ] },
      "GeometryTikzResponse": { "allOf": [ { "$ref": "#/components/schemas/Success" }, { "type": "object", "required": ["id", "name", "tikz"], "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "tikz": { "type": "string", "description": "完整 ```tikz 代码块，调用方必须逐字保留。" }, "updated_at": { "type": ["string", "null"], "format": "date-time" } } } ] },
      "Success": { "type": "object", "required": ["ok"], "properties": { "ok": { "type": "boolean", "const": true } } },
      "NoteResponse": { "allOf": [ { "$ref": "#/components/schemas/Success" }, { "type": "object", "required": ["note"], "properties": { "note": { "$ref": "#/components/schemas/Note" } } } ] },
      "NoteListResponse": { "allOf": [ { "$ref": "#/components/schemas/Success" }, { "type": "object", "required": ["notes"], "properties": { "notes": { "type": "array", "items": { "$ref": "#/components/schemas/Note" } } } } ] },
      "Error": { "type": "object", "required": ["ok", "error"], "properties": { "ok": { "type": "boolean", "const": false }, "error": { "type": "string" } } }
    },
    "responses": {
      "BadRequest": { "description": "请求正文或笔记类型不合法", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "笔记不存在或不是笔记类型", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    }
  }
}
