Context

The Calab.ai Handbook’s Evaluation Workbooks currently use dataviewjs code blocks to auto-generate summary tables from inline criterion fields. Quartz 4 does not support Dataview (Decision 01), so these blocks render as raw code on the published site — a known broken experience.

With the adoption of a distributed criteria architecture (Decision 04), criteria definitions are moving from inline fields within workbook files to structured YAML frontmatter in separate criteria files distributed across Guild Roles/ directories. The workbooks need a rendering mechanism that:

  1. Aggregates criteria from multiple files across multiple guild directories
  2. Renders in both Obsidian (authoring) and Quartz (publishing) with rendering parity
  3. Supports two display modes: summary (aggregated statistics) and detail (full criteria with score fields)
  4. Follows the established dual-rendering plugin pattern (Decision 03)

Decision

We will implement a criteria-list code block syntax with dual-rendering plugins — a Quartz transformer plugin and a mirrored Obsidian plugin — following the exact same pattern established by the page-list plugin (Decision 03).

Syntax

```criteria-list
role: Engineer
level: L3
mode: summary
```

Directives:

DirectiveRequiredValuesDescription
roleYesString (e.g., “Engineer”, “Architect”)The role type to aggregate criteria for
levelYesString (e.g., “L1”, “L2”, “L3”, “L4”, “L5”)The seniority level
modeYessummary or detailRendering mode

Rendering Modes

Summary mode — replaces the current dataviewjs auto-summary:

GuildManagement PracticeCriteriaSelf Score (%)Assessor Score (%)Delta (%)
Sales GuildStakeholder Mgmt Management3
Technology GuildSoftware Eng Management7
Technology GuildSolution Eng Management8
Technology GuildPlatform Architecture Management2
Delivery GuildConsulting Management10
Delivery GuildProject Mgmt5
Delivery GuildBusiness Analysis Management6

Detail mode — replaces the current inline criterion fields:

Renders all criteria grouped by Guild → Management Practice with scoring fields for each criterion.

File Scanning Logic

Both plugins use identical scanning logic:

  1. Starting from the 02 Guilds/ directory, scan all */Roles/ directories recursively
  2. For each .md file found, read its YAML frontmatter
  3. Filter to files where:
    • type == "role-criteria"
    • role matches the directive’s role value
    • level matches the directive’s level value
  4. Extract the criteria array from each matching file’s frontmatter
  5. Group all criteria by guild (from the file’s frontmatter) and subarea (from each criterion item)
  6. Sort groups: by guild name, then by subarea name within each guild
  7. Render according to the specified mode

Architecture

Obsidian (authoring)                     Quartz (publishing)
=============================           =============================
registerMarkdownCodeBlockProcessor      markdownPlugins() remark plugin
  └─ "criteria-list" language             └─ visit(tree, "code")
     │                                       │
     ├─ parseDirectives(source)             ├─ parseDirectives(node.value)
     ├─ vault.getFolderByPath("02 Guilds")  ├─ glob("content/02 Guilds/*/Roles/**/*.md")
     ├─ filter by frontmatter               ├─ filter by frontmatter
     │   (type, role, level)                │   (type, role, level)
     ├─ extract criteria arrays             ├─ extract criteria arrays
     ├─ group by guild + subarea            ├─ group by guild + subarea
     ├─ sort groups                         ├─ sort groups
     └─ MarkdownRenderer.render()           └─ emit MDAST table/heading nodes

Consequences

Benefits

  • Rendering parity: Workbooks render identically in Obsidian and Quartz (matching the Decision 03 standard)
  • Replaces broken rendering: Eliminates non-functional dataviewjs blocks from the published site
  • Dynamic aggregation: Adding or modifying criteria files automatically updates all workbooks that reference the same role+level
  • Consistent pattern: Follows the proven page-list dual-plugin architecture; maintenance teams already understand this pattern
  • Score management: The detail mode rendering includes score input fields that persist in the workbook file (not in criteria files)

Trade-offs

  • Two plugins to maintain: As with page-list, changes to the scanning/rendering logic must be synchronised between the Quartz transformer and Obsidian plugin
  • Build time: The Quartz transformer must scan all guild Roles directories and parse frontmatter from criteria files on every build. For the current ~15 files this is negligible; may need caching if file count grows significantly
  • No live-reload in Obsidian: As with page-list, adding a new criteria file requires toggling edit/read mode to see updated output

Considered Alternatives

A. Obsidian Databases (.base files) + Quartz Transformer

Use Obsidian’s native .base database views for workbooks and write a Quartz transformer to render .base files.

Why not chosen: .base files use Obsidian’s internal database format, which is not well-documented and subject to change. Building a Quartz renderer for this format creates a fragile coupling to Obsidian’s internal APIs. The criteria-list code block approach is self-contained and doesn’t depend on Obsidian-specific file formats.

B. Static Markdown Tables Generated by CI Script

Use a pre-build script that reads criteria files and generates static markdown tables in the workbook templates before Quartz build.

Why not chosen: This introduces a build dependency that must run before Quartz build. It would modify workbook files (creating merge conflicts in git), and the static tables in Obsidian wouldn’t auto-update without running the script. The dual-plugin approach provides real-time rendering in both environments without modifying source files.

C. Extend Existing page-list Plugin

Add criteria aggregation capabilities to the existing page-list plugin rather than creating a new plugin.

Why not chosen: The scanning logic, directive syntax, and rendering output are fundamentally different. page-list scans for overview files and renders navigation links; criteria-list scans for criteria files and renders scored tables. Combining them would violate single-responsibility and make both harder to maintain.

Next Actions

  • Implement per Plan 07, Steps 5-6
  • Consider adding score persistence directives in a future revision (e.g., score-file: path/to/scores.md)
  • Monitor rendering performance as criteria file count grows
  • 03 Obsidian to Quartz Rendering — Established the dual-rendering plugin pattern
  • 04 Distributed Role Criteria Architecture — Criteria distribution and governance model
  • quartz/plugins/transformers/pageList.ts — Reference implementation for dual-rendering plugins
  • content/.obsidian/plugins/obsidian-page-list/main.js — Obsidian-side reference implementation