i18n: settings.privacy.* keys for encryption disable/rotate/recovery UI (#338) - #343
Conversation
…ble/rotate (#338 items 1+2) Replaces the clearIdbPassphrase/rotateIdbPassphrase stubs with real journal-backed migrations: new primary-store adapters (images, binder assets, codex, app-data, snapshots, RAG vectors) plug into the existing secondary-store adapters via a new encryptionMigrationOrchestrator.ts, giving the previously test-only migration engine its first production callers. Disable/rotate now migrate and verify every protected store before touching the passphrase sentinel, and fail closed into the resumable journal (not a partial verifier change) on error. Breaks a circular import (storageEncryptionService -> orchestrator -> adapters -> storageEncryptionService) via a dynamic import at the two call sites, matching this codebase's existing dynamic-import convention for avoiding heavy/circular static coupling (listenerMiddleware.ts, aiApi.ts). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…st encryption (#338 items 1+2) Extends PassphraseModal with 'disable'/'rotate' modes (live migration progress bar), adds Disable/Change-passphrase buttons to PrivacySection, and wires both through useSettingsView to the real clearIdbPassphrase/rotateIdbPassphrase implementations. Adds EncryptionRecoveryModal + an App.tsx startup check: if a disable/rotate journal was interrupted (reload/crash) before reaching 'completed', it takes priority over the normal unlock flow and lets the user re-enter their passphrase(s) to safely resume — CryptoKey material is never persisted, only re-derivable. A 'recovery-required' journal (the migration's own verification found an inconsistency) is surfaced as a distinct, honest stuck state rather than a fake auto-fix. Refactors clearIdbPassphrase/rotateIdbPassphrase's post-migration commit steps and key/verifier derivation into shared helpers so resumeEncryptionMigration() (used by the recovery UX) reaches the exact same end state as a fresh-start migration. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ry UX (#338) Real-data round-trip tests (image, codex 3-shape dispatch, binder asset Blob<->bytes, RAG vectors aggregate<->individual duality) through rekey then disable — the prior disable/rotate tests only proved migration completes over empty stores. Adds resumeEncryptionMigration coverage (correct resume, wrong source passphrase, missing target passphrase for rekey), new EncryptionRecoveryModal component tests, and disable/rotate mode + progress-bar tests for PassphraseModal, PrivacySection's new Change/Disable buttons, and useSettingsView's handlePassphraseConfirm branches. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Updates CLAUDE.md § At-rest encryption and Known Technical Debt, docs/IDB-ENCRYPTION.md, and ADR-0018's status line to reflect the production migration wiring implemented in this branch — the orchestrator, primary-store adapters, protectedWriteAdmission cross-tab gate, and EncryptionRecoveryModal. Remaining gap noted honestly: no E2E coverage yet for the disable/rotate/recovery round trips (unit + component tests only so far). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ecovery UI (#338) 9 new keys (disable-failed, migration-progress + label, recovery title/body/stuck/ resume, changed-status) added to en/settings.json and propagated to all 19 locales via check-i18n-keys.mjs --fix. Real translations for the 5 production locales (en + de/es/fr/it); the remaining 14 carry the English fallback per this repo's tiered i18n convention. Rebuilds every public/locales/<lang>/bundle.json. Stacked on feat/encryption-migration-phase4-b to keep that PR's code-only diff under the ~100-file threshold the review bot needs to actually run. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes the doc-metrics drift check.mjs flagged after adding 9 settings.privacy.* keys for the encryption disable/rotate/recovery UI in the previous commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Skipping CodeAnt AI review — this PR changes more than 100 files, which usually means a migration, codemod, or vendored drop. Line-level review on diffs this large produces duplicate findings on the same rewrite pattern and drowns out anything that actually matters. If you still want a review, comment |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (144)
📝 WalkthroughWalkthroughThis PR updates locale source files and generated public bundles across multiple languages. It adds heuristic, assisted-mode, fallback, grammar, diagnostics, and encryption-warning strings. It also groups related translation keys and removes duplicate placements. ChangesLocalization catalog alignment
Estimated code review effort: 2 (Simple) | ~15 minutes Possibly related PRs
Suggested labels: ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Reviewer's guide (collapsed on small PRs)Reviewer's GuideAdds new settings.privacy encryption-related i18n keys and propagates them across all 19 locales and bundled JSON files, while updating README i18n key counts and keeping documentation metrics in sync. File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
🏁 CodeAnt Quality Gate ResultsCommit: ✅ Overall Status: PASSEDQuality Gate Details
|
Code Review by Qodo
1.
|
PR Summary by Qodoi18n: add settings.privacy encryption disable/rotate/recovery strings
AI Description
Diagram
High-Level Assessment
Files changed (145)
|
…Y false positive gitleaks' generic-api-key rule flags PASSPHRASE_SENTINEL_RECORD_KEY = 'idb_passphrase_sentinel_v1' (services/storage/idbPassphraseSentinel.ts, mirrored as a mock constant in storageEncryptionService.test.ts) purely because the variable name contains "PASSPHRASE" + "KEY" next to a sufficiently long, structured string. It's a static IndexedDB record-key identifier, not a credential — the app must know it deterministically to find the sentinel record via store.get/put/delete. Verified locally against the exact PR commit range with gitleaks 8.24.3 (matching CI): confirmed the finding reproduces without this config, and is suppressed with it, while an unrelated real-looking secret (Stripe key pattern) is still caught — the allowlist does not weaken detection elsewhere. No .gitleaks.toml existed before this; [extend] useDefault = true keeps every default rule active. Scoped to the exact leaked value only (not combined with a path filter) since gitleaks' top-level [allowlist] ORs paths/regexes rather than ANDing them — combining both would broaden the exemption to "anything in that file" instead of narrowing it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Independently flagged by CodeAnt, CodeRabbit, and qodo-code-review on PR #342: 1. Disable/rekey recovery can permanently strand an interrupted migration. commitDisableMigration deletes the sentinel/salt BEFORE completing the journal; commitRekeyMigration saves the new sentinel BEFORE completing the journal. If completeEncryptionMigration() fails after either mutation, the journal stays 'committing' but resumeEncryptionMigration's source-key derivation now fails — the sentinel it needs is already gone (disable) or already replaced (rekey) — permanently blocking recovery. resumeEncryptionMigration now special-cases a journal already at 'committing': for disable, an absent sentinel means the commit already ran — finish the journal bookkeeping without deriving a key. For rekey, if the old passphrase no longer decrypts the sentinel, verify the new passphrase against the journal's durable target verifier (independent of the sentinel) before concluding the commit already happened, instead of reporting a false wrong-passphrase error. 2. Blob optimistic-write check only compared size + MIME type, not bytes. Two Blobs can share both while holding different content (e.g. a concurrent binder-asset re-upload), so the migration's freshness check would silently accept a stale value and overwrite a newer asset. valuesMatch is now async and compares actual bytes for Blobs. writeBatch is split into a read phase (readCurrentValues) and a write phase (putValues) so the async Blob comparison never happens inside a live IDB transaction's request handler — safe because the whole batch already runs under an exclusive withMigrationAdmission() lock, so no writer can interleave between the two. 3. The app-data primary-store adapter corrupted IdbKeyStore records. APP_DATA_STORE holds project/settings data AND IdbKeyStore's raw non-extractable CryptoKey (local_crypto_key_v2) plus per-provider encrypted API keys (api_key_<provider>_enc/_iv, legacy gemini_api_key_*). The adapter only reserved the migration journal + passphrase sentinel keys, so enabling/rotating/disabling encryption would JSON.stringify the CryptoKey (destroying it) and re-encrypt the already-encrypted API-key bytes as if they were plaintext project data — permanently losing every stored API key. idbKeyStore.ts now exports isKeyStoreRecordKey(), wired into the app-data adapter via a new isReservedKey predicate (reservedKeys alone can't express the dynamic per-provider key family). New regression coverage: 6 resumeEncryptionMigration committing-phase tests (both operations × not-yet-committed / partially-committed sub-states), a real disable+rekey round-trip proving a stored API key survives migration, and 11 direct unit tests for valuesMatch's Blob comparison + isKeyStoreRecordKey. All 98 pre-existing storageEncryptionService tests still pass unmodified. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- ErrorBoundary: new `fallback` render-prop lets a boundary override the default non-blocking ErrorFallback; wired into App.tsx's EncryptionRecoveryModal boundary so a crash inside the recovery modal still renders a blocking, focus-trapped Modal instead of leaving the app underneath interactive during a pending disable/rekey recovery. - Condense every wrapped QNBS-v3 comment introduced this PR (App.tsx, EncryptionRecoveryModal.tsx) to the required single physical line. - docs/adr/0018: fix stale "disable/rekey rejected outright" and "cross-tab admission not shipped" claims to match the current implementation. - docs/IDB-ENCRYPTION.md: correct the protected-write call-path description — save paths snapshot the key via resolveProtectedWriteKey() then re-check only assertNoActiveEncryptionMigration() pre-transaction; delete paths call assertIdbProtectedWriteAllowed() directly. - tests/unit/storage/storageEncryptionService.test.ts: give every test a fresh IDBFactory instead of one shared factory for the whole file, via new _resetDbForTest()/_resetSentinelStoreForTest() exports alongside the existing __resetEncryptionMigrationJournalConnectionsForTest() — closes a latent cross-test leak where an encrypted record from one test could outlive its salt/sentinel into a later test. - tests/unit/hooks/useSettingsView.test.ts: add the rotate-failure coverage mirroring the existing disable-failure test. - tests/unit/ErrorBoundary.test.tsx: cover the new fallback prop. Assessed as already resolved by earlier commits in this PR (no further change needed, verified against current code): - PassphraseModal.tsx's rotate-mode catch already distinguishes a credential-mismatch IdbWrongPassphraseError (only ever thrown by the pre-migration old-passphrase check) from any other migration/journal failure, with test coverage for both paths. - primaryProtectedStoreAdapter.ts's verify()/migrateNext() both skip isReservedKey() records via readBatch(), so clearGeminiApiKey()/ clearApiKey() can no longer desync checkpoint.processed vs. verified — isKeyStoreRecordKey() already excludes them from migration scope entirely. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- src-tauri/osv-scanner.toml: add the GHSA-jmr9-qjv8-65gv (extract-zip) ignore entry already present on the sibling fix/333-download-progress-metrics branch — no fixed version exists, transitive Playwright devDependency only. - .gitguardian.yaml: extend the existing "Generic Password" false-positive path exclusion to storageEncryptionService.test.ts, which pairs fake sourcePassphrase/targetPassphrase fixture literals ('original', 'new-pass', 'wrong-old', ...) exercising the disable/rotate/recovery flows against fake-indexeddb — same detector, same false-positive class already documented for the locale trees above. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
settings.privacy.encryptionDisableConfirm was updated in English to a
detailed warning ("This will decrypt all your data and turn off at-rest
encryption...") but the other 18 locales still carried the old short
"Disable encryption" label. Since PassphraseModal renders this key as the
disable-mode warning paragraph (not a button label), non-English users
were seeing a misleading warning for a sensitive, irreversible-feeling
action. Translated properly for all 18 remaining locales and rebuilt bundles.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The IgnoredVulns entry itself was already added to src-tauri/osv-scanner.toml in an earlier commit on this branch; this adds the AUDIT.md documentation the repo's own housekeeping policy requires for accepted dependency risk. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… commits Resolves overlapping edits between this stacked i18n branch and #342's own correction-loop work, which also touched locales/*/settings.json (both English source wording and several locales' translations for the encryption disable/rotate/recovery UI strings evolved independently after this branch diverged). Conflicts in the 19 locales/*/settings.json files were resolved with a scripted 3-way per-key JSON merge (git's textual merge conflates adjacent JSON lines into hunks even when only some individual keys actually conflict) using this policy: - Only one side changed a key from the merge base -> take that side. - Both changed it identically, or only one side has a real (non-English) translation -> take the real translation. - Both sides have differing real translations -> take #342's (verified this is not arbitrary: #342's non-English wording for every such key matches #342's own updated English source exactly, confirming its correction loop updated English + every translation together as one coherent unit, while this branch was simply stale relative to that whole update). - ar/he/fa (documented English-fallback RTL stubs) -> always track #342's newer English source on conflict. - The one key where this branch's content was genuinely unique and never touched by #342 (settings.privacy.encryptionDisableConfirm's fuller, more informative EN copy) was preserved. All 19 public/locales/*/bundle.json files were regenerated fresh via `pnpm run i18n:bundle` rather than hand-merged (2914 keys x 19 locales, clean). Every other locale module file (characters/common/outline/worlds/ writer/etc.) this branch touches had no overlap with #342 and merged automatically with no conflicts. All non-locale code/test/doc files from #342's correction loop also merged automatically with no conflicts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 36028823 | Triggered | Generic Password | 0ddcb44 | tests/unit/storage/storageEncryptionService.test.ts | View secret |
| 36028823 | Triggered | Generic Password | 423c115 | tests/unit/storage/storageEncryptionService.test.ts | View secret |
| 36042393 | Triggered | Generic Password | 423c115 | tests/unit/storage/storageEncryptionService.test.ts | View secret |
| 36028823 | Triggered | Generic Password | 423c115 | tests/unit/storage/storageEncryptionService.test.ts | View secret |
| 36028823 | Triggered | Generic Password | 8cd4989 | tests/unit/storage/storageEncryptionService.test.ts | View secret |
| 36042393 | Triggered | Generic Password | 8cd4989 | tests/unit/storage/storageEncryptionService.test.ts | View secret |
| 36028823 | Triggered | Generic Password | 8cd4989 | tests/unit/storage/storageEncryptionService.test.ts | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secrets safely. Learn here the best practices.
- Revoke and rotate these secrets.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
These 9 threads were still unresolved from an earlier review pass on this
branch, surfaced while preparing to merge all 5 open PRs:
- services/storage/storageEncryptionService.ts: fixed a real data-integrity
bug in resumeEncryptionMigration's committing-phase rekey recovery. The
try block wrapped deriveAndVerifySourceKeyFromSentinel,
deriveAndVerifyTargetKeyFromVerifier, AND commitRekeyMigration together, so
a commitRekeyMigration failure (after both passphrases had already verified
successfully) fell into the catch block, which then re-verified the target
passphrase, concluded "the commit must have already happened," and cleared
the journal — even though commitRekeyMigration had genuinely failed and the
durable sentinel could still hold the old passphrase while stores were
already re-encrypted under the new key. With no journal left, neither
passphrase could unlock the library. Isolated source-passphrase
verification into its own try/catch so only a genuine source-verify
failure enters the "maybe already committed" recovery branch; a
commitRekeyMigration failure now propagates as a retryable error with the
journal intact.
- tests/unit/storage/storageEncryptionService.test.ts: added the missing
hasPassphraseSentinel() assertion to the "rejects when neither passphrase
matches" test, matching its disable counterpart, so a regression that
clears the journal/sentinel on a failed recovery would fail this test.
Renamed all fixture passphrase literals ('original', 'new-pass',
'wrong-old', 'wrong-new') to an unambiguous 'test-fixture-*-passphrase'
pattern to reduce GitGuardian false-positive risk.
- .gitguardian.yaml: documented why the test file's path exclusion remains
(occurrence-scoped ignored_matches would be the narrower ideal, but
computing their SHA256 hashes requires the ggshield CLI + an authenticated
scan, unavailable in this environment) now that the fixture rename above
reduces the practical risk the exclusion covers.
- 7 locales (el, fi, fr, it, ko, pt, ru): fixed mistranslated or
inconsistent encryption-UI strings (Greek "disable" read as "make
incapable"; Korean used "unused" instead of "at rest" for encryption, and
a literal-translation "disable" verb instead of the idiomatic term; Russian
"disable" read as "forbid"; French/Italian's stuck-recovery message named
"the data" instead of "the migration" as unresumable; Finnish mixed
"salasana"/"tunnuslause" for passphrase inconsistently; Portuguese's AI
provider ready-status showed the infinitive "prepare" instead of "ready").
Rebuilt all 19 runtime bundles via `pnpm run i18n:check` (2914 keys x 19
locales, clean).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CI's doc-metrics drift gate (scripts/check-doc-metrics.mjs) caught this: the i18n additions from this branch's own correction-loop work plus the merged-in stacked i18n branch raised the key count from 2904 to 2914, but README.md's four "2904 keys" mentions (badge + 3 prose references) were never updated to match. Regenerated via `node scripts/sync-readme-metrics.mjs`. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PR #342 was squash-merged into main, creating a new commit distinct from the individual commits this branch had already merged in from feat/encryption-migration-phase4-b. This produced a fresh set of textual conflicts in the 19 locales/*/settings.json files, but only ever the same single key already resolved once before: settings.privacy.encryptionDisableConfirm. This branch's own translation work (in all 19 languages, not just English) gives it a fuller, more informative warning than main's terse placeholder — kept this branch's version everywhere, matching the resolution already applied to this same key in the prior merge commit. Rebuilt all 19 public/locales/*/bundle.json bundles fresh via `pnpm run i18n:check` (2914 keys x 19 locales, clean) rather than resolving their conflicts by hand. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Real conflicts (not just textual noise) resolved: - hooks/useSettingsView.ts + tests/unit/hooks/useSettingsView.test.ts: this branch's D5 useMemo-wrapped context-value fix was missing `migrationProgress` from its returned object — main's independently-added encryption feature had it, this branch's earlier refactor didn't carry it forward. Merged both: kept the useMemo optimization, added migrationProgress to the memoized object and its dependency array. Test mocks reconciled the same way — kept this branch's stable-toast pattern (the actual regression test for the D5 fix) while aliasing mockToastInfo/mockToastSuccess to stableToast's own methods so the existing encryption tests' assertions against those names keep working. - CHANGELOG.md: two genuinely distinct "### Fixed" entries from different PRs (#332's desktop boot/SettingsView fixes vs. #341/#344's AI Writing Studio readability fixes) — kept both. - README.md + locales/*/settings.json (el/fi/hu/is/pt/sv) + their bundles: mix of stale i18n key-count badges (regenerated via `node scripts/sync-readme-metrics.mjs` post-merge rather than guessing) and the same encryptionSetupFailed/providerStatusReady translation conflicts already resolved once in the previous merge commit — reapplied the same per-key resolution (whichever side has the real, non-English translation). Rebuilt all 19 bundles fresh via `pnpm run i18n:check` (2915 keys x 19 locales, clean) rather than resolving bundle.json conflicts by hand. - src-tauri/osv-scanner.toml: cosmetic-only comment-header conflict (both sides already had the identical extract-zip IgnoredVulns entry) — kept the more current review-date comment. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Real conflicts resolved: - CHANGELOG.md: three genuinely distinct "### Fixed" entries from different PRs (#341/#344's AI Writing Studio readability fixes, already in main, vs. this branch's own voice-download-progress-scale fix) — kept both. - locales/pt/settings.json + its bundle: same providerStatusReady ("Preparar" infinitive vs. the already-fixed "Pronto") conflict already resolved on the other branches this session — took main's already-correct side, since this branch never touched that key. - README.md: stale i18n key-count/test-file-count badges — regenerated via `node scripts/sync-readme-metrics.mjs` post-merge rather than guessing (2918 keys × 19 locales, 547 test files). - src-tauri/osv-scanner.toml: cosmetic-only comment-header conflict (both sides already had the identical extract-zip IgnoredVulns entry) — kept the more current review-date comment. Rebuilt all 19 public/locales/*/bundle.json bundles fresh via `pnpm run i18n:check` rather than resolving bundle.json conflicts by hand. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ontradictory local-only doc - CHANGELOG.md: the [1.27.0] section never documented its own headline feature (#342/#343's disable-encryption/passphrase-rotation production wiring) and still carried the prior release's "intentionally unavailable ... see issue #338" caveat, directly contradicting what shipped in this same version (chatgpt-codex-connector). Added the missing entry, qualified the stale caveat as describing that point in time, and corrected the Docs section's "opened issue #338" line to note it closed in this release. - docs/DEEPSOURCE-REVIEW-LOOP.md: two restored local-only tracking commits (originally discarded by an earlier `git reset --hard`, recovered via cherry-pick) carried "LOCAL-ONLY — not pushed to remote" / "do not push this entry upstream" wording that became false the moment they were committed to a pushed branch (qodo-code-review, coderabbitai). Removed the contradictory local-only framing, keeping the content as a normal dated log entry. Also softened the JS-0440 remediation TODO's "consider a rule-level ignore repo-wide" suggestion per coderabbitai's security concern — a blanket ignore would hide future unsafe `dangerouslySetInnerHTML` uses, not just the one reviewed occurrence. The public/sw.js QNBS-v3-comment findings (qodo-code-review, coderabbitai) were false positives — the existing single-line QNBS-v3 comment on the line directly above APP_VERSION was already unchanged by this PR's one-line version-bump diff; verified via `git diff main~1 -- public/sw.js`. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…reliability fixes (#351) * docs(deepsource): log re-surfaced JS-0440 finding (local-only, not for push) Co-Authored-By: GitHub Copilot (Claude Sonnet 5) <noreply@github.com> * docs(deepsource): correct stale JS-0440 disposition, re-surfaced 2026-08-01 (local-only) Co-Authored-By: GitHub Copilot (Claude Sonnet 5) <noreply@github.com> * release: v1.27.0 — Phase 4 encryption production wiring + desktop/AI reliability fixes Closes out issue #338 (Phase 4 of the at-rest encryption lifecycle): disable encryption and passphrase rotation are now live in Settings › Privacy, backed by the durable resumable migration journal built in the prior release. Also ships three independently-diagnosed reliability/UX fixes: - Tauri desktop cold boot never read persisted state back (#332) — every desktop launch loaded as a brand-new user regardless of what was actually saved to disk; boot hydration now mirrors the already-correct save path. Quitting also now awaits any pending debounced autosave instead of risking a mid-debounce data loss. - AI Writing Studio manuscript text was unreadable, with the caret/selection visually drifting from the real text (#341) — a blur/font-mismatch/ scroll-desync defect in the invisible-input-over-visible-mirror rendering pattern used by both Writer Studio and the main manuscript editor. - Voice and WebLLM model download progress bars showed real byte counts and transfer speed instead of a bare percentage, and the voice download bar's progress-scale bug (stuck at ~95% for most of the download) is fixed (#333 item 1). All 5 correction-loop PRs (#342-#346) ran to full quiescence before merging — every CodeRabbit/CodeAnt/Qodo/Sourcery finding fixed or justified with evidence, 0 unresolved review threads, full CI green (Quality Gate, E2E, E2E Deep Coverage, Build, Storybook, Lighthouse, Visual Regression) — including a genuine data-integrity bug found and fixed during that loop: a shared try/catch in the rekey-resume recovery path could, on a crash immediately after `commitRekeyMigration`, misinterpret that crash as "already committed" and clear the migration journal while the durable sentinel still held the old passphrase — leaving neither passphrase able to unlock the library. 2919 i18n keys × 19 locales. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix: address PR #351 review findings — stale CHANGELOG claim + self-contradictory local-only doc - CHANGELOG.md: the [1.27.0] section never documented its own headline feature (#342/#343's disable-encryption/passphrase-rotation production wiring) and still carried the prior release's "intentionally unavailable ... see issue #338" caveat, directly contradicting what shipped in this same version (chatgpt-codex-connector). Added the missing entry, qualified the stale caveat as describing that point in time, and corrected the Docs section's "opened issue #338" line to note it closed in this release. - docs/DEEPSOURCE-REVIEW-LOOP.md: two restored local-only tracking commits (originally discarded by an earlier `git reset --hard`, recovered via cherry-pick) carried "LOCAL-ONLY — not pushed to remote" / "do not push this entry upstream" wording that became false the moment they were committed to a pushed branch (qodo-code-review, coderabbitai). Removed the contradictory local-only framing, keeping the content as a normal dated log entry. Also softened the JS-0440 remediation TODO's "consider a rule-level ignore repo-wide" suggestion per coderabbitai's security concern — a blanket ignore would hide future unsafe `dangerouslySetInnerHTML` uses, not just the one reviewed occurrence. The public/sw.js QNBS-v3-comment findings (qodo-code-review, coderabbitai) were false positives — the existing single-line QNBS-v3 comment on the line directly above APP_VERSION was already unchanged by this PR's one-line version-bump diff; verified via `git diff main~1 -- public/sw.js`. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: GitHub Copilot (Claude Sonnet 5) <noreply@github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Summary
Stacked on #342 (base branch). Adds the 9 new
settings.privacy.*i18n keys that PR#342's UI (
PassphraseModaldisable/rotate modes,EncryptionRecoveryModal,PrivacySectionbuttons) needs:encryptionChangedStatus,encryptionDisableFailed,encryptionMigrationProgress(+
Label),encryptionRecoveryTitle/Body*/Stuck/ResumeButtonAlso reuses several i18n keys that already existed from an earlier, never-wired-up B-1
scaffold (
encryptionChangeAction/Button,encryptionModalChangeTitle,encryptionDisableAction/Button/Confirm,encryptionModalDisableTitle) — PR #342'scomponents were adjusted to reference those instead of duplicating new ones.
check-i18n-keys.mjs --fix, perthis repo's tiered i18n convention (
/i18n-keyskill covers those manually later).public/locales/<lang>/bundle.json.check-doc-metrics.mjsdrift this created (README badge/table/prose:2904 → 2913 keys).
144 locale/bundle files + 1 README file. Kept as its own PR (not folded into #342) so
CodeAnt's ~100-file review threshold still fires on both PRs.
Test plan
pnpm run i18n:check— 19 locales match 2913 keyspnpm run typecheck— cleanpnpm run lint— cleannode scripts/check-doc-metrics.mjs— no drift🤖 Generated with Claude Code
Summary by Sourcery
Add new settings privacy i18n strings for encryption disable/rotate/recovery flows and update locale bundles and metrics accordingly.
Enhancements:
Documentation:
Summary by CodeRabbit
New Features
Warning Updates
Improvements