Advanced Markdown rendering for Paseo 0.8 and 0.9: math formulas and Mermaid diagrams in assistant messages
From the plugin's README
| Limit | Value |
|---|---|
| Formula | 4096 characters |
| Mermaid definition | 32 KiB |
| Image | 2,000,000 base64 characters; 8 M raster pixels and 4096 px longest edge (large diagrams are re-rendered at a lower scale, down to 0.5x) |
| Math raster | Density 1/2/3/4/6/8; logical size independent of density; lower detail is used when an image budget is reached |
| Math queue | 1 rendering, up to 128 waiting inputs; duplicate requests share work |
| Mermaid queue | 1 running, 8 waiting per plugin process; 15 s per task |
| Caches | 128 images / 8 MiB on the host and in each client |
Rendering happens on the selected daemon host only. Message content never leaves it; the render browser runs with networking disabled.
npmjs · Paseo 0.9+
paseo plugin add npm:paseo-advanced-markdown@0.2.1GitHub · Paseo 0.8 fallback
paseo plugin add custyhs/paseo-advanced-markdown --ref dc107748be91a9a1c90b5162b546e2e095d65571Git security status and fallback installation are pinned to commit dc107748be91.
From the plugin's README
From Git, pinned to a tag (recommended):
paseo plugin add custyhs/paseo-advanced-markdown --ref v0.2.1
paseo plugin ls
Paseo runs the manifest's preparation commands on the daemon host:
npm ci, npm run build, and npm run prepare-browser. The last one prepares
the formula assets and installs the pinned Mermaid CLI runtime and Chrome headless shell into
~/.cache/paseo-advanced-markdown (or $XDG_CACHE_HOME/paseo-advanced-markdown,
%LOCALAPPDATA%\paseo-advanced-markdown on Windows; override with
PASEO_ADVANCED_MARKDOWN_CACHE, which must be an absolute path). Relative
XDG_CACHE_HOME and LOCALAPPDATA values are ignored. That directory is outside Paseo's managed
checkouts, so plugin updates reuse it and a removed plugin can be cleaned up by
deleting it.
From a local checkout:
git clone https://github.com/custyhs/paseo-advanced-markdown.git
cd paseo-advanced-markdown
npm ci && npm run build && npm run prepare-browser
paseo plugin install "$PWD"
Put --host <host:port> before plugin to target a daemon other than the
CLI's default one. paseo plugin add and plugin install mean you trust this
codebase: its server side runs unsandboxed on the daemon host.
README
Renders math formulas and Mermaid diagrams inside assistant messages in Paseo 0.8.x and 0.9.x, as an installable plugin. No Paseo fork, no patch: the official app, daemon, and plugin SDK are the only dependencies.
$…$, \(…\), $$…$$, \[…\], and ```math fences,
typeset on the host with MathJax and rasterized with resvg.```mermaid fences rendered on the host with the pinned
Mermaid CLI and a plugin-managed Chrome headless shell.v0.2.1 prepares the plugin for npm distribution with prebuilt rendering code, verified offline assets, and a complete package source-size check. See the release notes and package validation.
v0.2.0 added a shared formula/diagram viewer, mouse dragging for overflowing formulas and code, and fixes for footnote rendering and native inline formula layout. See the release notes and validation record. The latest iPhone layout adjustment still needs device confirmation; Android UI validation remains pending.
| Side | Requirement |
|---|---|
| Paseo | app and daemon >=0.8.0 <0.10.0; compiler/SDK checks cover 0.8.0 and 0.9.0-beta.2 (see the compatibility record) |
| Daemon host | Node ≥ 22.22 and npm on PATH for the preparation step, Git, about 700 MiB of disk in the plugin cache, network access during installation only |
| Text font | A font covering any non-Latin characters used inside formulas (Chinese, Japanese, Korean, …). macOS and most desktop Linux installs already have one; see Text inside formulas |
| Daemon host OS | verified on macOS arm64; Linux needs a CJK font (fonts-noto-cjk), ps (procps), the usual Chrome shared libraries, and either unprivileged user namespaces or PASEO_ADVANCED_MARKDOWN_NO_SANDBOX=1 in the daemon's environment (Ubuntu 24.04 restricts them by default); Windows is untested |
| Clients | official browser web UI verified in detail; prior rendering flow verified by the maintainer on official iOS/Paseo 0.8.0 (2026-09-14) and another Mac; v0.2.0's latest iPhone layout and Android UI remain unverified (see docs/qa/) |
Plugins must be enabled on the daemon (Settings → Plugins, or pluginsEnabled
in config.json).
Paseo also checks prereleases against their stable core, so 0.9.0-beta.2
meets this range. This is a bounded compatibility policy, not a claim that every
0.8/0.9 build has received device QA. v0.1.5 adds this compatibility range;
tags through v0.1.4 still require exactly 0.8.0. Install v0.2.1 for the current release.
From Git, pinned to a tag (recommended):
paseo plugin add custyhs/paseo-advanced-markdown --ref v0.2.1
paseo plugin ls
Paseo runs the manifest's preparation commands on the daemon host:
npm ci, npm run build, and npm run prepare-browser. The last one prepares
the formula assets and installs the pinned Mermaid CLI runtime and Chrome headless shell into
~/.cache/paseo-advanced-markdown (or $XDG_CACHE_HOME/paseo-advanced-markdown,
%LOCALAPPDATA%\paseo-advanced-markdown on Windows; override with
PASEO_ADVANCED_MARKDOWN_CACHE, which must be an absolute path). Relative
XDG_CACHE_HOME and LOCALAPPDATA values are ignored. That directory is outside Paseo's managed
checkouts, so plugin updates reuse it and a removed plugin can be cleaned up by
deleting it.
From a local checkout:
git clone https://github.com/custyhs/paseo-advanced-markdown.git
cd paseo-advanced-markdown
npm ci && npm run build && npm run prepare-browser
paseo plugin install "$PWD"
Put --host <host:port> before plugin to target a daemon other than the
CLI's default one. paseo plugin add and plugin install mean you trust this
codebase: its server side runs unsandboxed on the daemon host.
paseo plugin update advanced-markdown # tracks the ref you installed
A fixed tag does not advance to the next release. If paseo plugin update --help
lists --ref, switch an existing Git installation without removing its settings:
paseo plugin update advanced-markdown --ref v0.2.1
Older CLIs require removing and adding the plugin with the new tag; record your
plugin settings before removal, because removal deletes them. To roll back, use
v0.2.0, which supports the same Paseo version range. Tags through v0.1.4 require
exactly Paseo 0.8.0 and cannot be used to roll back on 0.9.
A failed preparation during plugin update keeps the installed version running. Updates never touch
chat history, Drafts, or other plugins.
| Content | Behavior |
|---|---|
Complete inline or display math, closed math fence |
image; click or tap to inspect and copy |
A math fence whose body is itself wrapped in \[…\], $$…$$, \(…\), or $…$ |
the redundant wrapper is ignored, the formula renders |
Complete mermaid fence: flowchart, sequence, class, state, ER, Gantt (verified); other types on a best-effort basis |
image; click or tap to inspect and copy |
| Unclosed delimiters or fences while streaming | readable source until closed |
Prices ($5 and $10), \$, inline code, other fences |
plain text / code |
Footnote markers and definitions ([^note], [^note]: …) |
readable text; formulas in definition lines render, without footnote navigation |
| Invalid TeX, invalid Mermaid, oversized input | source with a short reason; other content still renders |
| Messages with inline images or links requiring host file navigation | left to Paseo's renderer |
| User messages, tool output, other timeline rows | unchanged |
Bare $$ display math that contains a blank line is split by Paseo while
streaming; each half stays readable source. Use a ```math fence for
multi-paragraph display math.
Copy scopes: copying happens inside the viewer. In Preview, Copy LaTeX or Copy Mermaid copies the original expression or diagram body. In Source, Copy Markdown copies the exact block, including its delimiters or fence. More offers the other copy format and Copy fragment Markdown, which copies the timeline row's text. Paseo may split one long reply into several rows while it streams; this last action copies only the row containing the entry.
In messages rendered by this plugin, overflowing code blocks support mouse dragging on desktop. Select text switches to text selection; Drag to scroll switches back. Copy source copies the entire original block body in either mode. Short code blocks and native clients keep their usual text selection and scrolling.
Formula size offers 75%, 100%, 125%, 150%, and 200%, relative to the text size. This preference is shared by clients of the selected host and affects only math. Reset formula size restores 100% without changing prose, Mermaid, or module switches.
Each paragraph/list/table cell measures its own width. A formula can shrink by up to 15% to fit; longer formulas keep their reading size and scroll horizontally. An oversized inline formula moves into a scrollable block at the same source position. Short formulas do not stretch to fill the available width. On native clients, ordinary fractions, sums, and scripts stay inline, with line height reserved from their image metrics across the text run. Formulas requiring more than two normal lines move into a separate block, with adjacent closing punctuation kept beside the image. The latest native layout still needs iPhone and Android device validation. On desktop, drag an overflowing formula left or right to pan; releasing a drag does not open the viewer. Trackpad and touch scrolling remain available.
Click or tap a formula, diagram, or its source placeholder to open the shared viewer. Keyboard users can activate the same entries. The conversation shows no hover toolbar or tap-to-reveal action row. Source remains selectable, and dragging or selecting text does not open the viewer.
The viewer has Preview and Source modes. Fit shows the complete image inside the available width and height; − and + adjust temporary zoom, with scrolling for larger images. The percentage is relative to the saved reading size and does not change the Formula size setting. Diagrams start fitted within the viewer without enlargement. Use the modal's close control to return to reading. Overflowing previews also support mouse dragging. Source mode retains text selection.
Loading, failed, and disabled-module entries open on readable source. Preview becomes available when an image is ready. Recoverable failures expose Retry inside the viewer. Copy actions stay available in either mode; Copy LaTeX preserves the original expression body, excluding a redundant outer wrapper.
Sharper PNGs are requested by display density and scale, up to 8×. Available images stay visible while more detail loads. Image/payload limits can cap detail; zoom cannot provide unlimited resolution. Images have source accessibility labels, not semantic MathML navigation or selectable mathematical glyphs.
The reviewed TeX profile adds mathtools and cancel, including \mathclap,
\coloneqq, \cancel, and \cancelto. Undefined macros fail locally; custom
macro definitions do not carry across formulas. Markdown link labels keep their
existing literal behavior. See the host limitations.
MathJax's math fonts cover Latin, Greek, and mathematical symbols. Anything else,
including Chinese, Japanese, and Korean in \text{…}, needs a text font on the
daemon host. The plugin reads a font collection, plus a matching bold companion when needed, and hands them to the rasterizer; it
looks for these, in order, and the first parseable font covering all required fallback characters wins:
| Platform | Looked for |
|---|---|
| macOS | PingFang, Hiragino Sans GB, STHeiti Light, Songti, Arial Unicode |
| Linux | Noto Sans/Serif CJK, WenQuanYi Zen Hei, AR PL UMing |
| Windows | Microsoft YaHei, SimSun, Microsoft JhengHei, Arial Unicode MS |
Set PASEO_ADVANCED_MARKDOWN_FONT in the daemon's environment to use a specific
font file instead. On a minimal Linux host, install one first, for example
apt-get install fonts-noto-cjk. When no usable font is found the formula keeps
its source and says so; installing a font takes effect on the next render, with
no plugin reload. For font and renderer failures, open the source placeholder and
use Retry in the viewer. A damaged font or missing glyph returns source with
an error instead of a successful blank image. If bold text is requested,
the font must include a matching bold face; common sibling filenames such as
NotoSansCJK-Bold.ttc are discovered automatically.
Settings → Plugins → Advanced Markdown, per host:
These switches do not change Paseo's built-in Mermaid rendering for rows the plugin does not own.
| Limit | Value |
|---|---|
| Formula | 4096 characters |
| Mermaid definition | 32 KiB |
| Image | 2,000,000 base64 characters; 8 M raster pixels and 4096 px longest edge (large diagrams are re-rendered at a lower scale, down to 0.5x) |
| Math raster | Density 1/2/3/4/6/8; logical size independent of density; lower detail is used when an image budget is reached |
| Math queue | 1 rendering, up to 128 waiting inputs; duplicate requests share work |
| Mermaid queue | 1 running, 8 waiting per plugin process; 15 s per task |
| Caches | 128 images / 8 MiB on the host and in each client |
Rendering happens on the selected daemon host only. Message content never leaves it; the render browser runs with networking disabled.
Paseo gives an assistant row to the first plugin whose transformer claims it. Do not enable this plugin together with another plugin that replaces assistant rows containing math or Mermaid (for example a separate math plugin); disable one of them.
npm ci
npm run build # Markdown/MathJax bundles, formula assets, runtime manifest
npm run prepare-browser # formula assets, Mermaid runtime and browser into the cache
npm run typecheck && npm run lint && npm run format:check
npm test # parser, renderer, cache, Mermaid, fault injection
npm run paseo-source # official Paseo 0.8.0 app sources for the smoke
npm run smoke # 0.8 compiler + official projection + RPCs
HERMES_BIN=… npm run smoke:hermes
To check a newer compiler/SDK without changing the 0.8 development lockfile:
npm install --prefix .compat-runtime --no-save --package-lock=false @getpaseo/server@0.9.0-beta.2 @getpaseo/plugin@0.9.0-beta.2
PASEO_COMPAT_RUNTIME=.compat-runtime npm run smoke
The app projection/stream fixtures remain pinned to 0.8.0. The selected compiler, manifest validator, SDK registrations and RPC handlers use the selected runtime; this smoke does not replace a real 0.9 client/device check.
npm run pack
npm run smoke:package
PASEO_COMPAT_RUNTIME=.compat-runtime npm run smoke:package
npm run pack builds the generated modules and writes
.smoke/npm/paseo-advanced-markdown-<version>.tgz, plus a file inventory and integrity
hash in .smoke/npm/pack.json. It does not publish anything. The generated package
contains the runtime sources, original resvg WASM, precompiled MathJax renderer,
static MathJax SVG font data, preparation scripts,
worker lockfile, and a production npm-shrinkwrap.json. Host libraries and build
tools are excluded from its dependencies. The precompiled renderer uses the same
MathJax 3.2.2 profile as the Git source and includes its license. Unused MathJax
speech/XML dependencies are not installed; dependency overrides in a published
package do not reliably control the consuming application's dependency graph.
The repository stays private in npm metadata because its manifest prepares a Git
checkout with npm ci and a source build. The generated npm package is public-ready
and its manifest prepares the formula assets, Mermaid worker and browser. Publish the
generated .tgz, not the repository directory; do not use bare npm pack here.
Keep the package and GitHub source versions aligned before publication.
The WASM and font JSON are data files, not embedded JavaScript. Preparation checks
their SHA256 and size and copies them to content-addressed paths under the cache's
assets/ directory. Runtime reads verify the same bytes without network access.
Old asset versions are retained for running processes and rollback. For formula-only
development or to repair damaged assets, run npm run prepare-assets after building,
using the same cache environment as the daemon. Prepare before reloading a local
directory installation; directory reloads do not run manifest build commands.
Packaging counts every JS/TS source file shipped under client/, server/,
shared/, and scripts/, plus the root entries, including generated modules and
declarations. It fails above 2,000,000 bytes in total or per file, or 200 files.
The count is saved in .smoke/npm/source-budget.json and does not depend on the
community scanner's import traversal.
smoke:package installs the tarball outside the checkout with lifecycle scripts
disabled and production dependencies only. It runs the package's preparation,
moves the installation to simulate activation, compiles both entries with the
selected official Paseo compiler, and renders real
formula and Mermaid PNGs through the compiled RPC handlers. Its browser cache and
reports stay under .smoke/npm/; it does not reload the production plugin or daemon.
QA notes and evidence: docs/qa/. Licensing: Apache-2.0, see LICENSE and
NOTICE (parts adapted from q5m-ai/paseo-math).
Scanned 22 Sep 2026, 12:46 UTC from custyhs/paseo-advanced-markdown.