Skip to content

Add opt-in screen & clipboard reference context for LLM cleanup (Feature 03) - #43

Open
csmashe wants to merge 2 commits into
linuxfrom
worktree-tw-screen-clipboard-context
Open

Add opt-in screen & clipboard reference context for LLM cleanup (Feature 03)#43
csmashe wants to merge 2 commits into
linuxfrom
worktree-tw-screen-clipboard-context

Conversation

@csmashe

@csmashe csmashe commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

Feeds the content the user is looking at into Medium/High LLM cleanup as read-only spelling reference, so dictated proper nouns, function identifiers, ticket numbers, and unusual names get the on-screen/clipboard spelling. Two independent global toggles (screen, clipboard), each with a per-profile override; both default off with zero cost when disabled.

What it does

  • Screen harvest (AtSpiUrlExtractor.TryHarvestFocusedContext): targets the focused element via a single Collection.GetMatches(STATE_FOCUSED) round trip with a tightly-bounded BFS fallback; reads the focused node + up to 8 nearby sibling labels. Caches the a11y bus address for process lifetime; skips password fields (focused and siblings); hard caps ~1 s wall-clock, ~40 node visits, ~2500-char output, and the per-node GetText read.
  • Clipboard: a single wl-paste/xclip read, exposed with an opt-in bounded + cancelable variant (a huge/hung clipboard can't stall or over-read).
  • Gating (DictationOrchestrator): capture runs in the existing background snapshot task, only when a toggle is effective for the matched profile and a Medium/High cleanup with an available provider will actually run. Non-browser windows capture concurrently with the (instant) URL early-return; browser windows defer until after the URL rematch so a URL-specific profile can turn a source off before it's read. The harvest is scoped to the recorded window and bails when the window can't be identified.
  • Injection-safe framing (PromptProcessingService.AppendReferenceContext): untrusted screen/clipboard text is wrapped as inert <reference_context> data (defanged closing delimiter, hard length cap, budget-split between sources), used only to fix spelling of words already dictated — never followed as instructions.
  • UI: two toggles in the Advanced section + two per-profile override combos; new strings translated across en/de/es/ru.
  • Coarse ScreenContextApplied / ClipboardContextApplied history flags.

Testing

  • Unit tests for the pure helpers: framing (defang + length cap), BuildReferenceContext (source labelling, empty handling, budget split so both sources survive), whitespace collapse, snippet combine/cap, password-role skip, window title relate.
  • Full Linux test suite green (648 tests). ReSharper inspections at zero on all touched files.
  • Went through a full adversarial + standard Codex review loop (converged after fixing: password-sibling read, the STATE_FOCUSABLE vs STATE_FOCUSED constant, provider-availability gating, window scoping, read-budget enforcement, and the URL opt-out ordering).

Note: TryHarvestFocusedContext is subprocess-bound, so the AT-SPI walk itself is verified via live a11y-bus probes rather than unit tests (per project convention).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added optional “reference context” using on-screen focused text and clipboard text to improve dictation cleanup.
    • Added advanced toggles for enabling these sources, plus per-profile overrides.
    • Added new localized UI labels and hints for the new options (including advanced settings).
  • Bug Fixes

    • Improved safeguards for context capture: bounded length, timeouts, and avoidance of password fields; context is only applied when it will actually be used.
  • Tests

    • Added automated coverage for reference-context framing, length limits, and enabled/override behavior.

…ure 03)

Feed a bounded, local-first snippet of the focused element's on-screen text
(+ nearby labels) and/or the clipboard into Medium/High LLM cleanup as read-only
spelling reference, so dictated proper nouns, identifiers, ticket numbers, etc.
get the on-screen spelling. Two independent global toggles (screen, clipboard),
each with a per-profile override; both default off.

- AtSpiUrlExtractor.TryHarvestFocusedContext: minimal-call focused-element harvest
  via Collection.GetMatches(STATE_FOCUSED) with a bounded BFS fallback; caches the
  a11y bus address for process lifetime; skips password fields (focused + siblings);
  caps ~1s wall-clock, ~40 node visits, ~2500 chars, and the GetText read; scopes to
  the recorded window (title-relate check) and bails when the window is unknown.
- ActiveWindowService.GetFocusedScreenContext(process, title) + interface method;
  TextInsertionService.TryGetClipboardTextAsync gains an opt-in bounded/cancelable read.
- DictationOrchestrator: capture inside the background snapshot task, gated on the
  effective per-profile toggle AND "a Medium/High cleanup with an available provider
  will run"; non-browser windows capture concurrently, browser windows defer until
  after the URL rematch so URL-profile opt-outs prevent the read. Threads a labelled,
  budget-split reference string through the cleanup pipeline; records coarse
  ScreenContextApplied/ClipboardContextApplied history flags.
- PromptProcessingService.AppendReferenceContext: injection-safe framing (treat as
  inert data, defang the closing delimiter, hard length cap) before the provider call.
- Settings UI: two toggles in the Advanced section + two per-profile override combos;
  new strings translated across en/de/es/ru.
- Tests for the pure helpers (framing/defang/cap, source labelling/budget split,
  whitespace collapse, password-role skip, title relate).
@csmashe

csmashe commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Introduces opt-in reference context capture from focused screen text and clipboard text for LLM cleanup, with new settings and per-profile overrides, AT-SPI harvesting, bounded clipboard reads, prompt framing, UI wiring, localization, and tests.

Changes

Reference Context Feature

Layer / File(s) Summary
Core contracts and models
src/TypeWhisper.Core/Interfaces/IActiveWindowService.cs, src/TypeWhisper.Core/Models/AppSettings.cs, src/TypeWhisper.Core/Models/Profile.cs, src/TypeWhisper.Core/Models/TranscriptionRecord.cs
Adds GetFocusedScreenContext to the interface, global ScreenContextEnabled/ClipboardContextEnabled settings, per-profile override properties, and applied-context flags on TranscriptionRecord.
AT-SPI focused-context harvesting
src/TypeWhisper.Linux/Services/ActiveWindowService.cs, src/TypeWhisper.Linux/Services/ActiveWindow/AtSpiUrlExtractor.cs, tests/TypeWhisper.Linux.Tests/AtSpiFocusedContextHelperTests.cs
Implements TryHarvestFocusedContext with self-app exclusion, focus-target matching, focused-element search, password-field guards, window-title relation checks, snippet combining/truncation, and bounded text reads; wires ActiveWindowService.GetFocusedScreenContext; adds helper tests.
Bounded clipboard reads
src/TypeWhisper.Linux/Services/TextInsertionService.cs, tests/TypeWhisper.Linux.Tests/TextInsertionServiceTests.cs
Extends clipboard reading with maxChars/CancellationToken, adds bounded read path and cancellation-aware handling in LinuxTextInsertionPlatform, updates test fakes.
Orchestrator capture and consumption pipeline
src/TypeWhisper.Linux/Services/DictationOrchestrator.cs
Adds capture budgets, session state, conditional background capture during start/stop (including deferred browser URL rematch), combined reference context building, cleanup-level gating, and history record flags.
Prompt framing and cleanup integration
src/TypeWhisper.Linux/Services/LlmCleanupService.cs, src/TypeWhisper.Linux/Services/PromptProcessingService.cs, tests/TypeWhisper.Linux.Tests/ReferenceContextFramingTests.cs
Threads referenceContext through cleanup and prompt processing, adds AppendReferenceContext to wrap/cap and sanitize injected text, with dedicated framing tests.
Settings and profile UI
src/TypeWhisper.Linux/ViewModels/Sections/AdvancedSectionViewModel.cs, src/TypeWhisper.Linux/ViewModels/Sections/ProfilesSectionViewModel.cs, src/TypeWhisper.Linux/Views/Sections/AdvancedSection.axaml, src/TypeWhisper.Linux/Views/Sections/ProfilesSection.axaml
Adds toggles/selectors and persistence for global settings and per-profile overrides, plus corresponding AXAML controls.
Localization
src/TypeWhisper.Linux/Resources/Localization/de.json, src/TypeWhisper.Linux/Resources/Localization/en.json, src/TypeWhisper.Linux/Resources/Localization/es.json, src/TypeWhisper.Linux/Resources/Localization/ru.json
Adds Advanced.* and Profiles.* localized labels/hints for reference context and overrides.

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

Sequence Diagram(s)

