4.9 KiB
name, description, metadata
| name | description | metadata | ||||||
|---|---|---|---|---|---|---|---|---|
| proofread-enhancements-4700 | Proofread/replacement-rule feature — sync, regex UI, Opt/Alt+P shortcut, i18n (issue |
|
Issue #4700 (FR: Proofread enhancements) — SHIPPED, merged to main via PR #4708. The proofread (校对/替换规则) find-replace feature lives in: data model ProofreadRule in src/types/book.ts; store src/store/proofreadStore.ts; engine src/services/transformers/proofread.ts; selection popup src/app/reader/components/annotator/ProofreadPopup.tsx; manager dialog src/app/reader/components/ProofreadRules.tsx (mounted in Reader.tsx); sidebar entry BookMenu.tsx.
What shipped (all test-first, full suite green):
- Sync — added
'globalViewSettings.proofreadRules'toSETTINGS_WHITELISTinsrc/services/sync/adapters/settings.ts(whole-field LWW). ⚠️ CORRECTION (the original "KEY INSIGHT" here was WRONG): book/selection-scope rules were PUSHED (serializeConfig keeps the viewSettings delta) but silently DROPPED on pull —useProgressSync.applyRemoteProgressonly consumedlocation/xpointerand discarded the rest of the synced config (the "Currently, only reading progress is synced" comment). So per-book/selection rules did NOT actually propagate across devices until the fix below. Library/global rules sync independently via the settings replica. See proofread-per-book-crdt-sync. - Regex — the transformer ALREADY fully supported
isRegex; only UI was missing. Added a Regex toggle to the selection popup AND a full "Add Rule" form (pattern/replacement/scope Book|Library/Regex/Case-sensitive) to the manager dialog, validated viavalidateReplacementRulePattern. Popup skips the whole-word validation when regex is on. - i18n — the whole-word warning in ProofreadPopup was a hardcoded English string (root cause of issue's point #2: Chinese user couldn't read it, thought symbols couldn't be replaced). Wrapped in
_()+ 8 new keys translated across all 33 locales viapnpm i18n:extract. - Shortcut — reused the existing
onProofreadSelection(ctrl+p/cmd+p).handleProofreadinAnnotator.tsxnow opens the rules manager (setProofreadRulesVisibility(true)) when there's no active selection, and opens the create-from-selection popup when there is. No new shortcut entry, no first-level toolbar button (maintainer said skip). NOTE: first attempt used a dedicatedopt+p/alt+paction — reverted because macOS Option+P is a dead-key (emits'π', not'p';useShortcutsmatches onevent.key). Ctrl+P avoids that entirely. The Annotator selection shortcuts have no unit-test harness (same as onTranslate/onDictionary), so this wiring isn't unit-tested;setProofreadRulesVisibilityitself is covered by ProofreadRules.test.tsx.
Later additions (same PR): modernized the manager dialog to the design-system primitives (SectionTitle, card eink-bordered border-base-200, input input-bordered, btn-contrast CTA disabled-until-pattern); scrollbar-to-edge via contentClassName='!px-0' on Dialog (the body's default px-6 sm:px-[10%] was insetting the inner scroll container); drag-to-reorder rules per category via @dnd-kit (mirrors CustomDictionaries.tsx — sensors, dragModifiers, SortableContext, drag-handle-only listeners). Reorder persistence = new proofreadStore.reorderRules(envConfig, bookKey, orderedIds) that rewrites only the order field (index-based) across BOTH stores (book config + global settings) in one call; the manager now sorts both displayed lists by order (stable, so default-1000 rules keep insertion order). NOTE: transformer re-buckets by scope (selection→book→library) so cross-scope drag order in the merged "Book Specific Rules" list is cosmetic — only within-scope order affects application; reordering a library rule there changes its GLOBAL order (affects all books).
Gotchas / caveats:
wholeWordfield is a near no-op in the transformer:normalizePatternalways wraps ASCII patterns in\b…\bregardless ofrule.wholeWord;isValidMatchnever reads it. It only gates the popup's pre-create validation (isWholeWordon the literal selection). So ASCII substring replacement (e.g. "cat" inside "category") is impossible today — pre-existing, out of #4700 scope.- macOS Option+letter dead-key:
useShortcutsmatches onevent.key, so anyopt+<letter>shortcut won't fire on macOS (Option+letter emits a special glyph, not the letter). Avoidopt+<letter>bindings; prefer ctrl/cmd. A robust fix would need code-based matching inuseShortcuts(deferred). - Test isolation: spying
useProofreadStore.getState().addRuleacross multiple tests leaks call counts — addvi.restoreAllMocks()in afterEach. - Run single test files with
npx dotenv -e .env -e .env.test.local -- vitest run <file>(barenpx vitestcrashes on supabaseatob).