Skip to content

Multi-toggle for what App Context sends (Off / App summary / Screenshot LLM) + per-app "send screenshot" allow-list#244

Open
pianoandglass wants to merge 3 commits into
zachlatta:mainfrom
pianoandglass:app-context-multiselector-pr
Open

Multi-toggle for what App Context sends (Off / App summary / Screenshot LLM) + per-app "send screenshot" allow-list#244
pianoandglass wants to merge 3 commits into
zachlatta:mainfrom
pianoandglass:app-context-multiselector-pr

Conversation

@pianoandglass

@pianoandglass pianoandglass commented Jun 25, 2026

Copy link
Copy Markdown

Summary

This reworks the existing App Context settings (formerly "Context Prompt") in the prompts tab into a single control over current activity and what it sends to the AI. Alongside the existing screenshot path, it adds on-device context detection: FreeFlow can describe the user app, window, and current web page locally and send a one-line text description — no image, no vision model. A single multi-toggle selects one of three context sources.

Screenshot sending can now be disabled or limited to a chosen list of apps.
With the default settings, behavior is identical to before this PR.

CleanShot 2026-06-25 at 06 31 43@2x

Context source

Control with three options:

  • Off — no context is sent; the cleanup model sees only your transcript.
  • App summary — FreeFlow builds one line of plain text about your app and current page on-device and sends it as text. No image, no vision model, no added latency.
  • Screenshot LLM — FreeFlow captures the active window and sends it to the vision model (the existing behavior). Depending on your connection (ping and upload speed) this can add up to ~3 s per transcription, and it uses vision quota.
CleanShot 2026-06-25 at 06 27 52@2x CleanShot 2026-06-25 at 06 28 10@2x CleanShot 2026-06-25 at 06 28 15@2x

Per-app "send screenshot" allow-list

Shown in Screenshot mode. Two choices:

  • Send screenshot to all apps — every app is captured (the default).
  • Only specific apps — only the apps you add are captured; every other app falls back to App summary (not Off). Apps are added through a standard file picker (/Applications) and listed as removable chips with their icon and name.

This lets you keep the screenshot only where it helps — for example an email client, where the model benefits from seeing recipients and the subject — while everything else stays fast and text-only.

CleanShot 2026-06-25 at 06 34 05@2x

What "App summary" sends

The one-line summary is built from window/app metadata read through the Accessibility API:

  • For web pages it includes the site host and page title, e.g. User is dictating on github.com (Pull requests) in Safari.
  • The site is read from the page itself (no fixed list of browsers), so it works in Safari, Chromium-based browsers (Chrome, Edge, Brave, Arc, Opera, Vivaldi, …), and standalone web apps / PWAs; Firefox/Zen are covered where their Accessibility data allows.
  • When the site name already matches the app, the redundant part is dropped (the ChatGPT app reads User is dictating in ChatGPT).
  • When you're typing in the address/search bar, it reads User is dictating in the <app> address bar.
  • Otherwise it reads User is dictating in <app>.

Vision Prompt, Test, and Resolution

The controls that already existed are kept and grouped under Screenshot mode, since they only apply when the vision model runs:

  • Vision Prompt — the former "Context Prompt" editor, renamed. The prompt text and its behavior are unchanged.
  • Test Vision Prompt — the former "Test Context Prompt" button, renamed. It now always runs the vision path, so the test works regardless of the selected mode or app scope.
  • Screenshot Resolution — the existing control, moved into this card.

When the source is Off or App summary, this whole block is disabled and dimmed, and the app icons in the allow-list chips are greyed so they read as inactive.

CleanShot 2026-06-25 at 06 54 55@2x

Defaults and scope

  • Defaults reproduce today's behavior exactly: Screenshot + all apps + 1024 px. Existing users see no change.
  • The new settings are stored in UserDefaults; AppState, the post-processing/cleanup logic, and the run-history database are not touched.
  • Deployment target is macOS 13.

Changes by file

Sources/AppContextSource.swift (new, ~300 lines) — the settings model and the Accessibility reads.

  • Three UserDefaults keys (appContextMode, appContextScreenshotScope, appContextScreenshotApps) and the Mode / Scope / EffectiveSource enums, with reads that default to the upstream behavior.
  • effectiveSource(forBundleID:) — resolves the source for one dictation, including the per-app rule (apps outside the allow-list fall back to App summary).
  • metadataSummary(...) — a pure function that composes the App-summary line (host + title, redundancy collapse, address-bar wording, title cleanup), plus the small string helpers it uses.
  • A "Browser URL & focus" section that reads the page host and focus through Accessibility: webContext() (one read; opts the app into Accessibility and skips when permission is missing), a breadth-first AXWebArea search, address-bar detection, and the low-level attribute helpers.

Sources/AppContextScopeStore.swift (new, ~195 lines) — the allow-list store and the scope UI.

  • AppContextAllowlist — an observable store that loads/persists the bundle-id list to UserDefaults and presents the "Add app…" file picker.
  • AppContextAppInfo — resolves and caches each app's display name and icon.
  • ScreenshotScopeSection — the two radio rows, the app chips (icon, name, remove button), the dashed "Add app…" button, the empty state, accessibility labels, and the icon-greying when the block is disabled.

