Skip to content

feat(history): add quick copy and hide silent taps - #85

Merged
fakechris merged 3 commits into
mainfrom
codex/history-list-copy-silent-filter
Aug 2, 2026
Merged

fakechris merged 3 commits into
mainfrom
codex/history-list-copy-silent-filter

Conversation

@fakechris

@fakechris fakechris commented Aug 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • show a one-click copy action directly on each history row without changing selection
  • hide only failed/cancelled captures shorter than 2 seconds when the first attempt has typed, structured absolute-silence evidence and no transcript
  • keep hidden sessions and audio persisted; a later successful retry makes the session visible again
  • migrate existing databases conservatively with bounded batches, leaving ambiguous, corrupt, or oversized evidence visible

No list delete shortcut is added, avoiding accidental destructive actions.

Scope

  • History-list copy control and copy-feedback concurrency
  • Exact-zero capture classification and conservative history visibility
  • SQLite v14 additive visibility column/index and bounded legacy backfill
  • Focused frontend, store, migration, and desktop tests
  • No unrelated product, roadmap, research, or generated artifacts

Public repository boundary

  • Contains only product source code and tests
  • No competitor/vendor research, private evaluation context, prompt-optimization/training artifacts, internal planning, roadmap, milestone, or evolution material
  • scripts/ci/check-public-repo-boundary.sh passes
  • scripts/ci/check-pr-scope.sh origin/main HEAD passes
  • Complete origin/main...HEAD commit and file range inspected

Validation

  • npm test (18 passed)
  • npm run build
  • cargo test -p lumen-store (66 passed)
  • cargo test -p lumen-asr-desktop (166 passed, 5 ignored)
  • cargo test --workspace
  • cargo clippy -p lumen-store -p lumen-asr-desktop --all-targets (only pre-existing warnings)
  • scripts/ci/check-public-repo-boundary.sh
  • scripts/ci/check-pr-scope.sh origin/main HEAD

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR classifies capture signals, persists session-history visibility, filters short absolute-silence sessions, prevents stale history updates, and adds guarded copy controls for session text.

Changes

History visibility and session controls

Layer / File(s) Summary
Capture signal classification and issue recording
apps/desktop/src-tauri/src/dictation.rs, apps/desktop/src/types.ts
Capture validation now distinguishes exact silence from invalid non-finite audio. Rejection paths record structured issue metadata.
Persistent history visibility and filtered listing
crates/lumen-store/src/schema.rs, crates/lumen-store/src/lib.rs, apps/desktop/src-tauri/src/commands.rs
Schema version 14 stores visibility, migrates qualifying legacy sessions, updates visibility after first attempts and retries, and lists visible sessions within the requested limit.
Latest loading and guarded copying
apps/desktop/src/latestRequestGate.ts, apps/desktop/src/clipboardWriteGate.ts, apps/desktop/src/sessionText.ts, apps/desktop/src/App.tsx, apps/desktop/src/styles.css, apps/desktop/tests/*
The desktop app ignores stale history responses, cancels pending work, selects the first nonblank text, and provides guarded detail and per-session copy controls with temporary feedback and cleanup.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the primary changes: adding quick history-row copying and hiding silent captures.
Description check ✅ Passed The description covers scope, repository-boundary checks, validation commands, implementation details, and exclusions.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/history-list-copy-silent-filter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
crates/lumen-store/src/lib.rs (1)

1050-1067: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Corrupted metrics JSON is silently discarded.

map_session_history uses .ok() to drop the serde_json::from_str error for metrics_json, so a corrupted or unreadable metrics blob silently produces initial_attempt: None with no trace. This differs from map_attempt (line 1075), which surfaces a rusqlite::Error::FromSqlConversionFailure for the same kind of malformed JSON. The fallback path is safe (the session stays visible rather than being wrongly hidden), but a corrupted record passes unnoticed, which makes diagnosing bad data harder.

Add a tracing::warn! on the parse failure before falling back to None, so genuinely corrupted rows are observable.

🩹 Proposed fix to log a parse failure
     let initial_attempt = metrics_json
-        .and_then(|json| serde_json::from_str::<PipelineMetrics>(&json).ok())
+        .and_then(|json| {
+            serde_json::from_str::<PipelineMetrics>(&json)
+                .inspect_err(|error| {
+                    tracing::warn!(%error, "failed to parse initial-attempt pipeline metrics")
+                })
+                .ok()
+        })
         .map(|pipeline_metrics| SessionHistoryAttempt {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lumen-store/src/lib.rs` around lines 1050 - 1067, Update
map_session_history so serde_json::from_str failures for metrics_json emit a
tracing::warn! containing the parse error before falling back to
initial_attempt: None. Preserve the existing safe fallback and successful
parsing behavior, and do not change the row-mapping error contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/desktop/src/App.tsx`:
- Around line 3063-3101: Update the raw-text copy button handler near the
existing copy controls to use showCopyFeedback instead of setting copied and
creating its own window.setTimeout. Preserve the raw-copy behavior while
ensuring its feedback timer is tracked by copyFeedbackTimerRef and shared with
copySession, so only the latest copy action controls copied state.

In `@crates/lumen-store/src/lib.rs`:
- Around line 667-697: Add the `idx_dictation_attempts_by_session_ordinal` index
to the database schema or migration for `dictation_attempts`, covering
`session_id` followed by `attempt_ordinal` in ascending order. Keep
`list_history_sessions` unchanged so its correlated earliest-attempt lookup can
use this index.

---

Nitpick comments:
In `@crates/lumen-store/src/lib.rs`:
- Around line 1050-1067: Update map_session_history so serde_json::from_str
failures for metrics_json emit a tracing::warn! containing the parse error
before falling back to initial_attempt: None. Preserve the existing safe
fallback and successful parsing behavior, and do not change the row-mapping
error contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 64ff1cf3-08b4-4163-956d-d2a911b22fc9

📥 Commits

Reviewing files that changed from the base of the PR and between a0b36b9 and 578ebd2.

📒 Files selected for processing (5)
  • apps/desktop/src-tauri/src/commands.rs
  • apps/desktop/src-tauri/src/dictation.rs
  • apps/desktop/src/App.tsx
  • apps/desktop/src/styles.css
  • crates/lumen-store/src/lib.rs

Comment thread apps/desktop/src/App.tsx
Comment thread crates/lumen-store/src/lib.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
crates/lumen-store/src/lib.rs (1)

20-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share the 2-second threshold with the v14 migration.

HIDDEN_SILENT_CAPTURE_MAX_MS defines the runtime hide threshold. The v14 backfill in crates/lumen-store/src/schema.rs at line 564 repeats the same value as the literal 2_000. If one value changes, migrated rows and newly written rows classify differently for the same evidence. Make schema.rs use this constant, or move the constant into schema and import it here.

♻️ Proposed change in crates/lumen-store/src/schema.rs
// crates/lumen-store/src/schema.rs
if metrics.audio_duration_ms >= super::HIDDEN_SILENT_CAPTURE_MAX_MS {
    continue;
}

Make HIDDEN_SILENT_CAPTURE_MAX_MS pub(crate) in crates/lumen-store/src/lib.rs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lumen-store/src/lib.rs` at line 20, Share the hidden silent-capture
threshold between runtime classification and the v14 migration: make
HIDDEN_SILENT_CAPTURE_MAX_MS accessible within the crate, then update the v14
backfill logic in schema.rs to compare against that constant instead of the
duplicated 2_000 literal.
crates/lumen-store/src/schema.rs (1)

507-581: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Wrap the v14 backfill in one transaction.

The backfill runs one autocommitted UPDATE per hidden session. On a database with many silent captures, this produces one commit (and one WAL fsync) per row, so migration time grows with the number of hidden rows. A single transaction around the whole loop also makes the backfill atomic with the version marker at line 587, which removes the partial-backfill state after an interrupted upgrade.

The loop itself terminates correctly: the keyset cursor sessions.id > ?2 advances strictly, and the predicate does not depend on history_visible.

♻️ Proposed change: single transaction for the backfill and the version marker
     if !has_v14 {
+        let backfill = conn.unchecked_transaction()?;
         let mut after_session_id = String::new();
         loop {
             let candidates = {
-                let mut statement = conn.prepare(
+                let mut statement = backfill.prepare(
                     r#"

Apply the same substitution to the UPDATE sessions SET history_visible=0 call, then commit the transaction after the loop.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lumen-store/src/schema.rs` around lines 507 - 581, Wrap the entire
!has_v14 backfill in a single transaction, beginning before the pagination loop
and keeping all candidate queries and history_visible updates within it. Update
the existing UPDATE sessions call to execute through the transaction, then
commit only after the loop completes and immediately before the v14 version
marker is written, preserving atomicity between the backfill and marker.
apps/desktop/src-tauri/src/dictation.rs (1)

362-379: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Persist a consistent message form for both capture issues.

record_capture_signal_issue stores the localized ABSOLUTE_SILENCE_MESSAGE for AbsoluteSilence, but stores the machine token INVALID_CAPTURE_ISSUE for InvalidSignal. CaptureSignalIssue::message() returns INVALID_CAPTURE_MESSAGE for the same variant, so the persisted message and the user-facing message diverge for one variant only. Choose one convention for PipelineStageIssue::message: either both localized, or both stable machine tokens with the localized text kept in the returned error.

♻️ One option: store stable tokens for both kinds
 fn record_capture_signal_issue(attempt: &mut DictationAttemptRecord, issue: CaptureSignalIssue) {
     let (kind, message) = match issue {
         CaptureSignalIssue::AbsoluteSilence => {
-            (PipelineIssueKind::AbsoluteSilence, ABSOLUTE_SILENCE_MESSAGE)
+            (PipelineIssueKind::AbsoluteSilence, ABSOLUTE_SILENCE_ISSUE)
         }
         CaptureSignalIssue::InvalidSignal => {
             (PipelineIssueKind::InputUnavailable, INVALID_CAPTURE_ISSUE)
         }
     };

Note: the test at lines 2142-2151 and the PipelineIssueKind-based filtering in crates/lumen-store/src/schema.rs would need the same token update.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src-tauri/src/dictation.rs` around lines 362 - 379, Make
record_capture_signal_issue persist a consistent message convention for both
CaptureSignalIssue variants: use stable machine tokens for AbsoluteSilence and
InvalidSignal, or localized messages for both. Keep
CaptureSignalIssue::message() aligned with the chosen convention, and update the
related test and PipelineIssueKind-based filtering in schema.rs if changing the
stored token.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/desktop/src/latestRequestGate.ts`:
- Around line 11-25: Update LatestRequestGate.run to detect when the in-flight
cycle’s revision was superseded by cancelPending() before returning the existing
cycle; only coalesce onto this.cycle when it still represents the current
revision, otherwise start a new cycle for the newly supplied request. Add
coverage for run(), cancelPending(), then run() before the first request
settles, asserting the second result is current.

In `@apps/desktop/tests/latestRequestGate.test.ts`:
- Around line 1-3: Update the apps/desktop test script to run Node’s test runner
with --experimental-strip-types so TypeScript test imports load under the CI
Node 22 version. Apply this root-cause fix for
apps/desktop/tests/latestRequestGate.test.ts (lines 1-3),
apps/desktop/tests/sessionText.test.ts (lines 1-3), and
apps/desktop/tests/clipboardWriteGate.test.ts (lines 1-3); no direct changes are
needed in those test files.

---

Nitpick comments:
In `@apps/desktop/src-tauri/src/dictation.rs`:
- Around line 362-379: Make record_capture_signal_issue persist a consistent
message convention for both CaptureSignalIssue variants: use stable machine
tokens for AbsoluteSilence and InvalidSignal, or localized messages for both.
Keep CaptureSignalIssue::message() aligned with the chosen convention, and
update the related test and PipelineIssueKind-based filtering in schema.rs if
changing the stored token.

In `@crates/lumen-store/src/lib.rs`:
- Line 20: Share the hidden silent-capture threshold between runtime
classification and the v14 migration: make HIDDEN_SILENT_CAPTURE_MAX_MS
accessible within the crate, then update the v14 backfill logic in schema.rs to
compare against that constant instead of the duplicated 2_000 literal.

In `@crates/lumen-store/src/schema.rs`:
- Around line 507-581: Wrap the entire !has_v14 backfill in a single
transaction, beginning before the pagination loop and keeping all candidate
queries and history_visible updates within it. Update the existing UPDATE
sessions call to execute through the transaction, then commit only after the
loop completes and immediately before the v14 version marker is written,
preserving atomicity between the backfill and marker.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ef081bfa-fae7-4821-a5a1-5484a1bb6f4f

📥 Commits

Reviewing files that changed from the base of the PR and between 578ebd2 and 87c536d.

📒 Files selected for processing (13)
  • apps/desktop/src-tauri/src/commands.rs
  • apps/desktop/src-tauri/src/dictation.rs
  • apps/desktop/src/App.tsx
  • apps/desktop/src/clipboardWriteGate.ts
  • apps/desktop/src/latestRequestGate.ts
  • apps/desktop/src/sessionText.ts
  • apps/desktop/src/styles.css
  • apps/desktop/src/types.ts
  • apps/desktop/tests/clipboardWriteGate.test.ts
  • apps/desktop/tests/latestRequestGate.test.ts
  • apps/desktop/tests/sessionText.test.ts
  • crates/lumen-store/src/lib.rs
  • crates/lumen-store/src/schema.rs
💤 Files with no reviewable changes (1)
  • apps/desktop/src/styles.css

Comment thread apps/desktop/src/latestRequestGate.ts
Comment thread apps/desktop/tests/latestRequestGate.test.ts
@fakechris
fakechris merged commit 64e9e45 into main Aug 2, 2026
4 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Aug 2, 2026
8 tasks done
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