2026-09-23·by Sijie Wang#standmeet#architecture#decision

rendering-and-extensibility

同步笔记的渲染与可扩展性

上级:obsidian-sync-mechanism

StandMeet 如何支撑它所同步的笔记的呈现 / 可扩展层(定理式 callout、数学公式、图表、动态组件)——这是已经定型的设计。

原则:内容可移植(一次写成)+ 呈现按宿主各自决定

Obsidian 插件无法在 Obsidian 之外运行,甚至连 Obsidian 自己的 Publish 也跑不动它们(Publish 是个浏览器应用,只有核心渲染和 CSS 能存活)。所以 StandMeet 只模仿可移植的 markdown 约定,绝不引入 Obsidian 的插件或 CSS

  • 同步只携带可移植的 markdown——callout > [!theorem]、KaTeX $$、wikilink [[X]]——这些在任何地方都能退化为引用块或纯文本;
  • StandMeet 是这套格式的其中一个渲染器(就像 Obsidian 是另一个);呈现方式由每个宿主自己定义(Obsidian:一段 CSS 片段;StandMeet:自己的 CSS)。

标准约定 → 原生渲染

StandMeet 的 markdown 处理管线(app/src/components/page/markdown.tsx,已经支持 KaTeX + mermaid)新增了:

  1. Callout——一个 remark/rehype 转换:> [!theorem] Theorem 1<div class="callout" data-callout="theorem">DOM 结构对齐 Obsidian,因此 CSS 几乎可以共用(定理框只定义一次,两边都能用);
  2. KaTeX(已支持)以及wikilink → corpus 解析(已支持——所以一个 > Prereq: 里的 wikilink 会解析到那个概念在 corpus 中的条目)。
  3. StandMeet 为定理 / 定义 / 证明框提供自己的 CSS

动态内容 → 沙箱化的 iframe

任何需要实时 JS 的东西都不是插件导入——而是一个 standmeet-widget 的围栏代码块,它的描述符会挂载一个沙箱化的 iframeiframe + postMessage,Figma / VS Code webview 那套模型)。宿主定义的边界很薄(一份 manifest:URL/bundle + capabilities + 尺寸;一套 postMessage schema:渲染数据传入,resize/event/capability-request 传出;沙箱权限)+ 插件自己定义内部实现;隔离优先,与 sandbox-js-hardening / connector-egress-guard 同一原则。

选择 iframe 有两个诚实的代价:性能——每个 iframe 都是一个独立的浏览上下文,所以 iframe 只用于富交互组件,轻量的部分(比如一个公式)仍然通过 remark/rehype 内联渲染;主题——iframe 内容不会继承宿主的 CSS(这是隔离的代价),所以主题要通过消息协议 / CSS 变量传进去。

额外信息放在哪里(三层)

  • 静态 / 预渲染(例如一个 Dataview Publisher 烘焙出的表格)→ 直接进正文,不需要额外信息;预烘焙的 HTML 有自己的围栏 standmeet-html,先 sanitize 再渲染(StaticHtmlBlockd89603a65,2026-07-06);
  • 块级动态组件 → 一个围栏的 standmeet-widget 代码块(内部是描述符:type/src/params/sandbox/height/seo);2026-07-06 已上线(93c91da80app/src/components/page/WidgetBlock.tsx)。识别只在渲染侧BLOCK_RENDERERSmarkdown.tsx:58)——importer 把这段围栏当正文原样带过,和图片引用改写不是一回事;
  • 笔记级配置 → frontmatter(standmeet-* 键)。

Obsidian 插件生态怎么办?(分类法)

  • 写作辅助类(Templater、QuickAdd)——无关紧要:它们在写作过程中运行,最终留下的是纯 markdown;我们只是照单全收;
  • 渲染类插件(KaTeX、Mermaid、TikZJax)——不用插件本身,直接使用同一个底层 JS 库rendering-engines);
  • 查询类插件(Dataview)——用原生且更强的方式做同一件事:corpus 是一个真正的 Postgres 数据库 + frontmatter + note_refs,corpus 查询胜过基于文件的 Dataview;第一个原生查询面是 SDK CorpusWidget 的查询语言(query="path:math/** sort:title limit:5"——子树 · 排序 · 上限;113ba6a1a,2026-09-06,sdk/packages/react/src/widgets/CorpusWidget.tsx);
  • 真正必须用到某个插件时在 Obsidian 那一侧导出时预渲染:让插件在它能跑的地方跑,摄入的是渲染后的输出(Dataview → 静态表格,Templater → 展开后的文本),而不是原始代码块——这符合由所有者触发导出的模型(vault-ingestion);
  • 唯一的例外:代码执行(Jupyter 那种)→ 走加固过的沙箱,绝不走插件导入。

两条不可谈判的原则

  1. 组件内容是用户提供的 → 通过 iframe 沙箱渲染,并受 corpus ACL 约束;
  2. SEO——iframe 组件不会被索引(schema 里原来的 seo_indexed 现在叫 publishedcorpus_notes.published);对 SEO 重要的内容要走服务端渲染,而不是做成组件。

为什么(与仓库既有决策的关联)

  • 不依赖 Obsidian / 不被锁定——只模仿约定,不引入插件(CSS 优于插件这一课);
  • 内容一次,呈现两次——与 vault-ingestion(单一 vault,发布时才受控)以及"markdown 是内容,CSS 是呈现"一致;
  • 优雅降级——没有 CSS 的时候,一个 callout 依然是一段可读的引用块。
about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →

rendering-and-extensibility