Sources/AppContextService.swift (modified, +35 lines) — the existing context service.

  • Three small guards, one at the top of each leaf function, so the chosen source is honored: the window capture is skipped outside Screenshot mode; the vision/LLM call is skipped outside Screenshot mode; the fallback returns empty (Off) or the App-summary line (App summary) and otherwise keeps the original text.
  • A per-instance forceScreenshotForTest flag used only by the Test button.
  • The body of collectContext(...) is unchanged (byte-identical to main).

Sources/SettingsView.swift (modified) — the Prompts tab.

  • Renamed the card "Context Prompt" → "App Context" and rebuilt its body into the layout above: the Context source segmented control + per-mode description, then the Screenshot-only block (Vision Prompt editor, Test Vision Prompt, the scope section, Screenshot Resolution) wrapped so it disables and dims outside Screenshot mode.
  • Added the appContextMode setting and pointed the Test button at the per-instance flag.
  • Label and styling updates so the card matches the rest of the tab (renamed labels, consistent fonts/spacing, the disabled-state dimming).

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Added an “App Context” setting with selectable modes: off, app metadata, or screenshot-based behavior.
    • Introduced a screenshot scope allowlist UI to allow specific apps (with persisted add/remove and app name/icon display).
    • Added a segmented “Context source” picker with mode-specific descriptions.
  • Bug Fixes

    • Screenshot/vision inference and capture now only run when the selected mode requires them (while still supporting forced test runs).
    • Improved context fallback behavior and summary generation based on the effective mode.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ba610773-5502-4933-a7cd-1d2878e69947

📥 Commits

Reviewing files that changed from the base of the PR and between 4bcb861 and af7b93d.

📒 Files selected for processing (5)
  • Makefile
  • Sources/AppContextScopeStore.swift
  • Sources/AppContextService.swift
  • Sources/AppContextSource.swift
  • Sources/SettingsView.swift
🚧 Files skipped from review as they are similar to previous changes (4)
  • Sources/AppContextSource.swift
  • Sources/AppContextScopeStore.swift
  • Sources/SettingsView.swift
  • Sources/AppContextService.swift

📝 Walkthrough

Walkthrough

This PR adds App Context mode selection, metadata context resolution, screenshot-scope allowlisting, and source-specific runtime behavior. Settings now configure these modes, while tests can explicitly force the screenshot path.

Changes

App Context mode and scope

Layer / File(s) Summary
Source model and resolution
Sources/AppContextSource.swift
Defines persisted modes and scopes, resolves the effective source, generates metadata summaries, and reads accessibility web context.
Service gating and fallback
Sources/AppContextService.swift
Gates inference and screenshot capture by effective source, switches fallback behavior, and supports a screenshot test override.
Settings and scope configuration
Sources/SettingsView.swift
Persists the context mode, adds mode-specific controls, disables screenshot settings when inactive, and forces screenshot mode for vision tests.
Screenshot scope allowlist editor
Sources/AppContextScopeStore.swift
Persists selected apps, opens the application picker, caches app metadata, and renders scope options and removable app chips.
Test build source wiring
Makefile
Adds Sources/AppContextSource.swift to the test runner prerequisites and compiles all prerequisites.

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

Sequence Diagram(s)

sequenceDiagram
  participant PromptsSettingsView
  participant AppContextService
  participant AppContextSource
  participant AccessibilityAPI
  participant ScreenshotCapture

  PromptsSettingsView->>AppContextService: run context prompt test
  AppContextService->>AppContextSource: resolve effective source
  AppContextSource->>AccessibilityAPI: read focused app context
  AccessibilityAPI-->>AppContextSource: metadata and focus data
  AppContextSource-->>AppContextService: off, metadata, or screenshot
  alt screenshot source
    AppContextService->>ScreenshotCapture: capture active window
    ScreenshotCapture-->>AppContextService: screenshot data
  else metadata or off source
    AppContextService-->>PromptsSettingsView: metadata summary or empty result
  end
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately describes the new App Context mode switch and per-app screenshot allow-list.
Docstring Coverage ✅ Passed Docstring coverage is 84.62% which is sufficient. The required threshold is 80.00%.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@pianoandglass
pianoandglass marked this pull request as ready for review June 25, 2026 10:35

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/AppContextService.swift (1)

159-169: 🔒 Security & Privacy | 🟡 Minor

Off mode does not leak to prompts, but retains metadata in history

The initial concern regarding prompt construction is resolved. Downstream services (PostProcessingService) ingest only context.contextSummary. Verification confirms that for the .off effective source, AppContextService explicitly returns an empty string for this field (line 323), ensuring no app title or selection data is sent to the LLM.

However, AppState.swift persists the individual fields (selectedText, windowTitle, appName) directly into the PipelineHistoryItem history (lines 2814, 2830-2832) regardless of the source mode. While this data is not transmitted to the model, it remains stored locally in the history log, which may still constitute a privacy concern depending on requirements.

🤖 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 `@Sources/AppContextService.swift` around lines 159 - 169, The off-mode prompt
issue is already resolved, but `AppState` still persists `selectedText`,
`windowTitle`, and `appName` into `PipelineHistoryItem` even when the effective
source is `.off`. Update the history-building path in `AppState` so those fields
are omitted, cleared, or conditionally suppressed when off mode is active, and
keep the behavior aligned with how `AppContextService` already returns an empty
`contextSummary` for `.off`.
🤖 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 `@Sources/AppContextService.swift`:
- Around line 326-333: The metadata fallback in AppContextService uses
AppContextSource.webContext(), which re-queries the frontmost app and can mix
data from different apps. Update the fallback path to thread the existing
appElement through the web-context lookup instead of calling webContext()
directly, and keep the metadataSummary assembly in sync with the captured
application context.

In `@Sources/AppContextSource.swift`:
- Around line 195-198: webContext() is re-reading the current frontmost app
instead of using the app already captured by collectContext(), which can mix
metadata from different focus states. Update the AppContextSource flow so
collectContext() passes the existing NSRunningApplication and/or AXUIElement
into webContext(), and have webContext() derive host, focus, and address-bar
state from that provided context rather than calling
NSWorkspace.shared.frontmostApplication again.
- Around line 105-116: In AppContextSource’s context-building logic,
addressBarFocused should be checked before any webHost-based phrasing so the
returned message uses the address-bar wording instead of the current page host.
Update the conditional flow around the host/title handling to prioritize the
address bar case first, then fall back to the existing hostMatchesApp and
webHost branches, keeping the existing app/title formatting intact.

---

Outside diff comments:
In `@Sources/AppContextService.swift`:
- Around line 159-169: The off-mode prompt issue is already resolved, but
`AppState` still persists `selectedText`, `windowTitle`, and `appName` into
`PipelineHistoryItem` even when the effective source is `.off`. Update the
history-building path in `AppState` so those fields are omitted, cleared, or
conditionally suppressed when off mode is active, and keep the behavior aligned
with how `AppContextService` already returns an empty `contextSummary` for
`.off`.
🪄 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: 27a20bdd-aa3a-4006-aa7d-7dcf694f6e74

📥 Commits

Reviewing files that changed from the base of the PR and between 13e2788 and 3b3dbfc.

📒 Files selected for processing (4)
  • Sources/AppContextScopeStore.swift
  • Sources/AppContextService.swift
  • Sources/AppContextSource.swift
  • Sources/SettingsView.swift

Comment thread Sources/AppContextService.swift Outdated
Comment thread Sources/AppContextSource.swift Outdated
Comment thread Sources/AppContextSource.swift Outdated
pianoandglass pushed a commit to pianoandglass/freeflow that referenced this pull request Jun 25, 2026
…d + address-bar precedence

- webContext(expectedBundleID:) bails when the frontmost app changed since collectContext captured it, so the App-summary line never mixes one app's page host with another app's name/title (CodeRabbit Major zachlatta#244). Kept collectContext byte-identical: the leaf passes the already-captured bundleIdentifier instead of threading the AXUIElement (which would edit collectContext and break cross-feature coexistence).

- metadataSummary checks addressBarFocused before the page host, so typing in the omnibox reads 'in the <app> address bar' even when a page is loaded (CodeRabbit Minor zachlatta#244).

Files: Sources/AppContextSource.swift, Sources/AppContextService.swift.
Saphi and others added 3 commits July 18, 2026 23:45
…reenshot LLM) + per-app "send screenshot" allow-list

Modified files:
- Sources/AppContextSource.swift (new)
- Sources/AppContextScopeStore.swift (new)
- Sources/AppContextService.swift
- Sources/SettingsView.swift
…d + address-bar precedence

- webContext(expectedBundleID:) bails when the frontmost app changed since collectContext captured it, so the App-summary line never mixes one app's page host with another app's name/title (CodeRabbit Major zachlatta#244). Kept collectContext byte-identical: the leaf passes the already-captured bundleIdentifier instead of threading the AXUIElement (which would edit collectContext and break cross-feature coexistence).

- metadataSummary checks addressBarFocused before the page host, so typing in the omnibox reads 'in the <app> address bar' even when a page is loaded (CodeRabbit Minor zachlatta#244).

Files: Sources/AppContextSource.swift, Sources/AppContextService.swift.
The upstream test target hardcodes its source list; this branch's
AppContextService.swift now references AppContextSource, so the test runner
failed to compile. Declared as a separate prerequisite line (prerequisites
accumulate in make) and switched the recipe to $^ so the compile list always
mirrors the declared prerequisites. Verified: make test builds and passes.

Modified files:
- Makefile
@pianoandglass
pianoandglass force-pushed the app-context-multiselector-pr branch from 4bcb861 to af7b93d Compare July 19, 2026 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant