Skip to content

Add encrypted dictation history - #43

Merged
sri-rang merged 23 commits into
mainfrom
feature/issue-42-encrypted-history
Aug 8, 2026
Merged

Add encrypted dictation history#43
sri-rang merged 23 commits into
mainfrom
feature/issue-42-encrypted-history

Conversation

@sri-rang

@sri-rang sri-rang commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add opt-in encrypted local dictation history backed by KDE Wallet
  • add retention, deletion, storage-location, and shared transcript-card workflows
  • refine responsive navigation, consistent dialogs and footer alerts, documentation, and screenshots

Validation

  • make screenshots — passed; five Plasma-themed screenshots inspected
  • make validate — passed

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add opt-in encrypted dictation history backed by KDE Wallet

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add opt-in encrypted dictation history using libsodium with keys stored in KDE Wallet.
• Provide history UI for retention limits, per-entry/all deletion, and storage-location actions.
• Update navigation/footer patterns, tests, CI deps, docs, and refreshed screenshots.
Diagram

graph TD
  UI["QML UI"] --> AC["AppController"] --> DH["DictationHistory"] --> ENC["libsodium AEAD"] --> FILE[("history.enc")]
  DH --> KW{{"KDE Wallet"}}
  UI --> DH
  AC --> DI["DesktopIntegration"] --> FM{{"File manager"}}

  subgraph Legend
    direction LR
    _ui["UI/Module"] ~~~ _file[("Local file")] ~~~ _ext{{"External"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use QtKeychain / Secret Service abstraction
  • ➕ Broader desktop support beyond KDE (GNOME Keyring, etc.)
  • ➕ Less KDE-specific code in core history implementation
  • ➖ Adds a new dependency and integration surface
  • ➖ May reduce UX quality on Plasma vs direct KWallet APIs
  • ➖ Still requires careful async/error handling like current approach
2. Store per-entry encrypted blobs instead of one encrypted snapshot
  • ➕ Avoid rewriting the full history file on each save
  • ➕ More resilient to partial corruption of a single entry
  • ➖ More complex directory layout, pruning, and atomicity guarantees
  • ➖ Potentially leaks metadata (file counts/timestamps) unless carefully mitigated
  • ➖ More filesystem operations and edge cases across platforms
3. Use KConfig/KConfigXT for history storage with a wallet-wrapped master key
  • ➕ Leverages existing KDE stack and potentially simpler serialization
  • ➕ Could allow incremental updates depending on backend
  • ➖ Harder to guarantee authenticated-encryption boundaries and file-format safety limits
  • ➖ Still needs atomic write strategy and corruption handling; complexity shifts rather than disappears

Recommendation: The PR’s approach (single authenticated-encrypted snapshot using libsodium + key in KDE Wallet) is a strong fit for Plasma: it keeps secrets out of the filesystem, fails closed when wallet/key/file are unavailable, and simplifies retention/deletion semantics. The main trade-off is rewriting the snapshot, but bounded by explicit retention limits and file-size caps; given the rigorous async/rollback handling and tests added here, this is a reasonable and maintainable design.

Files changed (33) +3458 / -387 · 8 not counted

Enhancement (9) +1661 / -153
AppController.cppPersist transcripts to encrypted history and expose storage actions +51/-2

Persist transcripts to encrypted history and expose storage actions

• Integrates DictationHistory lifecycle with settings and startup (including resuming pending deletion). Saves completed dictations into history and appends non-blocking warnings to delivery status when history can't save; adds invokables to reveal/open storage paths with error notifications.

src/AppController.cpp

AppController.hExpose DictationHistory to QML and add history/storage invokables +10/-1

Expose DictationHistory to QML and add history/storage invokables

• Adds a history QObject property plus invokable APIs for history enable/disable and opening storage locations; injects DictationHistory dependency for tests.

src/AppController.h

DictationHistory.cppImplement encrypted, retained dictation history with async persistence +769/-0

Implement encrypted, retained dictation history with async persistence

• Adds a QAbstractListModel-backed history store encrypted with XChaCha20-Poly1305 and keyed via KDE Wallet. Implements retention limits, per-entry/clear deletion, safe load/parse limits, atomic commits, failure rollback, and async save/load with queued snapshots and shutdown flush.

src/DictationHistory.cpp

DictationHistory.hAdd DictationHistory model API and key-provider abstraction +157/-0

Add DictationHistory model API and key-provider abstraction

• Defines the HistoryKeyProvider interface and a DictationHistory list model with properties for enablement, busy/error state, retention settings, storage path, and recent entries for the UI.

src/DictationHistory.h

PlatformIntegration.cppAdd file reveal/open helpers via FileManager1 DBus fallback +49/-0

Add file reveal/open helpers via FileManager1 DBus fallback

• Extends KDE desktop integration to reveal a file (ShowItems over DBus) or open a directory, with fallbacks to opening the parent folder URL.

src/PlatformIntegration.cpp

PlatformIntegration.hAdd DesktopIntegration callbacks for revealing files and opening dirs +3/-0

Add DesktopIntegration callbacks for revealing files and opening dirs

• Introduces OpenCallback and two new virtual methods to support opening/revealing storage paths from the UI.

src/PlatformIntegration.h

FooterContainer.qmlIntroduce shared footer container with consistent margins +14/-0

Introduce shared footer container with consistent margins

• Adds a reusable Pane wrapper that standardizes footer padding/margins across pages.

src/qml/FooterContainer.qml

HistoryEntryCard.qmlAdd reusable history entry card component +68/-0

Add reusable history entry card component

• Introduces a card UI component for displaying a timestamped transcript with copy and optional delete actions plus accessibility metadata.

src/qml/HistoryEntryCard.qml

Main.qmlAdd History page and refactor navigation, dialogs, and footers +540/-150

Add History page and refactor navigation, dialogs, and footers

• Adds a new History view (enable/disable, retention controls, per-entry and bulk deletion, storage path reveal) and a recent-history panel on the dictation page. Refactors navigation to include History, standardizes confirmation dialogs with Kirigami.PromptDialog, and moves status/warnings into consistent footers using FooterContainer.

src/qml/Main.qml

Tests (5) +1256 / -39
AppControllerTest.cppTest history persistence behavior and storage-location actions +130/-0

Test history persistence behavior and storage-location actions

• Adds tests ensuring only successful non-empty dictations are saved, failures don’t block delivery, and storage open/reveal actions report errors correctly.

tests/AppControllerTest.cpp

DictationHistoryTest.cppAdd comprehensive unit tests for encrypted history and async semantics +742/-0

Add comprehensive unit tests for encrypted history and async semantics

• Introduces extensive coverage for encryption/decryption, tamper/wrong-key rejection, file-size limits, retention pruning, transactional/rollback behavior, async save/load ordering, and shutdown flush behavior.

tests/DictationHistoryTest.cpp

FakeAppController.hExtend fake controller with history model and storage actions for QML tests +131/-0

Extend fake controller with history model and storage actions for QML tests

• Adds a FakeDictationHistory object and exposes it through FakeAppController, including toggles for busy/availability/reset-required and file-manager action stubs for UI tests.

tests/FakeAppController.h

check-screenshot-workflow.shUpdate screenshot workflow tests for new five-view set +8/-7

Update screenshot workflow tests for new five-view set

• Adjusts expected filenames and counts to match the added History view and new screenshot ordering.

tests/check-screenshot-workflow.sh

tst_Main.qmlExpand QML UI tests for History view, footers, and compact navigation +245/-32

Expand QML UI tests for History view, footers, and compact navigation

• Updates view indices/navigation tests for the new History tab and adds coverage for history controls, accessibility, busy-state disabling, reset action visibility, footer margin consistency, and storage-path buttons.

tests/qml/tst_Main.qml

Documentation (14) +489 / -169
SKILL.mdUpdate screenshot workflow expectation to five images +1/-1

Update screenshot workflow expectation to five images

• Adjusts the PR skill checklist to expect five numbered screenshots instead of four.

.codex/skills/dev-pr/SKILL.md

AGENTS.mdDocument UI convention for consistent confirmation dialogs +3/-0

Document UI convention for consistent confirmation dialogs

• Adds a guideline to reuse Kirigami components and standardize confirmations on Kirigami.PromptDialog.

AGENTS.md

README.mdDescribe optional encrypted history and update screenshots grid +15/-6

Describe optional encrypted history and update screenshots grid

• Updates project claims to reflect optional encrypted history (off by default), links to new history documentation, and refreshes the screenshot table to include the new History view.

README.md

DEVELOPMENT.mdAdd kwallet/libsodium deps and update screenshot window size/count +2/-2

Add kwallet/libsodium deps and update screenshot window size/count

• Documents new build dependencies and updates the screenshot workflow to render five views at 760×700.

docs/DEVELOPMENT.md

DICTATION_HISTORY.mdAdd encrypted history design and security-boundaries documentation +50/-0

Add encrypted history design and security-boundaries documentation

• Introduces a dedicated guide covering storage location, encryption/key handling, retention/deletion behavior, and security limitations.

docs/DICTATION_HISTORY.md

kastword.poRefresh translation catalog for new history and UI strings +418/-160

Refresh translation catalog for new history and UI strings

• Updates the translation catalog to cover new history UI, dialogs, and storage actions (and updates POT creation date/locations).

po/x-test/kastword.po

01-offline-dictation.pngRefresh Plasma-themed screenshot asset not counted

Refresh Plasma-themed screenshot asset

• Updates the shipped screenshot to match the refined UI layout and footer styling.

screenshots/01-offline-dictation.png

02-history.pngAdd/update History view screenshot asset not counted

Add/update History view screenshot asset

• Adds or refreshes the screenshot representing the new encrypted History view.

screenshots/02-history.png

02-speech-models.pngRefresh speech models screenshot asset not counted

Refresh speech models screenshot asset

• Refreshes the models screenshot to align with the new tab/filter UI.

screenshots/02-speech-models.png

03-audio-input.pngRefresh audio input screenshot asset not counted

Refresh audio input screenshot asset

• Refreshes the audio screenshot to align with the updated footer/status presentation.

screenshots/03-audio-input.png

03-speech-models.pngAdd/update new speech models screenshot asset not counted

Add/update new speech models screenshot asset

• Adds or refreshes the models view screenshot as part of the new five-view sequence.

screenshots/03-speech-models.png

04-audio-input.pngAdd/update new audio input screenshot asset not counted

Add/update new audio input screenshot asset

• Adds or refreshes the audio view screenshot consistent with the five-view set.

screenshots/04-audio-input.png

04-settings.pngRefresh settings screenshot asset not counted

Refresh settings screenshot asset

• Refreshes the settings screenshot to align with footer alerts and layout refinements.

screenshots/04-settings.png

05-settings.pngAdd/update new settings screenshot asset not counted

Add/update new settings screenshot asset

• Adds or refreshes settings screenshot as part of the new five-view sequence.

screenshots/05-settings.png

Other (5) +52 / -26
ci.ymlInstall kwallet and libsodium in CI images +2/-2

Install kwallet and libsodium in CI images

• Extends Arch and Fedora CI dependencies to include KDE Wallet and libsodium required by encrypted history.

.github/workflows/ci.yml

CMakeLists.txtAdd DictationHistory sources, KF6::Wallet, and libsodium; add tests +31/-9

Add DictationHistory sources, KF6::Wallet, and libsodium; add tests

• Wires encrypted history into the build by linking KF6 Wallet and libsodium, adds new QML resources, installs new docs/screenshots, and introduces a dedicated history unit test target.

CMakeLists.txt

MakefileInclude new DICTATION_HISTORY doc and five-screenshot set in install smoke/uninstall +10/-6

Include new DICTATION_HISTORY doc and five-screenshot set in install smoke/uninstall

• Updates install-smoke assertions and uninstall lists to cover the new documentation and revised screenshot set.

Makefile

ScreenshotGenerator.cppRender five views at 760×700 for screenshots +4/-5

Render five views at 760×700 for screenshots

• Updates screenshot generator dimensions and enumerated view list to include the new History page and revised ordering.

tools/ScreenshotGenerator.cpp

capture-screenshots.shCapture/validate five screenshot outputs in transaction flow +5/-4

Capture/validate five screenshot outputs in transaction flow

• Updates the screenshot capture script to validate five expected PNGs and enforce the new count during transactional replacement.

tools/capture-screenshots.sh

@qodo-code-review

Copy link
Copy Markdown

Qodo Fixer

No findings are available for this PR yet. Findings appear here once Qodo has reviewed the PR.

@qodo-code-review

qodo-code-review Bot commented Aug 8, 2026

Copy link
Copy Markdown

Code Review by Qodo

Action required

1. Oversize load blocks recovery ✓ Resolved 🐞 Bug ≡ Correctness
Description
When loadEntries() rejects an oversized history file, it returns resetRequired=false, and the QML UI
only shows the reset action when resetRequired is true. This can leave users with a permanently
failing encrypted history file that cannot be deleted from within the app UI.
Code

src/DictationHistory.cpp[R631-632]

+  if (file.size() > maximumHistoryFileSize)
+    return failure(i18n("The encrypted history file is too large to open safely."));
Evidence
The oversize error path uses the default resetRequired=false, while the QML explicitly gates the
reset action on resetRequired, and the enable/disable workflow only opens the disable+delete
dialog when history is enabled.

src/DictationHistory.cpp[631-635]
src/qml/Main.qml[215-257]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
If the history file is too large, `loadEntries()` returns a failure without setting `resetRequired=true`. The UI exposes the “Reset encrypted history” recovery action only when `resetRequired` is true, so oversize failures can become unrecoverable via the UI.

## Issue Context
- QML shows the reset action only when `appController.history.resetRequired` is true.
- The oversize path currently uses `failure(... /*resetRequired=*/false)`.

## Fix Focus Areas
- src/DictationHistory.cpp[631-635]
- src/qml/Main.qml[215-257]

## Suggested fix
- Change the oversize failure returns to set `resetRequired=true` (or introduce a distinct flag like `deletionRecommended`) so the UI provides a recovery path.
- Optionally, also add a UI action to delete existing encrypted history even when `enabled == false` but `available == false`, since `disableHistory(true)` can perform the cleanup.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. History save exceeds load cap ✓ Resolved 🐞 Bug ☼ Reliability
Description
DictationHistory::persistEntries() writes magic+nonce+cipher without enforcing
maximumHistoryFileSize, so a large history can be saved successfully but later rejected by
loadEntries(). This can brick history on next start/enable until the user manually removes the
encrypted file/key outside the app.
Code

src/DictationHistory.cpp[R712-715]

+  QByteArray cipher(plain.size() + crypto_aead_xchacha20poly1305_ietf_ABYTES, Qt::Uninitialized);
+  unsigned long long cipherLength = 0;
+  const int result = crypto_aead_xchacha20poly1305_ietf_encrypt(
+      reinterpret_cast<unsigned char *>(cipher.data()), &cipherLength,
Evidence
The loader hard-rejects any file larger than 64MiB, but the saver constructs and commits an
arbitrarily-sized encrypted blob (based on JSON plaintext size) and the retention settings allow up
to 10k entries, making oversize files feasible.

src/DictationHistory.cpp[631-635]
src/DictationHistory.cpp[707-732]
src/DictationHistory.cpp[402-404]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`DictationHistory::loadEntries()` refuses to open history files larger than `maximumHistoryFileSize` (64MiB), but `persistEntries()` does not bound the final encrypted payload size. This lets the app write a history file that it will later refuse to load.

## Issue Context
- The app allows up to 10,000 entries, and entry text is not size-limited.
- The size limit is enforced only at read-time, not at write-time.

## Fix Focus Areas
- src/DictationHistory.cpp[707-732]
- src/DictationHistory.cpp[629-635]
- src/DictationHistory.cpp[402-404]

## Suggested fix
- Before calling `commit(...)`, compute the final payload size (`magic.size() + nonce.size() + cipher.size()`).
- If it exceeds `maximumHistoryFileSize`, either:
 - prune oldest entries until it fits (re-serialize/re-encrypt in a loop), or
 - fail the save with a clear status telling the user to reduce retention / clear history.
- Ensure the behavior is consistent with the load-time limit so a successful save always remains loadable.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (3)
Remediation recommended
3. Unpinned libsodium in CMake 📘 Rule violation ☼ Reliability
Description
The new libsodium dependency is introduced via pkg_check_modules without an exact version or a
documented exception. This violates the dependency pinning requirement and can reduce build
reproducibility across environments.
Code

CMakeLists.txt[R18-19]

+find_package(PkgConfig REQUIRED)
+pkg_check_modules(Sodium REQUIRED IMPORTED_TARGET libsodium)
Evidence
PR Compliance ID 1 requires new/changed dependency entries to be pinned to an exact immutable
version (or have a documented exception). The added `pkg_check_modules(Sodium REQUIRED
IMPORTED_TARGET libsodium)` does not specify an exact version and has no adjacent exception
documentation.

Rule 1: Pin dependency versions in manifests and lockfiles
CMakeLists.txt[16-21]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new dependency (`libsodium`) is added without an exact version pin or an explicit documented exception.

## Issue Context
The compliance rule requires immutable version references where possible, or a nearby comment explaining why a range/unpinned dependency is required.

## Fix Focus Areas
- CMakeLists.txt[18-19]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Non-ASCII ellipsis in i18n() 📘 Rule violation ✧ Quality
Description
New/modified string literals include the Unicode ellipsis character (), violating the ASCII-only
string literal requirement. This can break tooling/pipelines that assume ASCII-only source strings.
Code

src/DictationHistory.cpp[249]

+  m_status = i18n("Opening KDE Wallet…");
Evidence
PR Compliance ID 576985 requires all newly added/modified string literals to contain only ASCII
characters. The added status text Opening KDE Wallet… contains the non-ASCII ellipsis () in
both the C++ source and the updated translation catalog entry.

Rule 576985: Enforce English-only string literals in code
src/DictationHistory.cpp[241-251]
po/x-test/kastword.po[241-254]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
String literals added in this PR include non-ASCII characters (e.g., the Unicode ellipsis `…`), which violates the English-only ASCII string literal policy.

## Issue Context
Examples include status strings used by `i18n(...)` and corresponding `.po` entries.

## Fix Focus Areas
- src/DictationHistory.cpp[249-249]
- po/x-test/kastword.po[241-254]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Rich-text injection in history 🛡 Security issue ⛨ Security CWE-79
Description
Attacker-influenced transcription text (from audio input) is rendered in a QML Label without forcing
plain-text, so HTML-like markup can be interpreted as rich text. This enables UI spoofing and may
trigger unintended link/resource handling when viewing dictation history.
Code

src/qml/HistoryEntryCard.qml[R35-38]

+        Controls.Label {
+            objectName: "historyEntryText"
+            Layout.fillWidth: true
+            text: root.transcriptText
Evidence
The history entry transcript is bound directly to a Controls.Label (text: root.transcriptText)
without an explicit plain-text setting, allowing rich-text auto-detection to interpret markup-like
transcript content. The transcript originates from transcription output and is passed into history
storage and then bound into HistoryEntryCard for display.

src/qml/HistoryEntryCard.qml[35-44]
src/AppController.cpp[545-570]
src/qml/Main.qml[352-373]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`HistoryEntryCard.qml` displays `transcriptText` inside a `Controls.Label` without setting `textFormat`. In Qt Quick, labels may interpret strings as rich text (via auto-detection), which can cause transcript content containing markup-like sequences to be rendered/handled unexpectedly.

### Issue Context
The transcript originates from `AppController::handleTranscriptionFinished()` and is stored/displayed later via the history model. Treat the transcript as untrusted display content and render it as plain text.

### Fix Focus Areas
- src/qml/HistoryEntryCard.qml[35-44]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Context used
✅ Compliance rules (platform): 20 rules
ⓘ  0 issues published inline · 0 in summary — for nitpicking, see all findings

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Previous review results

Review updated until commit acab861 ⚖️ Balanced

Results up to commit 199b93c ⚖️ Balanced


Action required

1. History save exceeds load cap ✓ Resolved 🐞 Bug ☼ Reliability
Description
DictationHistory::persistEntries() writes magic+nonce+cipher without enforcing
maximumHistoryFileSize, so a large history can be saved successfully but later rejected by
loadEntries(). This can brick history on next start/enable until the user manually removes the
encrypted file/key outside the app.
Code

src/DictationHistory.cpp[R712-715]

+  QByteArray cipher(plain.size() + crypto_aead_xchacha20poly1305_ietf_ABYTES, Qt::Uninitialized);
+  unsigned long long cipherLength = 0;
+  const int result = crypto_aead_xchacha20poly1305_ietf_encrypt(
+      reinterpret_cast<unsigned char *>(cipher.data()), &cipherLength,
Evidence
The loader hard-rejects any file larger than 64MiB, but the saver constructs and commits an
arbitrarily-sized encrypted blob (based on JSON plaintext size) and the retention settings allow up
to 10k entries, making oversize files feasible.

src/DictationHistory.cpp[631-635]
src/DictationHistory.cpp[707-732]
src/DictationHistory.cpp[402-404]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`DictationHistory::loadEntries()` refuses to open history files larger than `maximumHistoryFileSize` (64MiB), but `persistEntries()` does not bound the final encrypted payload size. This lets the app write a history file that it will later refuse to load.

## Issue Context
- The app allows up to 10,000 entries, and entry text is not size-limited.
- The size limit is enforced only at read-time, not at write-time.

## Fix Focus Areas
- src/DictationHistory.cpp[707-732]
- src/DictationHistory.cpp[629-635]
- src/DictationHistory.cpp[402-404]

## Suggested fix
- Before calling `commit(...)`, compute the final payload size (`magic.size() + nonce.size() + cipher.size()`).
- If it exceeds `maximumHistoryFileSize`, either:
 - prune oldest entries until it fits (re-serialize/re-encrypt in a loop), or
 - fail the save with a clear status telling the user to reduce retention / clear history.
- Ensure the behavior is consistent with the load-time limit so a successful save always remains loadable.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Oversize load blocks recovery ✓ Resolved 🐞 Bug ≡ Correctness
Description
When loadEntries() rejects an oversized history file, it returns resetRequired=false, and the QML UI
only shows the reset action when resetRequired is true. This can leave users with a permanently
failing encrypted history file that cannot be deleted from within the app UI.
Code

src/DictationHistory.cpp[R631-632]

+  if (file.size() > maximumHistoryFileSize)
+    return failure(i18n("The encrypted history file is too large to open safely."));
Evidence
The oversize error path uses the default resetRequired=false, while the QML explicitly gates the
reset action on resetRequired, and the enable/disable workflow only opens the disable+delete
dialog when history is enabled.

src/DictationHistory.cpp[631-635]
src/qml/Main.qml[215-257]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
If the history file is too large, `loadEntries()` returns a failure without setting `resetRequired=true`. The UI exposes the “Reset encrypted history” recovery action only when `resetRequired` is true, so oversize failures can become unrecoverable via the UI.

## Issue Context
- QML shows the reset action only when `appController.history.resetRequired` is true.
- The oversize path currently uses `failure(... /*resetRequired=*/false)`.

## Fix Focus Areas
- src/DictationHistory.cpp[631-635]
- src/qml/Main.qml[215-257]

## Suggested fix
- Change the oversize failure returns to set `resetRequired=true` (or introduce a distinct flag like `deletionRecommended`) so the UI provides a recovery path.
- Optionally, also add a UI action to delete existing encrypted history even when `enabled == false` but `available == false`, since `disableHistory(true)` can perform the cleanup.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Comment thread src/DictationHistory.cpp
Comment thread src/DictationHistory.cpp Outdated
@qodo-code-review

Copy link
Copy Markdown

PR approved by Qodo

All merge criteria satisfied — approved by default policy

@sri-rang
sri-rang merged commit 1d036af into main Aug 8, 2026
5 checks passed
@sri-rang
sri-rang deleted the feature/issue-42-encrypted-history branch August 8, 2026 16:45
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit acab861

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