sequenceDiagram
  participant DictationOrchestrator
  participant ActiveWindowService
  participant TextInsertionService
  participant LlmCleanupService
  participant PromptProcessingService

  DictationOrchestrator->>ActiveWindowService: GetFocusedScreenContext(processName, title)
  ActiveWindowService-->>DictationOrchestrator: focused screen context or null
  DictationOrchestrator->>TextInsertionService: TryGetClipboardTextAsync(maxChars, ct)
  TextInsertionService-->>DictationOrchestrator: clipboard context or null
  DictationOrchestrator->>DictationOrchestrator: BuildReferenceContext(screen, clipboard)
  DictationOrchestrator->>LlmCleanupService: CleanAsync(text, level, statusCallback, referenceContext)
  LlmCleanupService->>PromptProcessingService: ProcessSystemPromptAsync(systemPrompt, inputText, ct, referenceContext)
  PromptProcessingService->>PromptProcessingService: AppendReferenceContext(systemPrompt, referenceContext)
  PromptProcessingService-->>LlmCleanupService: framed prompt
  LlmCleanupService-->>DictationOrchestrator: cleaned text
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 main change: opt-in screen and clipboard reference context for LLM cleanup.
Description check ✅ Passed The description is detailed and mostly matches the template, but it omits a distinct Related Issue section and formal Test Plan checklist.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-tw-screen-clipboard-context

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

Comment thread src/TypeWhisper.Linux/Services/TextInsertionService.cs Fixed

@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: 3

🧹 Nitpick comments (2)
tests/TypeWhisper.Linux.Tests/TextInsertionServiceTests.cs (1)

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

Mirror the bounded/cancellable clipboard contract in the fake.

The fake ignores maxChars and ct, so tests through TextInsertionService.TryGetClipboardTextAsync(maxChars, ct) won’t catch contract regressions.

Proposed test fake update
 public Task<string?> TryGetClipboardTextAsync(
     int maxChars = int.MaxValue,
     CancellationToken ct = default
 )
 {
-    return Task.FromResult(Clipboard);
+    ct.ThrowIfCancellationRequested();
+    if (Clipboard is null || maxChars == int.MaxValue)
+    {
+        return Task.FromResult(Clipboard);
+    }
+
+    var limit = Math.Min(Clipboard.Length, Math.Max(0, maxChars));
+    return Task.FromResult<string?>(Clipboard[..limit]);
 }
🤖 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 `@tests/TypeWhisper.Linux.Tests/TextInsertionServiceTests.cs` around lines 1152
- 1157, The test fake for TryGetClipboardTextAsync currently ignores the
maxChars and ct parameters, so update the fake implementation to mirror the real
clipboard contract used by TextInsertionService by honoring the cancellation
token and applying the maxChars bound to the returned Clipboard value. Make the
change in the TryGetClipboardTextAsync method so tests exercise the same
behavior as the production path and can catch regressions in bounded or
cancellable clipboard reads.
src/TypeWhisper.Linux/Views/Sections/ProfilesSection.axaml (1)

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

Trailing unused row in RowDefinitions.

The row list has 27 entries (14 Auto + 13 1 separators), but the highest Grid.Row actually used in this grid is 24, which only needs 25 entries. One extra Auto,1 pair is dead weight.

🧹 Proposed trim
-                            <Grid RowDefinitions="Auto,1,Auto,1,Auto,1,Auto,1,Auto,1,Auto,1,Auto,1,Auto,1,Auto,1,Auto,1,Auto,1,Auto,1,Auto,1,Auto">
+                            <Grid RowDefinitions="Auto,1,Auto,1,Auto,1,Auto,1,Auto,1,Auto,1,Auto,1,Auto,1,Auto,1,Auto,1,Auto,1,Auto">
🤖 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 `@src/TypeWhisper.Linux/Views/Sections/ProfilesSection.axaml` at line 295, The
Grid in ProfilesSection.axaml has an extra unused trailing row definition in its
RowDefinitions list. Trim the last dead `Auto,1` pair from the Grid setup so the
row count matches the highest `Grid.Row` used by the section, keeping the layout
identifiers and separator pattern in sync.
🤖 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 `@src/TypeWhisper.Linux/Services/ActiveWindow/AtSpiUrlExtractor.cs`:
- Around line 320-322: The screen-context matching in AtSpiUrlExtractor is too
permissive because `IsMatchingApp` allows browser-family aliases, which can map
`msedge` to `Google Chrome` and harvest the wrong window. Tighten the logic in
the `processHint`/`appName` path to require exact app identity or a
focused-context-specific alias table for this extractor, and avoid reusing the
broader `IsMatchingApp` behavior here.

In `@src/TypeWhisper.Linux/Services/DictationOrchestrator.cs`:
- Line 2860: The StopAsync flow in DictationOrchestrator is restoring
ducking/media too late relative to the new snapshotTask.WaitAsync timeout, which
can leave audio ducked and media paused after recording stops. Move the
audio/media restoration logic to run before the snapshot wait in StopAsync, and
keep the existing snapshot wait/capture handling afterward so the system is
restored promptly even if browser URL/context capture takes up to 5 seconds.

In `@src/TypeWhisper.Linux/Services/PromptProcessingService.cs`:
- Around line 139-170: AppendReferenceContext only neutralizes the exact
lowercase closing tag, so the sanitization can be bypassed with casing variants.
Update the closing-tag replacement in AppendReferenceContext to use a
case-insensitive replace on the untrusted referenceContext so any variant of the
reference_context terminator is defanged before being inserted into the prompt.
Keep the rest of the framing logic in PromptProcessingService unchanged.

---

Nitpick comments:
In `@src/TypeWhisper.Linux/Views/Sections/ProfilesSection.axaml`:
- Line 295: The Grid in ProfilesSection.axaml has an extra unused trailing row
definition in its RowDefinitions list. Trim the last dead `Auto,1` pair from the
Grid setup so the row count matches the highest `Grid.Row` used by the section,
keeping the layout identifiers and separator pattern in sync.

In `@tests/TypeWhisper.Linux.Tests/TextInsertionServiceTests.cs`:
- Around line 1152-1157: The test fake for TryGetClipboardTextAsync currently
ignores the maxChars and ct parameters, so update the fake implementation to
mirror the real clipboard contract used by TextInsertionService by honoring the
cancellation token and applying the maxChars bound to the returned Clipboard
value. Make the change in the TryGetClipboardTextAsync method so tests exercise
the same behavior as the production path and can catch regressions in bounded or
cancellable clipboard reads.
🪄 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

Run ID: 4b987f0a-994e-44c3-98c7-b8c9922800dc

📥 Commits

Reviewing files that changed from the base of the PR and between e189d7f and bbea00b.

📒 Files selected for processing (21)
  • src/TypeWhisper.Core/Interfaces/IActiveWindowService.cs
  • src/TypeWhisper.Core/Models/AppSettings.cs
  • src/TypeWhisper.Core/Models/Profile.cs
  • src/TypeWhisper.Core/Models/TranscriptionRecord.cs
  • src/TypeWhisper.Linux/Resources/Localization/de.json
  • src/TypeWhisper.Linux/Resources/Localization/en.json
  • src/TypeWhisper.Linux/Resources/Localization/es.json
  • src/TypeWhisper.Linux/Resources/Localization/ru.json
  • src/TypeWhisper.Linux/Services/ActiveWindow/AtSpiUrlExtractor.cs
  • src/TypeWhisper.Linux/Services/ActiveWindowService.cs
  • src/TypeWhisper.Linux/Services/DictationOrchestrator.cs
  • src/TypeWhisper.Linux/Services/LlmCleanupService.cs
  • src/TypeWhisper.Linux/Services/PromptProcessingService.cs
  • src/TypeWhisper.Linux/Services/TextInsertionService.cs
  • src/TypeWhisper.Linux/ViewModels/Sections/AdvancedSectionViewModel.cs
  • src/TypeWhisper.Linux/ViewModels/Sections/ProfilesSectionViewModel.cs
  • src/TypeWhisper.Linux/Views/Sections/AdvancedSection.axaml
  • src/TypeWhisper.Linux/Views/Sections/ProfilesSection.axaml
  • tests/TypeWhisper.Linux.Tests/AtSpiFocusedContextHelperTests.cs
  • tests/TypeWhisper.Linux.Tests/ReferenceContextFramingTests.cs
  • tests/TypeWhisper.Linux.Tests/TextInsertionServiceTests.cs

Comment thread src/TypeWhisper.Linux/Services/ActiveWindow/AtSpiUrlExtractor.cs Outdated
Comment thread src/TypeWhisper.Linux/Services/DictationOrchestrator.cs
Comment thread src/TypeWhisper.Linux/Services/PromptProcessingService.cs
… using

- AtSpiUrlExtractor.IsFocusTargetApp: drop IsMatchingApp browser-family aliasing
  (Edge↔Chrome) for the harvest — exact process match or title-trailing match only,
  so a capture scoped to the recorded window can't harvest another browser's screen.
- DictationOrchestrator.StopAsync: restore audio ducking / media before awaiting the
  background snapshot (now up to 5 s) so the user's audio isn't left ducked after stop.
- PromptProcessingService.AppendReferenceContext: defang the closing delimiter
  case-insensitively (attacker-controlled text; the LLM reads pseudo-XML loosely).
- TextInsertionService.TryGetClipboardTextAsync: restore `using` for the process
  (CodeQL) while keeping kill-on-timeout via an inner finally.
@csmashe

csmashe commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

2 participants