Summary
The Obsidian-native Canvas Candy path applies classes to a card via a CSS :has() selector that reads cssclasses from an embedded markdown file referenced as a file-type node — not from frontmatter on a text node. This is architecturally distinct from the baseline implementation in src/renderer/extensions/cssclasses.ts (already ported; conformance tests in #16) and needs design work before implementation.
The two mechanisms
Baseline (already ported):
- Frontmatter inside a text-type node's body
- Class extracted from
node.text
- Class applied to the same node it appears on
This issue:
- A
.md file is referenced as a file-type node on the canvas
- That file's frontmatter declares
cssclasses
- A CSS rule like
.canvas-node-container:has(.cc-postit) { ... } matches against the "containing card" based on the embedded file's class
- Class is applied to a card or group "containing" the file reference
Open architectural questions
These need answers before any implementation work:
- What is "containing card" in this renderer's model? JSON Canvas has no formal container concept for cards. Obsidian's grouping is partly visual (group nodes) and partly DOM-based (CSS
:has() evaluating against rendered HTML). What's the equivalent in a Skia/Rust renderer with no DOM?
- Sync vs async enrichment. File contents need to be resolved at parse/enrichment time. That's an async fetch. How does it fit the renderer's current sync-enrichment pipeline? Pre-resolve before render? Two-phase enrichment?
- Cache invalidation. What happens when the referenced file changes on disk? Re-enrich on every file change? Subscribe to file watchers?
- Nesting. Should this work for file-node → file-node → file-with-frontmatter chains? If yes, depth limits?
Out of scope
- A general CSS selector engine. A purpose-built mapping (file-node references a file with
cssclasses → apply those classes to the file node's containing card) is sufficient. We don't need to evaluate arbitrary :has() selectors.
- Implementation. This issue is design-first. Write a design doc, get it reviewed, then split implementation into sub-issues.
Recommended approach
- Build a minimal reproduction in pure JSON Canvas (no Obsidian) that exhibits the target behaviour
- Write a design doc covering all four architectural questions above
- Coordinate with whoever owns the markdown rendering library (frontmatter parsing should be shared, not duplicated)
- Only after design review: split into implementation sub-issues
Agent-ready prompt
Design — do not implement — Canvas Candy's file-node-frontmatter mechanism. Read both the upstream TfTHacker/obsidian-canvas-candy plugin source AND the baseline implementation at workspace-sh/workspace's packages/canvas-ui/src/extensions/cssclasses.ts to understand the gap.
The baseline handles frontmatter-on-text-node. This issue is about frontmatter-inside-an-embedded-markdown-file-referenced-as-file-node — a different mechanism entirely.
Output a design doc (as an issue comment) covering: (a) the "containing card" concept in this renderer's model, (b) sync vs async enrichment, (c) cache invalidation, (d) whether nesting is supported. Reference Obsidian's actual behaviour as the ground truth.
Don't write implementation code. After the design is reviewed, propose a split into implementation sub-issues.
Summary
The Obsidian-native Canvas Candy path applies classes to a card via a CSS
:has()selector that readscssclassesfrom an embedded markdown file referenced as a file-type node — not from frontmatter on a text node. This is architecturally distinct from the baseline implementation insrc/renderer/extensions/cssclasses.ts(already ported; conformance tests in #16) and needs design work before implementation.The two mechanisms
Baseline (already ported):
node.textThis issue:
.mdfile is referenced as a file-type node on the canvascssclasses.canvas-node-container:has(.cc-postit) { ... }matches against the "containing card" based on the embedded file's classOpen architectural questions
These need answers before any implementation work:
:has()evaluating against rendered HTML). What's the equivalent in a Skia/Rust renderer with no DOM?Out of scope
cssclasses→ apply those classes to the file node's containing card) is sufficient. We don't need to evaluate arbitrary:has()selectors.Recommended approach
Agent-ready prompt