Skip to content

feat(sync): bundle dictionary settings into the settings replica kind#4096

Merged
chrox merged 2 commits into
mainfrom
feat/sync-dict-bundled-settings
May 8, 2026
Merged

feat(sync): bundle dictionary settings into the settings replica kind#4096
chrox merged 2 commits into
mainfrom
feat/sync-dict-bundled-settings

Conversation

@chrox
Copy link
Copy Markdown
Collaborator

@chrox chrox commented May 8, 2026

Summary

Adds three entries to SETTINGS_WHITELIST so dictionary providerOrder, providerEnabled, and webSearches flow through the bundled settings replica with whole-field LWW. Collapses the original PR 6 (dict_provider_position) and PR 7 (dict_web_search) plans — per-element CRDT rows were over-engineered for settings users edit a few times a year, and the existing precedent (customHighlightColors, customThemes) is whole-field LWW too.

defaultProviderId (last-used tab) is deliberately excluded — that's per-device state.

What's in here

Commit 1 — bundle dictionary settings into the settings replica kind

  • Three new whitelist entries for dictionarySettings.{providerOrder, providerEnabled, webSearches}
  • customDictionaryStore.applyRemoteDictionarySettings mirrors pulled values into the in-memory store the reader popup + settings panel read from, so changes show up without a panel reload
  • Fix: saveCustomDictionaries mutated the existing settings object in place and called setSettings with the same reference — the replicaSettingsSync subscriber's reference-equality check saw no change and never published. Build a fresh settings reference instead.

Commit 2 — make dict.id stable across devices (= contentId)

  • dict.id used to equal the per-device random Math.random()-derived bundleDir, so two devices that imported the same .mdx had different ids for the same dictionary. The synced providerOrder was meaningless on the receiving side — providerOrder.filter((id) => known.has(id)) filtered every imported-dict entry out.
  • id: bundleDirid: contentId at the 4 import paths in dictionaryService.ts and in buildLocalDictFromRow. bundleDir keeps tracking the device-local on-disk path; on-disk layout is unchanged.
  • After this change providerOrder / providerEnabled are uniformly contentId-keyed end-to-end, so the bundled settings replica syncs them across devices with no seam translation.

Test plan

  • pnpm test — 4062 passed, 8 skipped
  • pnpm lint — clean
  • Two-browser end-to-end: reorder dictionaries on Device A → triggers replicas?kind=settings POST → Device B's next pull applies the same order
  • Toggle a dict, add a custom web search, edit the URL: each triggers a settings replica push
  • Fresh import: dict.id === contentId (verified via the 4-provider stardict / mdict / dict / slob import paths)

🤖 Generated with Claude Code

chrox and others added 2 commits May 9, 2026 02:34
Adds three entries to the SETTINGS_WHITELIST so `providerOrder`,
`providerEnabled`, and `webSearches` flow through the bundled
settings replica with whole-field LWW. `defaultProviderId` (last-
used tab) is deliberately excluded — it's per-device state.

The customDictionaryStore exposes `applyRemoteDictionarySettings`
so pulled values propagate into the in-memory mirror that the
reader popup and the dictionary settings panel read from. Without
this the mirror would stay stale until the next panel mount.

Also fixes `saveCustomDictionaries`: it mutated the existing
settings object in place and called `setSettings` with the same
reference, so the replicaSettingsSync subscriber saw no change
and never published. Build a fresh settings reference instead.

Collapses the original PR 6 (`dict_provider_position`) and PR 7
(`dict_web_search`) plans, which proposed per-element CRDT rows
with deterministic actor-id tiebreaks. Whole-field LWW is the
right call given how rarely users edit these on two devices at
once — same precedent as `customHighlightColors` and
`customThemes` shipping through the bundled kind in PR 5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the per-device `Math.random()` bundleDir as `dict.id` with
the cross-device-stable `contentId`. `bundleDir` keeps tracking the
device-local on-disk path, so on-disk layout is unchanged.

Touch points:
- 4 import paths in `dictionaryService.ts` + `buildLocalDictFromRow`
  in `replicaDictionaryApply.ts` set `id: contentId` instead of
  `id: bundleDir`.
- Every `providerOrder` / `providerEnabled` entry that came from
  the dict store is now uniformly contentId-keyed, so the bundled
  `settings` replica syncs them across devices without any seam
  translation.

Dicts with no contentId (very old, never synced) keep their
bundleDir as id and remain local-only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@chrox chrox merged commit 51a553d into main May 8, 2026
8 checks passed
@chrox chrox deleted the feat/sync-dict-bundled-settings branch May 8, 2026 18:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant