Skip to content

feat: support Canvas Candy via file-node frontmatter (Obsidian :has() mechanism) #20

@LeslieOA

Description

@LeslieOA

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:

  1. 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?
  2. 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?
  3. Cache invalidation. What happens when the referenced file changes on disk? Re-enrich on every file change? Subscribe to file watchers?
  4. 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

  1. Build a minimal reproduction in pure JSON Canvas (no Obsidian) that exhibits the target behaviour
  2. Write a design doc covering all four architectural questions above
  3. Coordinate with whoever owns the markdown rendering library (frontmatter parsing should be shared, not duplicated)
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions