同步笔记的渲染与可扩展性
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)新增了:
- Callout——一个 remark/rehype 转换:
> [!theorem] Theorem 1→<div class="callout" data-callout="theorem">,DOM 结构对齐 Obsidian,因此 CSS 几乎可以共用(定理框只定义一次,两边都能用); - KaTeX(已支持)以及wikilink → corpus 解析(已支持——所以一个
> Prereq:里的 wikilink 会解析到那个概念在 corpus 中的条目)。 - StandMeet 为定理 / 定义 / 证明框提供自己的 CSS。
动态内容 → 沙箱化的 iframe
任何需要实时 JS 的东西都不是插件导入——而是一个 standmeet-widget 的围栏代码块,它的描述符会挂载一个沙箱化的 iframe(iframe + 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 再渲染(StaticHtmlBlock,d89603a65,2026-07-06); - 块级动态组件 → 一个围栏的
standmeet-widget代码块(内部是描述符:type/src/params/sandbox/height/seo);2026-07-06 已上线(93c91da80,app/src/components/page/WidgetBlock.tsx)。识别只在渲染侧(BLOCK_RENDERERS,markdown.tsx:58)——importer 把这段围栏当正文原样带过,和图片引用改写不是一回事; - 笔记级配置 → frontmatter(
standmeet-*键)。
Obsidian 插件生态怎么办?(分类法)
- 写作辅助类(Templater、QuickAdd)——无关紧要:它们在写作过程中运行,最终留下的是纯 markdown;我们只是照单全收;
- 渲染类插件(KaTeX、Mermaid、TikZJax)——不用插件本身,直接使用同一个底层 JS 库(rendering-engines);
- 查询类插件(Dataview)——用原生且更强的方式做同一件事:corpus 是一个真正的 Postgres 数据库 + frontmatter +
note_refs,corpus 查询胜过基于文件的 Dataview;第一个原生查询面是 SDKCorpusWidget的查询语言(query="path:math/** sort:title limit:5"——子树 · 排序 · 上限;113ba6a1a,2026-09-06,sdk/packages/react/src/widgets/CorpusWidget.tsx); - 真正必须用到某个插件时 → 在 Obsidian 那一侧导出时预渲染:让插件在它能跑的地方跑,摄入的是渲染后的输出(Dataview → 静态表格,Templater → 展开后的文本),而不是原始代码块——这符合由所有者触发导出的模型(vault-ingestion);
- 唯一的例外:代码执行(Jupyter 那种)→ 走加固过的沙箱,绝不走插件导入。
两条不可谈判的原则
- 组件内容是用户提供的 → 通过 iframe 沙箱渲染,并受 corpus ACL 约束;
- SEO——iframe 组件不会被索引(schema 里原来的
seo_indexed现在叫published,corpus_notes.published);对 SEO 重要的内容要走服务端渲染,而不是做成组件。
为什么(与仓库既有决策的关联)
- 不依赖 Obsidian / 不被锁定——只模仿约定,不引入插件(CSS 优于插件这一课);
- 内容一次,呈现两次——与 vault-ingestion(单一 vault,发布时才受控)以及"markdown 是内容,CSS 是呈现"一致;
- 优雅降级——没有 CSS 的时候,一个 callout 依然是一段可读的引用块。