apps/server/src/assets/llm/skills/dashboards.md
A dashboard is a collection view that renders each child note as a widget on a drag-and-drop grid, similar to Grafana or a home-automation dashboard. The user can freely place and resize the widgets; the layout is saved automatically.
book (create_note with type book).viewType label to dashboard on it (set_attribute with type label, name viewType, value dashboard).Every child note of the dashboard becomes a widget.
A widget is just a child note, rendered with its normal content. Pick the note type by what the widget should show:
| Widget should show | Child note type |
|---|---|
| Formatted text, checklists, links | text |
| A diagram | mermaid |
| A code snippet | code |
| An embedded web page | webView (content is the URL) |
| A drawing | canvas |
| Dynamic / interactive content | render + a Preact JSX child note (see below) |
After creating a widget note, ALWAYS give it an icon: find a fitting one with search_icons and assign it with set_attribute as the widget note's iconClass label (e.g. bx bx-line-chart). The icon is shown in the widget's title bar. Never prepend an emoji to the widget title instead.
For widgets that compute something, fetch data, or respond to clicks, use a render note backed by a Preact JSX component:
render as a child of the dashboard — this is the widget.code note with mime text/jsx as a child of the render note, exporting a default component.~renderNote relation on the render note pointing to the JSX note. You CANNOT set this relation yourself — it enables code execution, so set_attribute refuses it as dangerous. Tell the user exactly what to do, e.g.: "Open the widget note '<render note title>', click the attribute area at the top, and add ~renderNote pointing to '<JSX note title>'." Mention which JSX note to target by title.JSX rules (load the frontend_scripting skill for the full API):
import only; hooks come from "trilium:preact", API methods from "trilium:api".React, require(), or await import() — Trilium uses Preact and JSX notes are ES modules.export default.originEntity from "trilium:api" — it is the render note hosting the component. Do NOT use getActiveContextNote(): on a dashboard the active note is the dashboard itself, not the widget.Example — a widget showing the number of notes created in the last 7 days:
import { useState, useEffect } from "trilium:preact";
import { searchForNotes } from "trilium:api";
export default function RecentNotesWidget() {
const [count, setCount] = useState(null);
useEffect(() => {
searchForNotes("note.dateCreated >= TODAY-7").then(notes => setCount(notes.length));
}, []);
return (
<div style="text-align: center;">
<h3>Notes this week</h3>
<strong style="font-size: 2em;">{count === null ? "…" : count}</strong>
</div>
);
}
dashboard.json attachment (role viewConfig) on the dashboard note. It is managed by the UI and syncs across devices and splits. You cannot alter it — no tool can modify attachments — and there is no need to: the dashboard automatically picks up new child notes as widgets and auto-places them.set_attribute with ~renderNote — it will be rejected as dangerous. Ask the user to add the relation instead.~renderNote on the dashboard note itself — it belongs on the render-type child.dashboard.json attachment (or any view configuration) to lay out widgets — attachments cannot be modified by tools, and the dashboard picks up new widgets automatically; rely on auto-placement.getActiveContextNote() inside a widget's JSX to get "this widget's note" — when the dashboard is open, the active note is the dashboard, not the widget. Use originEntity (the render note) instead.#viewType=dashboard on a text note — the view type only applies to book (and search) notes.search_icons and set the iconClass label instead.