Files
trilium_share_gruvbox/AGENTS.md
T

170 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AGENTS.md Trilium Share Gruvbox Blog System
Public repository for the **Gruvbox public-share theme + blog system** that runs on a
[TriliumNext](https://github.com/TriliumNext/Trilium) instance (`notes.familie-zink.org`,
live blog: `richard.familie-zink.org`). This file captures the requirements and design
decisions so future work stays consistent.
## What this is
A blog built on Trilium's Public Share: a backend **blog generator** builds index/category/tag
pages, an RSS feed, a sitemap, a search index and a garden-state map from notes that carry a
`publish=true` label. Frontend `shareHtml` scripts add search, prev/next, reading time,
widget rendering (Mermaid/Excalidraw/MindMap) and a themed footer. All styling lives in one
CSS file (Gruvbox, light + dark, theme toggle without reload).
## Repository layout
| Path | Note in Trilium | Purpose |
|---|---|---|
| `blog_generator.js` | `blog_generator` (backend script) | builds index, categories, tags, feed, sitemap, `blog_data`, `state_map` |
| `blog_publish_manager.js` | `blog_publish_manager` (backend script) | mirrors notes between `Unpublished` and the blog on `publish` label changes |
| `theme/blog_share_theme.css` | `blog_share_theme.css` (`shareCss`) | the entire Gruvbox theme |
| `theme/favicon.svg`, `theme/cookie.woff2` | `favicon`, `Cookie` | site icon + BMC font |
| `lib/highlight.js`, `lib/hljs_line_numbers.js` | `highlight.js`, `hljs line numbers plugin` | code highlighting |
| `lib/3d/*` | three.js, OrbitControls, STLLoader, occt-import-js (WASM) | local libraries for the 3D model viewer |
| `scripts/*.js` | one code note each (`.blog`), attached via `~shareHtml` | frontend snippets (verbatim content incl. `<script>`) |
| `templates/blog_article.html` | `Blog Article` | article template with promoted labels |
| `examples/*` | ignis8 icons/script | sample category logo |
| `generated/*` | `feed.xml`, `sitemap.xml`, `blog_data`, `state_map` | regenerated by the generator (reference only) |
| `assets/palette.png` | | Gruvbox palette (from morhetz/gruvbox, MIT) |
## How the generator works (keep this intact)
- Collects notes with `publish=true` under the blog root (skips `blogGarden` subtrees).
- **Index**: page 1 = blog root, further pages as notes under `.blog` named by
`blogPagedFor` + `blogPagedNumber` labels, `shareAlias = <alias>-<n>` (`blog-2`, …).
- **Categories**: any top-level note under the blog root that is not a system note
(`Tags`, `.blog`, `Impressum`, `About Me`, `Search`) is a category; `blogGarden`
marks a growth-state garden. Category pages are paginated the same way.
- **Sorting**: index, categories, tags and `blog_data` are all sorted by publication
date descending (`date` label, fallback `dateCreated`). Never change this.
- **Pagination**: `POSTS_PER_PAGE = 10`. Prev/next on index/category pages link only
between that section's pages (server-side `.blog-pagination`), not globally.
- **Article prev/next** (`scripts/prev_next.js`) is scoped to the article's category
(`blog_data.articles[].category`); falls back to the global list when uncategorized.
- **Menu sorting** (`scripts/menu_date_sort.js`, `shareHtmlLocation=body:end`): reads
`blog_data.articles[].date` (ISO, used for sorting via `data-pubdate`) and displays
`dateText` (`Fri 2026-08-28`), re-sorts each category's menu entries by date descending
and prefixes the label with `Fri 2026-08-28 <shortened title>` (icons preserved).
Non-article entries are left in place.
- **Dates**: all visible dates (post cards, garden table) are rendered by the generator's
`dateText()` helper as `Fri 2026-08-28` (English weekday + ISO date); `blog_data.date`
stays ISO for sorting, `blog_data.dateText` is added for display. JSON-LD keeps ISO dates.
- **Thumbnails** (`thumbSrc()`): prefer a child note of the article titled `thumb.<ext>` **or**
`*_thumb.<ext>` (e.g. `thumb.png`, `ngc281_thumb.jpg`) → `api/notes/<id>/download`; otherwise
use the first `<img>` from the note content. Layout: image left, summary right (`.blog-post-inner`),
`max-height:256px`, lazy-loaded. The generator auto-sets `shareHiddenFromTree` on matching
thumb child notes so they never appear in the blog menu. **There is no server-side resizing**
see "Limitations".
- Regenerates `feed.xml`, `sitemap.xml`, `state_map` (garden states) and `blog_data`
(search index: id/title/url/summary/tags/category). Sets `shareDescription` from `summary`.
## Publishing workflow (blog_publish_manager)
New notes are created under `Unpublished/<Category>` (the `Unpublished` root mirrors the blog
categories). Setting `publish=true` makes the manager clone the note into the matching blog
category (`ensureNoteIsPresentInParent`) and run the generator; setting it back to `false`
moves the clone back out of the blog. So: create the note under `Unpublished/<Category>`,
set the other labels, and set `publish=true` **last**.
## Wiring / attributes (the ground truth)
- All inheritable relations live on the **blog root**: `~shareCss → blog_share_theme.css`,
`~shareFavicon → favicon`, `~shareHtml → <each script>`, `~runOnNoteContentChange/…
→ blog_generator`, `~runOnAttributeChange/Creation → blog_publish_manager`.
- Every script note carries `shareHtmlLocation` (`head:end`, `body:end`, `content:start`,
`body:start`) and `shareHiddenFromTree`.
- `feed.xml``shareAlias=feed` + `shareRaw`; `sitemap.xml``shareAlias=sitemap.xml` + `shareRaw`.
- Generator + publish manager are `application/javascript;env=backend`; generator has `run=daily`.
- Digital Garden category: `blogGarden`, `label:state` definition
(`promoted,alias=Growth Condition,single,select,options=Seed;Seedling;Sprout;Sprouting;Bud;Evergreen;Draft;Review`, inheritable), `~shareHtml → garden h1 green`.
- On import the note IDs differ → replace the `__X_ID__` tokens in scripts/generator (see README).
## Theme / design rules (keep consistent)
- **Gruvbox palette** only: bg `#282828`/`#1d2021`, fg `#ebdbb2`, red `#fb4934`,
green `#b8bb26`, yellow `#fabd2f`, blue `#83a598`, purple `#d3869b`, aqua `#8ec07c`,
orange `#fe8019`; light-mode fades from Gruvbox Light.
- **Radius system** (user decision, 2026-08-28): `8px` for code blocks/code notes,
inline code, embedded images, diagrams (mermaid/mindmap/excalidraw) and thumbnails;
`10px` for blog cards; `6px` for chips/tags/buttons/inputs; `4px` for tiny menu icons.
Embedded images and inline code use the **same 8px** radius.
- Inline code: `#content code { border-radius:8px; padding:.1em .35em }` (dark bg in both themes).
- Embedded images: `#content img:not(.blog-thumb) { border-radius:8px }`.
- **Figure captions**: `#content figcaption` is a Gruvbox chip (secondary bg, highlight
border, 8px radius, muted italic text, centered).
- **3D models** (`scripts/3d_model_viewer.js`): renders `.step`/`.stp`/`.stl` as file notes
(title ends in the extension), inline attachment links, or links to published model file
notes from text notes (resolved via `window.blogData`). The viewer is **full-bleed**
(`~100vw`, `height: min(70vh, 900px)`, ResizeObserver), with a loading progress bar and a
caption that shows `name · size · triangle count`. STEP is parsed
by **occt-wasm** (OpenCASCADE **V8**, MIT/Apache-2.0) in a **module worker**
(`new Worker(url, {type:"module"})`, non-blocking) with
`linearDeflection: 0.4, angularDeflection: 0.2`. Libraries hosted as local notes via
`__THREE_ID__`/`__THREE_ORBIT_ID__`/`__THREE_STL_ID__`/`__OCCT_JS_ID__`/`__OCCT_WASM_ID__`.
User guide: `docs/embed-3d-model.md`.
- **Mobile gotchas (both were real bugs)**: (1) mesh >65535 vertices must be split into
chunks with `Uint16` indices (`chunkGeometry`) or mobile GPUs without
`OES_element_index_uint` render nothing; (2) `geometry.setIndex(rawTypedArray)` in
three r128 leaves `geometry.index` WITHOUT `.count` → always wrap:
`setIndex(new THREE.BufferAttribute(index, 1))`; (3) tighten camera `near`/`far` from the
model size after `fit()` (`near=d·0.05, far=d·10`) for 16-bit depth buffers.
- **3D viewer in the Trilium app** (independent of the blog): `app/3d-model-viewer-app.jsx`
(widget, `#widget=3dModelViewer`, `parent: note-detail-pane`) + `app/3d-worker.js` (STEP
module worker that imports the occt-wasm bundle from a code note, so it works under the
app's CSP). The widget renders the viewer for file notes whose title ends in
`.step`/`.stp`/`.stl`.
- Images in the share are served full-size only (no resize endpoint) → keep the
`_thumb` child-note convention for small thumbnails.
- Built-in share footer navigation and book children list are hidden
(`#content-footer .navigation`, `#index`).
- **SEO**: a custom share template (`templates/share/page.ejs`, note `share template`,
`application/x-ejs`, wired via `~shareTemplate` inheritable on the blog root, with
`tree_item`/`prev_next`/`toc_item` partials as children) adds per-page canonical,
`og:url`/`og:image`/`og:type`, Twitter cards, JSON-LD (WebSite/BlogPosting/CollectionPage)
and `index,follow`. The generator's `ensureSeoMeta()` sets `shareDescription` +
`shareOpenGraphURL` on every published note and category. `robots.txt`
(`shareAlias=robots.txt`, `shareRaw`) points at the sitemap; `~shareOpenGraphImage`
on the blog root sets the default OG image.
## Limitations / gotchas
- **Backend script module whitelist is hardcoded** (`dayjs`, `marked`, `turndown`,
`cheerio`, `axios`, `xml2js`, `escape-html`, `sanitize-html`, `lodash`); `sharp`,
`fs`, `child_process`, `http` etc. are blocked. Therefore **image resizing inside a
backend script is impossible** without patching Trilium source. Do not attempt to add
sharp-based code to the generator.
- `occt-import-js` / OpenCASCADE (used by the 3D viewer) is **LGPL-2.1** (see
`THIRD_PARTY_LICENSES.md`); three.js is MIT. Both are hosted locally under `lib/3d/`.
- Triggering the generator via an attribute change on the **blog root itself** is ignored
by the self-trigger guard (`isGeneratedNoteId(BLOG_ROOT_ID) → true`). To force a full
regeneration, change an attribute/content on a **published article** instead.
- The share never resizes images; the `_thumb` child-note convention is the intended path
for bandwidth-friendly thumbnails.
- Comments/commits in the repo are English; communication with the owner is German.
## Releasing changes (do this after every change set)
1. Edit the corresponding Trilium note (live) and verify on `richard.familie-zink.org`.
2. Mirror the anonymized file(s) into this repo: replace
`richard.familie-zink.org``https://YOUR-INSTANCE.example.com`,
`Richard's Blog``Your Blog`, `by fam.zink``by the author`, and every note-ID
token → `__…_ID__` (mappings are the tokens already used in `blog_generator.js` /
`scripts/*.js`). New component notes (e.g. the app widget) get a `__…_ID__` token in
the repo reference file.
3. Check licenses: `THIRD_PARTY_LICENSES.md` must list every vendored third-party file;
original code stays GPL-3.0. Update it if new libraries were added.
4. Update docs if behaviour changed: `docs/embed-3d-model.md` (3D viewer), `README.md`
(features/layout) and this file.
5. Commit (English message) and push to `main` with the neutral identity
`trilium-share-gruvbox <trilium-share-gruvbox@users.noreply.git.familie-zink.org>`.
Never commit real note IDs, the ETAPI token, or personal data (name/domain are allowed
in the README demo badge only).
## Repo gotchas
- shields.io badges: a literal `-` in label/message must be escaped as `--` in the badge URL
(e.g. `familie-zink.org``familie--zink.org`), otherwise shields renders a "404 badge not
found" error badge.