claude-obsidian Canvas 规范详解:Obsidian JSON Canvas 的节点、边、坐标系统与实战避坑指南
【免费下载链接】claude-obsidianSelf-organizing AI second brain for Obsidian + Claude Code. Drop any source and Claude reads, links, and files it into one connected knowledge graph of plain Markdown you own. AI note-taking, personal knowledge management (PKM), and an open-source Notion alternative. Based on Karpathy's LLM Wiki pattern.项目地址: https://gitcode.com/GitHub_Trending/cl/claude-obsidian
本文围绕 claude-obsidian 仓库中的 Canvas JSON 规范参考 展开,系统讲解.canvas文件的双键结构、四类节点与边的字段语义、坐标系统和配色约定,并进一步结合 Canvas 技能定义、事务模块 和 事务测试 说明该规范如何被工程化地执行:从 ID 生成策略、图像尺寸计算到写作用法的路径与写入范围约束。读完后你可以直接手写或程序化生成合法的 canvas 文件,并理解该仓库为什么要求 canvas 变更必须走可恢复的事务通道。
文件格式与 ID 约定
Canvas 文件本质是 UTF-8 编码的 JSON 文件,扩展名为.canvas,只有两个顶层键:
nodes:节点数组;edges:边(连接关系)数组。
该结构对齐 JSON Canvas 1.0 开放规范。所有结构体都支持任意附加字段([key: string]: any)以保持前向兼容——Obsidian 在读写 canvas 文件时会原样保留未知字段。这一点对自动化工具很关键:解析和重写 canvas 时不应丢弃自己不认识的字段,否则会破坏用户在 Obsidian 中保存的视图状态等私有数据。
关于 ID 格式,规范中有明确的产品约定,值得单独强调:
- JSON Canvas 1.0 只要求每个节点和边的 ID 是唯一字符串,不规定长度或字符集;
- 该技能的新变更(mutation)偏好随机 16 位小写十六进制 ID(例如
"a1b2c3d4e5f67890"),并校验其未被占用; - 规范文档较长示例里出现的描述性 ID(如
title-0001、zone-logos)只是可读性标签,不是生成规则。
为什么强调随机 ID?因为规范在"常见错误"一节把ID 冲突列为首要风险:生成新 ID 前必须先读取文件中已存在的全部 ID。随机 16 位 hex 的空间足够大,把碰撞概率降到可忽略水平,同时"校验未使用"这一步是强制的。
坐标系
规范用一张 ASCII 图定义了坐标系统:
x increases → ┌───────────────────────────────── │ (-920, -2400) (0, -2400) │ y │ (-920, 0) (0, 0) ← origin ↓ │ │ (-920, 540) (500, 540)四条核心规则:
- 原点 (0, 0) 是画布视口的中心;
- x 向右增大,负 x 表示位于中心左侧;
- y 向下增大,负 y 表示位于中心上方——注意这与多数数学坐标系相反;
- 节点的
x/y是节点左上角的坐标,不是中心点; - Obsidian 首次打开画布时会自动平移视图以适配全部节点,所以负坐标完全合法,只是"更靠左/更上"而已。
规范在常见错误一节专门提醒:y: -2400在y: -1000上方(越负越靠上)。这是布局脚本中最容易写反的一维——如果你按"屏幕坐标系 y 向上"的直觉摆放节点,整版布局会上下颠倒。
四类节点
Text 节点
把 markdown 内容渲染为带样式的卡片:
{ "id": "text-title-4821", "type": "text", "text": "# Heading\n\nParagraph with **bold** and `code`.", "x": -400, "y": -300, "width": 400, "height": 120, "color": "6" }要点:
text是 markdown 字符串,换行必须写成 JSON 转义的\n,而不是字面的反斜杠加 n 两个字符;- 可读的最小尺寸:
width ≥ 200、height ≥ 60; color可选,省略即为默认(无颜色)。
Canvas 技能 在操作手册层补充了两条硬约束:每个节点的x、y、width、height都必须是整数;文本节点高度过小虽能渲染但可能被裁剪,经验公式是height ≥ 内容行数 × 24。
File 节点
内联渲染库(vault)内的图像、PDF、markdown 笔记或其他文件:
{ "id": "img-cover-7823", "type": "file", "file": "_attachments/images/example.png", "x": -900, "y": -100, "width": 420, "height": 236 }要点:
file必须是vault 相对路径——不能是绝对路径,也不能是~/开头的家目录快捷写法;- 支持的类型:
.png.jpg.webp.gif.pdf.md.canvas; .md文件渲染为预览卡片,.pdf文件渲染首页预览;- 与普通节点一样可以携带可选的
color字段。
Canvas 技能 对路径安全的要求比规范正文更严格:file和background一律拒绝绝对路径、..目录穿越、家目录快捷路径,以及通过符号链接逃逸 vault 的路径,并且只能引用已经存在于 vault 内的文件。技能文档还给出了一个增强写法:file可配合可选的subpath: "#Heading"字段,让笔记节点定位到文档内某个标题。
Group 节点(Zone)
带标签的矩形区域。需要特别注意:group 不裁剪也不包含节点,它只是一个视觉参考框。所谓"放在 group 里面",只是把节点定位到了它的包围盒范围内——JSON 中不存在父子关系。
{ "id": "zone-branding-3391", "type": "group", "label": "Brand Identity", "x": -920, "y": -880, "width": 1060, "height": 290, "color": "6", "background": "_attachments/images/grid-bg.png", "backgroundStyle": "cover" }字段说明:
label:显示在 group 框顶部;color:同时着色 group 边框和标签;background(可选):group 背景图,vault 相对路径;backgroundStyle(可选):背景渲染方式——"cover":填满 group,必要时裁切(默认行为);"ratio":保持宽高比,完整放入 group 内部;"repeat":平铺背景图;
- group 不参与自动布局,纯粹是视觉容器。
Link 节点
把 Web URL 渲染为内嵌预览卡片:
{ "id": "link-karpathy-2233", "type": "link", "url": "https://github.com/karpathy", "x": 200, "y": -300, "width": 400, "height": 120 }要点与安全边界:
url必须是合法的https://URL;- Obsidian 会抓取目标页的 Open Graph 元数据(标题、描述、缩略图);
- 写入 link 节点本身不发起任何网络请求;但用户在 Obsidian 中打开或渲染它时,Obsidian 会访问 URL 所在主机。规范因此要求先向用户披露这一"渲染期出站"(render-time egress),如果用户不接受,就改用 text 节点承载 URL 文本。
Canvas 技能 对此的表述是:link 节点不授予抓取该页面的许可——生成 JSON 无请求,但在 Obsidian 中打开可能拉取该 URL 主机的 Open Graph 元数据,必须先确认用户可接受,否则用 text 节点。
Edges(边)
边表示节点之间的连接,在纯排版型的画布(mood board)上通常留空。
{ "id": "e-hub-cidx", "fromNode": "hub", "fromSide": "right", "fromEnd": "none", "toNode": "c-idx", "toSide": "left", "toEnd": "arrow", "label": "concepts", "color": "5" }字段语义与默认值(这里有一个容易踩坑的不对称默认值):
| 字段 | 必填 | 说明 |
|---|---|---|
id | 是 | 边的唯一 ID |
fromNode/toNode | 是 | 源/目标节点的 ID,必须指向已存在或同批草稿中的节点 |
fromSide/toSide | 否 | "top""bottom""left""right";省略时 Obsidian 根据节点相对位置自动计算最佳边 |
fromEnd | 否 | 源端端帽,默认"none",取值"none"|"arrow" |
toEnd | 否 | 目标端端帽,默认"arrow",取值"none"|"arrow" |
label | 否 | 显示在边上的文本 |
color | 否 | 与节点相同的调色板("1"–"6"或十六进制) |
由于大多数边表达有向关系,这个不对称的默认组合(fromEnd: "none"、toEnd: "arrow")意味着:什么都不写,就是一条从源指向目标的单箭头。这是规范专门点名的设计意图。
配色参考表
| 编码 | 颜色 | 近似 Hex | 典型用途 |
|---|---|---|---|
"1" | 红 / 番茄色 | #e03e3e | 警告、归档 |
"2" | 橙 | #d09035 | 进行中的工作 |
"3" | 黄 / 金 | #d0a023 | 未完成、笔记 |
"4" | 绿 / 青 | #448361 | 内容、来源 |
"5" | 蓝 / 青 | #3ea7d3 | 导航、信息 |
"6" | 紫 / 罗兰 | #9063d2 | 标题、身份标识 |
完全省略color字段即为默认外观(无边框色、标签透明)。边的color与节点共用同一调色板,额外支持十六进制值。
图像尺寸计算
file 节点的width/height不应拍脑袋,规范给出的流程是:先用工具读出图像实际像素,再按宽高比查表。
python3 -c "from PIL import Image; img=Image.open('path.png'); print(img.width, img.height)" # 或 identify -format '%w %h' path.png宽高比对照表:
| 宽高比 | 条件(ratio) | Canvas 宽 | Canvas 高 |
|---|---|---|---|
| 16:9(宽屏) | 1.6–2.0 | 420 | 236 |
| 2:1(超宽) | > 2.0 | 440 | 220 |
| 4:3 | 1.2–1.6 | 380 | 285 |
| 1:1(正方形) | 0.9–1.1 | 280 | 280 |
| 3:4 | 0.6–0.9 | 240 | 320 |
| 9:16(竖版) | < 0.6 | 200 | 356 |
| 任意 | 400 | 520 | |
| 未知 | 兜底 | 320 | 240 |
自动定位伪代码
规范提供了一段确定性的放置算法,用于向画布追加节点时的自动布局。它的核心逻辑分三种情况:
function place_node(canvas, zone_label, new_w, new_h): zone = find group node where label == zone_label padding = 20 if zone not found: max_y = max(n.y + n.height for n in canvas.nodes) + 60 return (-400, max_y) # Nodes visually inside zone inside = [n for n in canvas.nodes if n.type != 'group' and zone.x <= n.x < zone.x + zone.width and zone.y <= n.y < zone.y + zone.height] if inside is empty: return (zone.x + padding, zone.y + padding) # Rightmost point in zone rightmost = max(n.x + n.width for n in inside) next_x = rightmost + 40 if next_x + new_w > zone.x + zone.width - padding: # Overflow → new row bottom_of_row = max(n.y + n.height for n in inside) return (zone.x + padding, bottom_of_row + padding) # Same row row_y = min(n.y for n in inside) # align to top of existing row return (next_x, row_y)逐条解读:
- 找不到目标 group时,新节点落在整画布最底部之下 60 px、x = -400 处;
- 目标 group 为空时,落在 group 内左上角,留 20 px 内边距;
- 同排有空位时,新节点接在"区域内最右点 + 40 px 间隙"处,y 对齐当前行的顶边(取行内最小 y),实现行首对齐;
- 横向放不下(
next_x + new_w越过 group 右边界减去 padding)时,换行:回到 group 左内边距,y 取"当前行底部 + 20 px"。
注意算法用"视觉包含"(坐标落在包围盒内)判断节点属于哪个 zone,而不是任何父子字段——再次印证 group 只是视觉容器。Canvas 技能 在操作手册中把这套参数固化为硬性要求:20 px 内边距、40 px 间隙、必要时换行,group 扩容前需先给出预览。
完整示例:双 Zone 画布
一个最小完整的双分区画布,覆盖 text、group、file 三类节点与空 edges 数组:
{ "nodes": [ { "id": "title-0001", "type": "text", "text": "# Brand Reference\n\n**AI Marketing Hub** visual assets", "x": -920, "y": -2440, "width": 560, "height": 180, "color": "6" }, { "id": "zone-logos", "type": "group", "label": "Logos & Icons", "x": -920, "y": -2200, "width": 1800, "height": 320, "color": "6" }, { "id": "img-logo-pro", "type": "file", "file": "_attachments/images/example.png", "x": -900, "y": -2180, "width": 420, "height": 236 }, { "id": "img-icon-free", "type": "file", "file": "_attachments/images/example-icon.png", "x": -440, "y": -2180, "width": 280, "height": 280 }, { "id": "zone-covers", "type": "group", "label": "Skill Covers", "x": -920, "y": -1820, "width": 1800, "height": 340, "color": "3" }, { "id": "img-seo", "type": "file", "file": "_attachments/images/example-cover.png", "x": -900, "y": -1800, "width": 420, "height": 236 } ], "edges": [] }从这个示例可以读出几处隐含的布局约定:zone 之间的垂直间距(第一个 zone 底部 y=-1880 到第二个 zone 顶部 y=-1820 留了 60 px)、图像节点相对 group 左内边距 20 px(-920 → -900)、标题区与第一个 zone 之间的 40 px 间隔(-2440+180=-2260 → -2200)。节点数组在技能手册中被明确说明是自底向上的 z-order:重写 canvas 时必须保持现有数组顺序,新节点追加即可。
常见错误清单
规范最后集中列出五类高频错误,前四条与前面各节呼应,最后一条是文本节点的尺寸经验:
- 路径格式错误:应使用 vault 相对路径如
_attachments/images/file.png,不能是宿主机绝对路径; - ID 冲突:生成新 ID 前必须读取全部现有 ID 并校验唯一性;
- 负 y 方向混淆:
y: -2400在y: -1000上方,越负越靠上; - Group 不裁剪:JSON 中没有父子关系,"放进 group" 只是坐标落在包围盒内;
- 文本节点高度不足:Obsidian 能渲染但可能裁剪,建议
height ≥ 内容行数 × 24。
仓库实现侧:规范如何被强制执行
规范文档给出"应该怎么写",而 claude-obsidian 仓库的源码定义了"写坏了会发生什么"。canvas 是 事务模块 中一个独立的操作类型canvas(见OPERATION_TYPES集合),其写入范围在代码中被硬编码约束:
if operation_type == "canvas" and not ( (relative.startswith("wiki/canvases/") and relative.endswith(".canvas")) or relative == "wiki/canvases/index.md" ): raise TransactionValidationError( "WRITE_SCOPE_VIOLATION", "canvas operations may write only wiki/canvases/*.canvas " f"and wiki/canvases/index.md: {relative}", )(transaction.py)
即 canvas 事务只能写wiki/canvases/下的.canvas文件和目录索引wiki/canvases/index.md,任何其它目标都会抛出WRITE_SCOPE_VIOLATION。事务测试 中有对应的最小权限用例:把.raw/.manifest.json(受管元数据)写进 canvas 包会得到MANAGED_METADATA_COLLISION,把wiki/sources/NotACanvas.md写进 canvas 包会得到WRITE_SCOPE_VIOLATION。
从源码结构看,这套约束是刻意设计的"操作类型即权限边界":canvas、base、fold、capture等操作类型各自声明了内容域,generic类型也被限定为 wiki-only,即使它名义上最宽泛。
规范与技能手册之间的分工也体现在工程流程上。Canvas 技能 规定完整的变更流程:
- 只读操作(状态查询、列表)直接解析
.canvas文件,报告节点数、group 标签、悬空边端点和缺失的 vault 相对文件目标;默认画布不存在时只报告并提供创建预览,不在状态请求中创建文件; - 草稿阶段:读取整个 canvas 和目录索引,记录每个目标的期望 SHA-256(必须不存在的文件记
null);保留未知字段和数组顺序;新节点要求唯一 ID、整数坐标与尺寸、边端点必须存在; - 校验与预览:JSON 可解析且只用支持的节点类型、ID 唯一、边端点存在、尺寸为正整数、
file/background路径安全且存在、新节点不与现有节点意外重叠或溢出目标 group; - 应用阶段:打包为一个
claude-obsidian.transaction.v1bundle(操作类型canvas),目录索引若变化则与画布同包提交,然后:
python3 "$CORE" transaction inspect BUNDLE --vault VAULT # Set APPROVAL_SHA256 to the inspect result's approval_sha256 after review. python3 "$CORE" transaction apply BUNDLE --vault VAULT \ --approved-plan-sha256 "$APPROVAL_SHA256"approval_sha256来自 inspect 结果,它把展开后的计划绑定到规范化的 vault 根,不能复用于另一个 vault——这一机制在 操作事务参考 中有完整契约说明(预期哈希、原子替换、持久化日志、可恢复回滚)。
对于删除、替换、改名等破坏性变更,技能要求先展示预览并获得明确同意,然后报告操作 ID、变更路径、板名、节点 ID 和最终坐标,且不做 Git 提交——若需要版本历史,走独立的显式 checkpoint 命令。
另外值得一提的旁路保护:仓库的 URL 凭据检测模块 会拦截 userinfo 凭据和敏感查询参数(token、api_key、X-Amz-Signature 等)。虽然它服务于 URL 校验场景而非 canvas JSON 本身,但与规范中 link 节点"披露渲染期出站"的要求共同构成该仓库对外部资源的一贯审慎态度:canvas 里放一个 URL 是纯本地 JSON 写入,而网络行为只发生在用户在 Obsidian 里打开它的那一刻。
小结
把 canvas-spec.md 当作生成.canvas文件的事实标准:双键结构 + 未知字段保留、左上角锚定的 y 向下坐标系、text/file/group/link 四类节点各自的必填与可选字段、边的不对称默认箭头、"1"–"6"调色板、按宽高比查表的图像尺寸、20/40 px 的自动定位参数,加上五条常见错误清单,就是一份可直接落地的生成规则。而 Canvas 技能 与 事务实现 则在其外围加了两层护栏——vault 相对路径安全校验和wiki/canvases/的写入域约束——确保程序化生成的画布既符合规范格式,又不会越权改动 vault 的其他部分。
【免费下载链接】claude-obsidianSelf-organizing AI second brain for Obsidian + Claude Code. Drop any source and Claude reads, links, and files it into one connected knowledge graph of plain Markdown you own. AI note-taking, personal knowledge management (PKM), and an open-source Notion alternative. Based on Karpathy's LLM Wiki pattern.项目地址: https://gitcode.com/GitHub_Trending/cl/claude-obsidian
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考