Skill: Check Template Rendering
Use this to verify a layout renders without errors and inspect template variable values.
How it works
/_system/renderlayout compiles and executes a Jet template server-side against a real note from the vault. It returns a previewURL — a direct link to the rendered HTML. No sync needed: share this link with the user while they're actively editing, it's faster than waiting for vault sync.
Tool: scripts/trip2g-preview.mjs
The repo ships scripts/trip2g-preview.mjs — a CLI wrapper that reads the API key automatically from .obsidian/plugins/trip2g/data.json (walks up from cwd).
# run from vault dir (where .obsidian/ lives), e.g. docs/
# smoke test — no files needed
node scripts/trip2g-preview.mjs \
--layout-src "{{ note.HTMLString() }}" --layout-path "/_debug.html" \
--note-src "hello"
# → http://localhost:8081/_system/renderlayout?preview_id=abc123
# test a layout file against a real note
node scripts/trip2g-preview.mjs \
--layout-file _layouts/mesh/index.html \
--note-path /en/user/templates
# fetch rendered HTML directly
node scripts/trip2g-preview.mjs \
--layout-src "{{ note.M().Debug() }}" --layout-path "/_debug.html" \
--note-path /demo/template_meta_test \
--fetch
Warnings and errors go to stderr. Exit code 1 on failure.
Share the printed URL with the user — they can open it in browser.
Inspect variables with debug()
{{ debug(note.M()) }}
{* → *templateviews.Meta: &{raw:map[extra_content:[channels prices]]}
methods: [Debug Get GetBool GetInt GetString GetStrings Has Raw] *}
{{ note.M().Debug() }}
{* → {"extra_content":["channels","prices"],"title":"Sales"} *}
Always use parentheses when passing methods to
debug():
{{ debug(note.Title()) }}✓ →string: My Title
{{ debug(note.Title) }}✗ →func() string: 0x...(method reference, not value)
Preview vs production: known differences
| Feature | Preview | Production |
|---|---|---|
| autoimport components | ✅ works | ✅ works |
yield_blocks() CSS |
✅ works | ✅ works |
{{ asset("file.css") }} |
❌ returns path as-is | ✅ resolves to CDN URL |
htmlInjectionsHead |
✅ empty slice (no error) | ✅ real injections |
note.M() frontmatter |
✅ works (both note.path and note.src) |
✅ works |
{{ asset() }} doesn't resolve in preview because assets are tied to note version IDs in the production loader. Use note.path to get real asset URLs, or inline CSS/JS directly in the template during development.
Rendering a single BEM component
To preview one component in isolation, explicitly import its file and yield the block.
See en/user/bem for BEM conventions and @lid/@did naming.
Important caveats:
- Import paths must be relative to the layout path (after
/_layouts/prefix is stripped).
From/_layouts/mesh/_preview.html→ import"bar"resolves to/mesh/bar✓ yield_blocks()returns empty in preview (wire phase is skipped). Yield style blocks directly instead.- Dependencies must be imported manually — autoimport doesn't work in preview.
# Render mesh/bar.html component with its styles
node scripts/trip2g-preview.mjs \
--layout-path "/_layouts/mesh/_preview.html" \
--layout-src '{{ import "_blocks" }}{{ import "bar" }}{{ import "button" }}<style>{{ yield _style_mesh_bar() }}{{ yield _style_mesh_button() }}</style>{{ yield mesh_bar() }}' \
--note-src "hello"
Pattern:
{{ import "_blocks" }}— shared layout wrapper (if needed){{ import "component" }}— the component file (and its dependencies)<style>{{ yield _style_component() }}</style>— inline CSS (instead ofyield_blocks){{ yield component() }}— the HTML block
Tip: /_system/renderlayout without params always serves the latest render — keep it open in a browser while iterating.
Jet range gotcha
Single-variable range gives the index (0, 1, 2…), not the value:
{* WRONG — item = 0, 1, 2 *}
{{ range item := note.M().GetStrings("extra_content") }}{{ item }}{{ end }}
{* CORRECT *}
{{ range i, item := note.M().GetStrings("extra_content") }}{{ item }}{{ end }}