v3.5.5 — Win font fix, self-hosted analytics, PR #48 polish
Three things rolled into one patch: a 6-week-old Windows UI font regression finally diagnosed and fixed, telemetry migrated off Aptabase to our own Cloudflare D1 endpoint (zero third-party SaaS in the data path), and a polish PR from @beihai23 covering PDF export timeout, outline button location, and mermaid overlay contrast.
🇨🇳 The Windows UI font fix (issue #43)
A Win11 user (@sparkdonn-netizen) reported that since v0.1.12 the UI Chinese characters had gone slightly thin and inconsistent — not blurry exactly, but visibly different from native Win ClearType rendering. The picture made it obvious. The cause took longer to find.
Back in v0.1.12, commit
12b82c7
("feat(ios): iPad v0.1.11 feature integration + CJK font fix")
bundled a 43 KB LXGW WenKai subset as
"SoloMD CJK" — a deliberately-soft handwritten font we
embed for the iOS Simulator (which has no PingFang). The intent was
a fallback. But the implementation put it first in the
font-family chain, like this:
--font-ui: "SoloMD CJK", system-ui, -apple-system, ..., "Microsoft YaHei", ...
Combined with the @font-face rule's
unicode-range: U+3400-9FFF, this told every Chromium /
WebView2 / WKWebView: "for any CJK character, prefer SoloMD CJK
over everything that follows." So Windows YaHei was bypassed for
Chinese. Mac PingFang was bypassed. Linux Noto Sans CJK was
bypassed. Everywhere fell through to the bundled handwritten font,
which has thinner strokes than YaHei and looks visibly different
especially at small UI sizes (toolbar buttons, sidebar items, menu
text).
The Settings → Font picker didn't help because that picker only
controls the editor font (the CodeMirror pane),
not the UI chrome. The UI uses --font-ui, which
SoloMD CJK had hijacked.
The fix: move "SoloMD CJK" from
position 1 to last in the chain, no other changes. The
unicode-range still triggers a download only when the
bundled font is needed. On every real platform, the cascade now
finds a system CJK font first:
- Windows → Microsoft YaHei (ClearType subpixel rendering)
- macOS → PingFang SC (Quartz hinting)
- Linux → Noto Sans CJK SC
- Real iPad → PingFang
- iOS Simulator → falls through to bundled SoloMD CJK (its original purpose)
Lesson saved internally: a bundled @font-face with
unicode-range always goes last in the chain.
The unicode-range gates download, not preference; chain order
determines which font wins.
📊 Self-hosted analytics — Aptabase → Cloudflare D1
We had been using Aptabase — a privacy-respecting analytics service — to count app launches and feature usage. The dashboard showed ~2 active users despite 3,400+ downloads, which made it hard to reason about anything. Rather than diagnose the third-party SaaS, we ripped it out entirely.
The replacement is a single Cloudflare Pages Function backed by Cloudflare D1:
-
Desktop app POSTs to
solomd.app/api/track. Same data shape as before — event name, anonymous device UUID (localStorage), app version, OS, locale, a few small props. No IP, no User-Agent, no fingerprint. - Validator drops anything PII-shaped: oversized props, weird event names, malformed UUIDs. Returns 204 in every case so a flaky build can't probe the endpoint.
- Storage in D1's free tier covers us by 3× at current volume (5M reads/day, 100k writes/day, 5GB storage).
-
A token-protected dashboard at
solomd.app/admin/shows daily activity, event breakdown, version distribution, OS, locale. Bearer token insessionStorage, never in a cookie or URL.
The Tauri side dropped tauri-plugin-aptabase
entirely — including the runtime workaround for that plugin's
Windows launch crash. The frontend's
track() wrapper is now a 100-line fire-and-forget
fetch with a 2-second timeout. Opt-out toggle stays in
Settings → 发送匿名使用数据 (default on).
Architecture writeup is at web/ANALYTICS.md in the
repo. The whole change is a single commit you can read end-to-end.
🐛 PR #48 — @beihai23 polish, four small wins
Big thanks to @beihai23 (王志刚) — third PR from this contributor, all of them quality fixes:
- PDF export 30-second timeout.
markdownToPdfBlobnow wraps its work in aPromise.raceagainst a 30s timer, so html2pdf.js or Mermaid getting stuck no longer freezes the UI indefinitely. The DOM cleanup path is also idempotent now, eliminating double-remove exceptions on early errors. - Outline button moved into the tab bar. The old floating ≌ button at the top-left of the editor area is gone. Each markdown tab now has its own ≌ icon — click it to toggle the outline pane for that tab specifically. Active state is accent-colored, non-active fades on hover.
- Mermaid SVG overlay contrast. When you click a Mermaid diagram to open it in the lightbox, the SVG now gets a theme-matching background card (white on light theme, dark slate on dark theme). Solves the "Mermaid lines invisible in dark mode lightbox" bug.
- HTML cleanup. The
<table>elements in MarkdownHelp and Slideshow now wrap rows in<tbody>, eliminating a Vite build warning.
📥 Download
Download v3.5.5 → All releases
v3.0.x+ users will see an in-app update prompt within 24h. On
macOS, brew upgrade --cask solomd works too.
What's next — v3.6
Issue #44
— image and table inline rendering inside live edit mode — is
next up. It's not a CSS change; it needs a CodeMirror
block-decoration extension that intercepts image-syntax lines and
renders the actual image inline (collapsing the source
![]() when the cursor isn't on that line). Same
treatment for tables: render the formatted table when not
editing, expand to source when the caret enters.
Beyond #44: iPad / Mac App Store resubmission gates are queued, and the v4 thrust on agent-endpoint surfaces (CLI + MCP + BYOK as the public surface for "any LLM can drive this vault") continues.