v10.2.0 — Reachable
V10.1 made the reads trustworthy. This release makes the project reachable — it has a face, a mark that matches it, and an install path that resolves — and it corrects three more read-path defects, one of which is V10.1's own release notes being wrong about a fix.
The landing site
Five static pages served from /docs on main, built from the Wonder design rather than re-invented: Home, How it works, Use cases, Pricing, Docs.
Hand-rolled CSS, no Tailwind CDN — no runtime dependency, no flash of unstyled content, and the whole sheet is smaller than the CDN's bootstrap. No JavaScript at all: the FAQ is <details>, the docs nav is anchors. Honours prefers-reduced-motion.
Verified in a browser rather than assumed, which caught two responsive bugs that a source read would not have: nav links wrapping to two lines between 720px and 900px, and the stacked docs sidebar putting sixteen links and labels above the first word of documentation on narrow screens.
The brand mark
BrainLLM.svg was a traced bitmap — 133 hand-plotted paths carrying several hundred near-identical blue/purple hexes, 60 KB, in a palette that appeared nowhere on the site, in the app, or in the launch copy.
It is now exact geometry, and the same figure the site's wordmark carries: a 3×3 grid on a 512 canvas, r=38 on a 92px pitch, six nodes lit in #f59e0b and three dark in #3f3f46 over a #0a0a0f plate. 60 KB → 1.6 KB. It reads as a small graph rather than a brain, which is what the product actually is — typed nodes, some connected, some not yet.
The plate is part of the mark, not a backdrop: rasterised icons land in favicon and connector slots over unknown backgrounds, and amber dots alone vanish against a light one.
BrainLLM.png is now generated by bun run icons alongside the four icon sizes. It was previously a hand-exported raster — which is precisely why the README kept showing the old artwork after the mark had changed. Every brand raster now has one source, so that drift cannot recur.
Also: full favicon set plus theme-color, and og:image made absolute — scrapers fetch it out of page context, so a relative path resolves against their host and the card renders blank.
Read path
section= and outline() disagreed on a heading's name. setSection() built <h3>\s*target\s*</h3> and tested it against raw HTML, so a heading carrying any inline markup could never match — its inner HTML is not its text. headingOutline() strips those tags. The two halves of one contract disagreed, and outline() was handing callers a name that section= would then refuse.
The refusal is not an error. A section= miss silently appends a new section — so trusting outline()'s own output produced duplicates rather than a failure, twice on one note before it was noticed. Both sides now compare stripped heading text, making the contract single-valued: any name outline() prints is a name section= matches. Five regression tests, including that invariant asserted over a body mixing plain, <code>, <strong>/<em> and entity headings.
recall(regex=) over-matched on alternation. Trilium's %= filter widens: zzzznotrealzzzz|triliumnext/trilium:latest returned five notes where three contained either alternative, and a six-term staleness sweep returned a note containing none of them. This is the tool you reach for to ask "has this wrong claim leaked anywhere else" — and a false clean sweep reports the exact opposite of the truth.
%= is now the candidate filter only; every candidate is re-tested against the real regex before it is returned, with dropped candidates reported rather than hidden. An invalid pattern errors up front instead of being handed to the backend.
This corrects the record. V10.1's notes said regex was "fixed, verified with both controls". That verdict came from one simple pattern and was over-generalised — a control proves the case you ran, not the claim you want to make.
consistency() read group 1 instead of the first group that captured. An alternation puts the value in whichever branch matched, so a([0-9]+)|b([0-9]+) leaves m[1] undefined whenever the second branch wins. The call then silently degraded to presence mode and reported "the pattern has no capture group" about a pattern that plainly has two. Found by using the tool for its intended job — a staleness sweep over this brain's own tool counts — one day after shipping it.
Audit path
The unlabeled-node sweep no longer flags the user's own folders. Extending the check to Knowledge/Master in V10.1 found a genuine invisible note and a false positive: a book the user created to group per-month notes was reported as a note that had lost its label. Knowledge/Master holds text notes, so a book with children there is human organisation. Skipped — while Knowledge/Domains stays exempt from the skip, because its children are books and genuinely want #noteType=domain.
Housekeeping
start-trilium.ps1 hardcoded the author's install path and reported port 37840 while every config example used 8080 — a documented seam that had drifted into a contradiction. It also framed the failure wrongly: the brain this repo serves runs hosted, so "tools time out" almost never means "start the desktop app". Removed, and all four references rewritten to point at the real diagnosis — the backend named by TRILIUM_BASE_URL is unreachable, and since every ETAPI call is bounded at 30s with one retry on idempotent reads, a timeout is the backend rather than the tool.
config.example.json now offers three labelled options — npx, local build, and remote — each with a comment saying when to pick it, plus a note that hosted Claude surfaces need OAuth rather than a bearer token. The npx option is only possible at all since the package gained a bin in V10.1.
Release hygiene
All seven version sites now move together. The documented rule covered six; docs/index.html gained a version pill with the landing site and was never added to the set, so the site would have advertised the previous version against this release.
These six commits sat on main for a day with no tag, no release and no npm version — everything green locally, nothing reaching a user. Worth stating plainly, since it is the same class of defect as the rest of this release: the artefact and the thing it is supposed to govern drift independently, and only one of them is real at run time.