Rendering & extensibility of synced notes
Parent: obsidian-sync-mechanism
How StandMeet supports the presentation / extensibility layer of the notes it syncs (theorem callouts, math, diagrams, dynamic widgets) — the settled design.
Principle: content portable (write once) + presentation per host
Obsidian plugins are not runnable outside Obsidian, and even Obsidian's own Publish can't run them (it's a browser app — only core rendering + CSS survive). So StandMeet mirrors the portable markdown conventions, never imports Obsidian's plugins/CSS:
- sync carries only portable markdown — callouts
> [!theorem], KaTeX$$, wikilinks[[X]]— which degrade to blockquotes/plain text anywhere; - StandMeet is one renderer of that format (like Obsidian is another); presentation is defined per host (Obsidian: a CSS snippet; StandMeet: its own CSS).
Standard conventions → rendered natively
StandMeet's markdown pipeline (app/src/components/page/markdown.tsx, already KaTeX + mermaid) adds:
- Callouts — a remark/rehype transform:
> [!theorem] Theorem 1→<div class="callout" data-callout="theorem">, with the DOM aligned to Obsidian's so the CSS is nearly shared (the theorem box is defined once, applied on both); - KaTeX (already) and wikilinks → corpus resolution (already — so a
> Prereq:wikilink resolves to that concept's corpus entry). - StandMeet ships its own CSS for the theorem/definition/proof boxes.
Dynamic content → a sandboxed iframe
Anything needing live JS is not a plugin import — it's a standmeet-widget fenced block whose descriptor mounts a sandboxed iframe (iframe + postMessage, the Figma / VS-Code-webview model). Thin host-defined boundary (a manifest: URL/bundle + capabilities + sizing; a postMessage schema: render-data in, resize/event/capability-request out; sandbox permissions) + plugin-defined internals; isolation-first, same principle as sandbox-js-hardening / connector-egress-guard.
Two honest tradeoffs of the iframe choice: performance — each iframe is a separate browsing context, so iframes are for rich/interactive widgets while lightweight bits (a formula) stay inline via remark/rehype; theming — iframe content doesn't inherit host CSS (the cost of isolation), so the theme passes through the message protocol / CSS variables.
Where the extra info lives (three tiers)
- static / pre-rendered (e.g. a
Dataview Publisher–baked table) → straight into the body, no extra info; prebaked HTML has its own fence,standmeet-html, sanitized then rendered (StaticHtmlBlock,d89603a65, 2026-07-06); - per-block dynamic widget → a fenced
standmeet-widgetblock (descriptor inside:type/src/params/sandbox/height/seo); shipped 2026-07-06 (93c91da80,app/src/components/page/WidgetBlock.tsx). Recognition is render-side only (BLOCK_RENDERERS,markdown.tsx:58) — the importer passes the fence through as body text untouched, unlike the image-ref rewrite; - note-level config → frontmatter (
standmeet-*keys).
What about Obsidian's plugin ecosystem? (the taxonomy)
- authoring helpers (Templater, QuickAdd) — irrelevant: they run while writing and leave plain markdown; we just ingest it;
- rendering plugins (KaTeX, Mermaid, TikZJax) — don't use the plugin, use the same underlying JS library directly (rendering-engines);
- query plugins (Dataview) — do it natively and stronger: the corpus is a real Postgres DB + frontmatter +
note_refs, so corpus queries beat Dataview-over-files; the first native query surface is the SDKCorpusWidgetquery language (query="path:math/** sort:title limit:5"— subtree · sort · cap;113ba6a1a, 2026-09-06,sdk/packages/react/src/widgets/CorpusWidget.tsx); - when a plugin is genuinely required → pre-render on the Obsidian side at export: let the plugin run where it works and ingest the rendered output (Dataview → static table, Templater → expanded text), not the raw block — fits the owner-triggered export model (vault-ingestion);
- the one exception: code execution (Jupyter-style) → route through the hardened sandbox, never a plugin import.
Two non-negotiables
- Widget content is user-provided → renders through the iframe sandbox + is subject to corpus ACL;
- SEO — iframe widgets aren't indexed (the schema's
seo_indexedis nowpublished,corpus_notes.published); SEO-critical content is server-rendered, not a widget.
Why (ties to the vault's decisions)
- No Obsidian dependency / no lock-in — mirror conventions, don't import plugins (the CSS-over-plugins lesson);
- content once, presentation twice — matches vault-ingestion (single vault, publish-gated) and "markdown = content, CSS = presentation";
- graceful degradation — without the CSS a callout is still a readable blockquote.