All notable changes to Medict are recorded
here. The most recent release is at the top. For the full history before
v3.1.0, see git log v3.0.1..HEAD.
Platform-aware title bar, ECDICT rendering fix, and inflection fallback for the built-in dictionary.
- Platform detection (
App.Platform()): the frontend can now query the host OS (runtime.GOOS) over IPC. On Windows and Linux, where the native title bar cannot be hidden, the in-app fake title bar shrinks by 12px (26px → 14px), reclaiming vertical space for dictionary content. macOS keeps the full-height strip for window dragging. - ECDICT inflection fallback (part of #786): searching or clicking an
inflected English form now falls back to its base form —
parts→part,studies→study,boxes→box,ran/went/writtenvia an irregular-verb table,-ingforms (lying→lie,running→run), and comparatives (bigger→big,happiest→happy). Works in both exact lookup and prefix search, including in-entry hyperlink lookups. Non-English (e.g. Chinese) input is exempt from the English-only rules.
- ECDICT line breaks: ECDICT's source CSV encodes in-field line breaks as
a literal
\n(backslash-n), which the importer stores verbatim — so multi-sense definitions rendered as one run-on line with visible\ntext (10k+ rows in the bundled preset affected). The renderer now converts both the literal\nsequence and real newlines to<br>. No database reinstall is needed.
Performance: dictionary BuildIndex 51x faster, ECDICT Search 100x faster, BK-tree persistence, benchmark harness.
- Benchmark harness (#806, #807):
cmd/benchmarktool profiles both built-in dictionaries (ECDICT + cc-cedict) with per-scenario CPU/heap profiling, outputs structured JSON for AI analysis.
- ECDICT Search 100x faster (#808):
PRAGMA case_sensitive_like = ONlets SQLite use the primary key index forLIKE 'prefix%'— prefix search on 50K entries drops from 12ms to 0.1ms. Regression guard test prevents removal.
- Dictionary BuildIndex 51x faster (#781): the BK-tree (fuzzy search structure) consumed 92% of BuildIndex time (~2 minutes for 195K entries). Now built asynchronously in a background goroutine — BuildIndex drops from 2m3s to 2.4s. The UI no longer freezes when adding large dictionaries.
- BK-tree persistence: the in-memory BK-tree is now serialized to
.melev/bktree.gobafter async build. On subsequent launches it loads in ~1.6s (vs 2-minute rebuild), making fuzzy search immediately available. - Search non-blocking on BK-tree miss: if the background BK-tree build is still running, Search misses skip fuzzy (return empty) instead of blocking.
Dictionary groups, persistent definition zoom, a unified settings experience, and a more reliable offline-first default dictionary.
- Dictionary groups: organize dictionaries from the dictionary sidebar and manage groups with the same bottom action-bar pattern used by bookmarks.
- Definition zoom (#260): the search-page zoom controls now change the whole definition document in 10% steps from 80% to 160%, work in single- and multi-dictionary modes, and persist across reloads.
- Standalone CSS editor (#783): dictionary CSS editing now opens in its own Wails window, starts from the dictionary's existing styles when available, and keeps live preview support.
- Settings documentation tabs: usage, privacy, and license documents now live inside Settings with a stable sidebar and top-level document tabs.
- Offline-first default dictionary: removed the unreliable Bing default and added an in-app installer for the full ECDICT dataset while retaining the bundled subset as the always-available fallback.
- Settings and debug tools: flattened the settings hierarchy, merged version information into About, and embedded the resource-query debugger directly in the Proton-styled settings layout.
- Definition lookup interaction: hovering marks word boundaries with an underline; lookup occurs only after clicking, without a popup.
- Interface consistency: unified header alignment, dictionary icons and active state, bookmark/dictionary sidebars, action bars, controls, and footer placement. Vite was upgraded to version 6.
- Definition-link and sidebar lookups now update both the active entry and the search field, and sidebar entries remain clickable after navigation.
- Dictionary-provided cover icons are preferred, with a shared fallback icon for dictionaries that do not include one.
- Online dictionary failures degrade cleanly instead of returning unhandled 500 responses.
Hotfix: CSS editor storage moved to app config dir (fixes readonly filesystem error).
- CSS editor saves to app config dir (#783): the per-dictionary CSS
override was stored in the dictionary directory (
_medict_user.css), which fails on read-only filesystems (EROFS). Now stored under<app_config_dir>/user_css/<dictId>.css(always writable).WrapContentreads from an in-memory map loaded at startup instead of reading a file per render.
Dictionary CSS editor (CodeMirror + live preview) + photon framework restore (fixes layout regressions from v3.1.8's UnoCSS migration).
- Dictionary CSS editor (#783): a CodeMirror-powered CSS editor accessible
from the lookup-page toolbar. Edit per-dictionary CSS overrides with live
preview (client-side iframe injection, 300ms debounce — no round-trip).
Overrides persist to a sidecar
_medict_user.cssand auto-inject on every future lookup viaWrapContent.
- Layout regressions from UnoCSS migration (v3.1.8): the photon CSS framework was retired in v3.1.8 and replaced with UnoCSS shortcuts that didn't match photon's exact layout — causing sidebar lists to disappear, settings sidebar to go horizontal, main layout overflow, and toolbar button misalignment. Photon is now restored (the UnoCSS token color unification is kept); the conflicting shortcuts removed.
Default EN-CN dictionary (offline ECDICT + free online Bing), recursive dict-scan fix, a frontend style unification (UnoCSS), and frontend test infrastructure.
- Default English-Chinese dictionary (#794, #795): Medict now ships with an
offline EN-CN dictionary out of the box — a curated ECDICT subset (~50k
high-frequency words, embedded, no key, no network) as the default, plus a
free Bing online dictionary (scrapes
cn.bing.com/dict/clientsearch, no API key). Both appear in the dict list and participate in multi-dict / hover-popup / Anki export like any other dictionary. - Frontend test infrastructure (#801): introduced vitest + happy-dom +
@vue/test-utils; first batch of unit tests covering the 3 Pinia stores (ui / bookmark getters / dictbuildEntryURL).
- Frontend style unification — UnoCSS (#800): adopted UnoCSS
(presetWind3, Tailwind-compatible) as the atomic CSS layer; established a
single design-token source (
tokens.ts: brand#326cb8, gray scale, danger, font stacks) shared by both UnoCSS and the naive-uithemeOverrides— no more off-brand colors. All components' hardcoded colors unified to tokens; the legacy GitHub photon CSS framework retired (−2352 lines, classes replaced by UnoCSS shortcuts).
- Recursive dict-scan (#257, #797): nested dict directories (e.g.
English/OALD9/oald9.mdx) are now found exactly once — category folders are no longer mis-attributed as dictionaries, and duplicates are eliminated.
Multi-dict query, Anki export, hover-popup lookup, and a bookmarks v2 overhaul.
- Multi-dict simultaneous query (#776, #778): a 「多」toggle in the dict toolbar turns the dict icons into multi-select; searching then queries every selected dictionary and stacks the definitions vertically (GoldenDict-style). The active set is persisted across restarts via the settings write-back (#777).
- Export bookmarks to Anki (#774, #775): the 生词本 page can export its words
to a native
.apkg— each notebook becomes an Anki deck, and each card carries the saved HTML snapshot with images extracted as Anki media (validated against Anki's own importer). - Hover-popup word lookup (#780): hovering a word in a definition for ~0.4s
pops up its full definition near the cursor (macOS Dictionary / GoldenDict
style). Double-click lookup (#258) and
entry://jumps are unchanged. Toggle via thehoverpopuppreference. - Export current entry as HTML (#784): a toolbar button saves the currently displayed entry as a self-contained HTML file (resources inlined) for debugging complex entries.
- Settings write-back (#777): user preferences now persist to
medict.toml(the foundation used by multi-dict selection and the hover-popup toggle).
- Bookmarks v2 (#772): the 生词本 store migrated to a pure-Go SQLite store with notebooks (create / rename / delete / set-default) and offline HTML snapshots — saved words stay viewable even after the source dictionary is unloaded.
- Dictionary display name (#782, #788): the dict list now shows the real
dictionary title (mdx header
Title/ stardictbookname) instead of the file name — including at first load, before the index is built.
Header UI redesign + bookmarks nav-rail fix.
- Bookmarks page missing navigation (#768): 生词本 page lacked
<AppHeader>, making it impossible to switch tabs. Fixed. - macOS double title bar: removed the redundant 26px fake-title-bar
(
Frameless:falsealready provides a native title bar).
- Header redesign: logo shrunk (h1 24px→16px, area 120px→72px), search bar
now
flex:1(fills width), back/forward/star buttons restyled (transparent bg, hover, gap spacing), header height 60px→48px.
Hotfix: bookmarks page navigation.
- Bookmarks page missing navigation rail (#768): the 生词本 page was missing
<AppHeader>(which containsAppFunctions), so users could not switch tabs after entering it. Now wrapped in the standardx-layoutshell.
Bookmarks / saved-words feature + navigation restructure.
- Bookmarks / 生词本 (#643): save words while looking them up (star button in
the search bar), then browse and re-lookup them in a dedicated 「生词」tab.
- Star button next to back/forward in the search bar — saves the current word
- dictionary context.
- Full-page bookmarks list with search/filter, click-to-lookup, and delete.
- Persisted to
bookmarks.jsonin the app config directory.
- Star button next to back/forward in the search bar — saves the current word
- Navigation restructure: the left sidebar tabs are now 搜索 / 生词 / 词典 / 设置. Plugins and Debug moved into the Settings page as sub-navigation items (插件设置 already existed; 调试工具 newly added).
New features + bug fixes from the stale-issue review (#258/#259/#260), plus the TypeScript strict-mode type-safety gate (#702) and backend/frontend cleanup.
- Double-click to look up a word (#258): double-clicking text in a dictionary entry selects it (using the browser's CJK-aware word-boundary selection) and looks it up — matching macOS Dictionary.app behavior.
- Keyboard & wheel zoom (#260):
Ctrl/Cmd + =/-andCtrl/Cmd + scrollnow zoom the definition iframe (previously only toolbar buttons).
@@@LINKmulti-target & chained redirects (#260): a headword redirecting to multiple targets (e.g. 滋 → 滋 + 滋) no longer shows blank; chained redirects (A→B→C) now follow through instead of stopping at the first hop or leaking raw@@@LINK=text. Cycle-guarded, max depth 8.- Dictionary name truncation (#259):
mdict.Name()usedTrimRight(cutset) which ate trailingm/d/xchars —mad.mdxbecamema. Fixed toTrimSuffixof the actual extension.
- TypeScript strict mode (#702):
tsconfig.jsonnow hasstrict: true;vue-tsc --noEmitpasses with zero errors and runs as a CI gate. All 66 strict errors fixed (typedqueryPendingList/selectDict, debounce params,$onActioncallback,*.vueshim, etc.). - Backend naming consistency (#737):
back_server.go+back_server_inner.gomerged into one file;DictCon → Controller,MainDict → Dict,dc.ds → svc. - Startup logging (#735):
[medict-init]bootstrapfmt.Printf→ logger.
Maintenance release: internal architecture cleanup — no user-visible behavior
change. Three refactors land together (all backend builds + frontend bun build
green; CI passing).
- Typed IPC (#729): the frontend↔backend call path no longer goes through a
string-dispatched
App.Dispatch(apiName, map)+ runtime type assertions. Each handler is now a typedAppmethod (InitDicts/GetAllDicts/SearchWord(dictId, word)/BuildIndexByDictId(dictid)), the same proven Wails native-binding pattern the app already used forResourceServerAddretc. RemovesDispatch/handlerMap/DispatchIPCReqand three dead no-backend frontend calls. DictServicedependency injection (#727): removed the package-global singleton (sync.Once+GetDictService());Appnow owns the*DictServiceand injects it intoBackServer.SetUp. Improves testability and constructor injection.- Startup/query log trimming (#735): per-Locate/Lookup
INFOlogs in go-mdict and the holder downgraded toDEBUG; two strayfmt.Printfremoved. Reduces log I/O and noise during startup/indexing. (BuildIndex's per-keyword path was already log-free.)
Note: typed IPC changes the frontend↔backend binding contract internally; the typed-binding mechanism is already in use app-wide, so runtime behavior is unchanged.
model.Resp.datatyping (as unknown ascasts) is unaffected — that's tracked separately in #702.
Patch release: fixes a live crash during index build, entry:// link
navigation, and a class of keyword-index correctness bugs from the indexer
review (#722), plus architecture cleanups. No data-format breakage (existing
.melev caches are rebuilt once on the new schema).
- Live panic on index build (#724): the leveldb connection pool could hand
out a nil connection and panic during
BuildIndex. Pool removed; a single shared*leveldb.DBhandle is used (goleveldb is concurrency-safe). entry://links with=///.(#718/#723): no longer trigger an OS "There is no application set to open the URL" dialog; in-iframe jumps work.- Prefix search for P/F/K/W-initial words (#723): a
TrimLeft-vs-TrimPrefixbug had collapsed distinct words onto one key and broken prefix search. - Same-headword entries / homographs (#742): the leveldb key now carries the
record offset, so "bank" sense 1 & 2 no longer overwrite each other — both
surface in search. (One-time index rebuild via
schema_versionbump.) - Dictionary list ordering (#740): sorted by name, not by dir-path MD5.
- stardict (#739):
Lookup/LookupResourcereturn a cleanErrNotFoundinstead of swallowing errors / returning empty 200s. - macOS release now ships a
.app.zipalongside the.dmg(#721).
- Indexer
Closelifecycle end-to-end (LvDB → indexer → holder → service →App.shutdown); the.mdxparser opens/closes per op, so no handle leak (#731). - Index built via a single leveldb batch write; the fuzzy BK-tree is built
from the leveldb index (no
.mdxre-parse on cache hit) (#741). model.ErrNotFoundsentinel — a normal miss is distinguishable from a real error viaerrors.Is(#732).- Word lookup is now an explicit Gin route (
GET /__mdict/__tcidem_query); resource lookups stay a catch-all (#728). - Boilerplate reduction, dead-code removal, log consolidation (#734/#735/#736/#737/#738).
First release since v3.0.1. Bundles the reliability, fuzzy-search, and build modernization work from the 2024–2026 cycle.
- Mdict fuzzy search (#666 / #697): lookups are prefix-first, with an in-memory BK-tree (character-level Levenshtein, tolerance 2) fallback when the prefix misses — so near-misses still find the entry.
- leveldb indexer is now the active index engine; each
.mdxgets a.melevsidecar cache, and the BK-tree is rebuilt lazily for users whose cache skipsBuildIndex. - PR-level CI (backend
go build/vet/test+ frontendbun build) on every pull request todevelop/master. (#701) - Architecture guide (
CLAUDE.md) documenting the two-channel (Wails IPC + embedded Gin server) design for contributors and AI assistants.
entry://links whose IDs contain=,/,.etc. (e.g.entry://topic_transport-by-water_level=c1) no longer trigger a system "There is no application set to open the URL" dialog — in-iframe jumps now work, with a client-side click interceptor as a safety net. (#718)- Word-lookup main flow (#707): declarative iframe, fixed a global
keyboard-listener leak, and a last-write-wins guard on
searchWordso fast consecutive lookups no longer race. - Startup robustness (#705): init failures surface as a dialog instead of crashing the app.
- History stack back/forward (#699): a
keyword/key_wordfield mix-up broke navigation; the field is now unified tokeyword. - Wails IPC browser fallback (#700): the browser path returned
undefineddue to a missingreturn. GracefulStopno longer callslog.Fatal/os.Exiton a shutdown error. (#683)DictServicesingleton is now race-free (sync.Once).- CORS restricted to a local allowlist instead of reflecting any
Origin. - Correct
Content-Typeforwoff2fonts; correct static-server URL prefix check. - Stardict index type distinguished from Mdict; symlinked dictionary directories are followed.
- Frontend package manager migrated pnpm → bun. (#695)
- Backend loggers given meaningful per-package identifiers (#704);
DictServicelock granularity tightened so no I/O runs while holding the lock. - Frontend dead code and unused dependencies removed. (#703)
- Bumped to Go 1.25; upgraded
golang.org/x/*andlogrus. (#690) - Re-enabled and modernized the release pipeline: tag-triggered, Go 1.25, modern actions, three platforms (macOS universal, Linux x86_64, Windows x86_64), correct version injection on every platform.
- Cleared
develop-branchgo vet/test debt to keep CI green. (#715)
Note: the previous automated release workflow had been silently failing on every
developpush for months (stale Go version, retired runners, deprecated actions). It has been replaced by the tag-triggered pipeline above.