Standalone desktop reader (Electron) for EPUB/PDF/FB2/MOBI/AZW3/CBZ with highlights, notes and optional AI. Product name: UV Reader / 柚肥阅读. UI copy, README and docs are Chinese-first. The Obsidian plugin packaging has been removed; the reader UI is migrating from an Obsidian-hosted implementation to a host-agnostic one.
packages/reader/src/— reader source.main.js(~13k lines) is still the UI entry and importsobsidian; every other module is pure (no host imports) and unit-tested.packages/host-shim/— Obsidian API compatibility layer that letsmain.jsrun inside Electron during the migration.apps/desktop/— Electron shell (main/preload/renderer) with its ownpackage.jsonand lockfile.
npm cifirst;postinstallrunspatch-pdfjs.cjs(idempotent pdf.js sentence-period fix, reverts on every fresh install).npm test— reader-core tests (node --test tests/*.test.mjs). One file:node --test tests/core-config.test.mjs; one test: add--test-name-pattern="…".npm run test:shim/npm run test:desktop— compatibility layer / shell tests.npm run check:i18n— dictionary, placeholder, HTML and URL parity across 9 languages plus exact-string assertions aboutpackages/reader/src/main.js.npx eslint packages/reader/src/ --max-warnings=0— CI treats warnings as failures.npm run desktop:install(once; behind a blocked network setELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/),desktop:build,desktop:dev,desktop:start,desktop:smoke(boots against a starter book and exits 0/1),desktop:e2e(Playwright-Electron).
CI order (.github/workflows/ci.yml): test → test:shim → test:desktop → check:i18n → eslint → desktop build → smoke under xvfb.
packages/reader/src/main.jsis the Obsidian-flavored UI being peeled apart; 15 test files and check-i18n assert exact substrings there. Rewording or moving code breaks the gates, so put testable logic in pure modules underpackages/reader/src/*.js.- Keep
packages/host-shimbehavior-compatible with Obsidian:setViewStatemust callview.setState,Modalneeds a close button/backdrop/Esc stack and must not clobberModal.scope(history filtering usesfilterScope),SettingneedssettingEl, components needselectEl/toggleEl/sliderEl, andSecretStorage.getSecretmust be synchronous. - Desktop builds write to
apps/desktop/dist/(gitignored). Never recreate rootmain.js/styles.css; those plugin artifacts are gone. packages/reader/src/pdf-cmaps-data.jsis generated byscripts/generate-pdf-cmaps.mjs; do not edit.assets/starter-books/are pinned Project Gutenberg sources (sha256 incatalog.json,-text -whitespacein.gitattributes); regenerate withnpm run build:books, never hand-edit.- esbuild aliases node builtins and
localforagetobuild-stubs/empty.js;__PDF_WORKER_CODE__and__QBR_ENGINE_VIEW_TAG__are build-time defines. Do not import node builtins frompackages/reader/src/.
- Optimize for reading, highlighting and taking notes; minimize setup steps and cognitive load.
- Use progressive disclosure. Show service/model choices, required inputs and real readiness first. Fold paths, protocols, installation commands, response customization, storage internals and maintenance. Surface blocking errors with a specific recovery action.
- Prefer existing accounts, verified defaults, standard icons and short visible labels. Do not claim a connection works before checking it.
- Unnecessary hover tooltips are off by default, including Obsidian tooltips generated from
aria-label,setTooltipand nativetitle. Preserve accessible names witharia-labelledbyor visually hidden labels, keyboard focus, visible slider values and useful error feedback. Do not hide tooltips globally or affect other plugins. - A tooltip exception needs a concrete usability reason; do not add one just to repeat an icon label or visible text. Keep it scoped to the relevant control.