Skip to content

feat(history): copy entries via double-click/⌘C/menu + keyboard navigation#568

Open
Fe2-O3 wants to merge 11 commits into
altic-dev:mainfrom
Fe2-O3:feat/history-copy-gestures
Open

feat(history): copy entries via double-click/⌘C/menu + keyboard navigation#568
Fe2-O3 wants to merge 11 commits into
altic-dev:mainfrom
Fe2-O3:feat/history-copy-gestures

Conversation

@Fe2-O3

@Fe2-O3 Fe2-O3 commented Jul 10, 2026

Copy link
Copy Markdown

Description

Adds faster copy affordances to Activity → History and makes the entry list keyboard-navigable:

  • Double-click any entry → copies its transcription.
  • ⌘C → copies the currently-selected entry.
  • ↑ / ↓ → move the selection within the list once it's focused (previously the arrows drove the sidebar selection).
  • All four copy paths — double-click, ⌘C, the Copy button, and the right-click Copy menu — now share a single, consistent "Copied" confirmation: a small toast that appears next to the cursor and fades out, so the feedback shows up where you're already looking.

No change to what gets copied — every path reuses the existing TranscriptionHistoryEntry.clipboardText selection (processed-then-raw) centralized in #450, so gestures, keyboard, menu, and button all place identical text on the pasteboard.

Why

The Copy button and right-click menu already work, but double-click and ⌘C are the muscle-memory gestures for "grab this text" everywhere else on macOS (Finder, Mail, Notes). For a dictation app whose whole point is getting text out quickly, wiring the standard gestures + arrow-key navigation into History removes friction.

Implementation notes

  • The history list was a custom ScrollView + LazyVStack of plain Button rows with a manual @State selection, so it never became the keyboard first responder — which is exactly why ⌘C and the arrow keys didn't reach it. The list container is now .focusable() and takes focus on row click, handling .onMoveCommand (↑/↓, with the selected row scrolled into view via ScrollViewReader) and .onCopyCommand (⌘C).
  • Copy/selection logic is extracted into a small, UI-free HistoryCopy enum (text(for:), nextIndex(current:count:moveUp:)) so it's unit-testable without a running app.
  • The confirmation is a borderless, non-activating NSPanel at the cursor, so a subsequent ⌘V still targets the frontmost app.

Type of Change

  • ✨ New feature

Related Issue or Discussion

Closes #566
Closes #567

(Lineage: extends the clipboard idea from #3 and the menu-bar "Copy Last Transcript" from #450 into the History list.)

Testing

  • Tested on Intel Mac
  • Tested on Apple Silicon Mac
  • Tested on macOS version: 27.0 (Apple Silicon, M1 Max)
  • Ran linter locally: swiftlint --strict --config .swiftlint.yml on the changed files → 0 violations
  • Ran formatter locally: swiftformat --config .swiftformat on the changed files → 0 require formatting
  • Ran tests locally: 10 HistoryCopy unit tests pass — ** TEST SUCCEEDED **

Screenshots / Video

"Copied" toast on a selected row (double-click / ⌘C):

PR568-1-row-toast

Same unified toast from the Copy button:

PR568-2-copy-button

Notes

Personal note: I'm running a build with this now, and it's already made grabbing past transcriptions noticeably easier for me. I'm dealing with an injury that makes extra mouse-aiming and keystrokes costly, so double-click / ⌘C / arrow-key access to History is a real day-to-day accessibility win — which is a big part of why I wanted to contribute it back rather than just keep a local patch. Thank you for building FluidVoice and keeping it open source. 🙏

Fe2-O3 added 9 commits July 9, 2026 20:59
Central, unit-tested seam for what double-click/⌘C copy from the History
list. Delegates to TranscriptionHistoryEntry.clipboardText. Refs altic-dev#566

[authored on non-Xcode host; pending build+test verification on studio]
Adds a count:2 simultaneousGesture that selects the row and copies its
text via HistoryCopy. Single-click selection is unchanged. Refs altic-dev#566

[authored on non-Xcode host; pending build+test verification on studio]
Adds .onCopyCommand on the list; responder-chain based so it does not
shadow ⌘C in the search field. Also enables Edit ▸ Copy. Refs altic-dev#566

[authored on non-Xcode host; pending build+test verification on studio]
Transient per-row pill shown after double-click/⌘C copy, since neither
gesture gives other feedback. Optional polish; isolated commit so it can
be dropped if maintainers prefer no toast. Refs altic-dev#566

[authored on non-Xcode host; pending build+test verification on studio]
The standalone HistoryCopyTests.swift was not a member of the
FluidDictationIntegrationTests target (explicit membership, not
folder-synced), so its cases never ran. Relocate the 6 tests next to the
sibling clipboardText tests in DictationE2ETests.swift. Refs altic-dev#566
The copy button, right-click Copy (Text/Raw/Both), double-click, and ⌘C
now all trigger the same "Copied" confirmation. Adds a matching indicator
in the detail header next to the Copy button. Refs altic-dev#566

[authored on non-Xcode host; pending build+test verification on studio]
Replace the inline row/header "Copied" pills with a small floating panel
that appears next to the mouse cursor and fades out — the expected copy
feedback, shown where the user is looking. Non-activating panel so ⌘V
still targets the frontmost app. All copy actions share it. Refs altic-dev#566

[authored on non-Xcode host; pending build verification on studio]
⌘C via .onCopyCommand never fires because the custom Button-based list
never becomes first responder (same root cause as arrow keys driving the
sidebar instead of the list). Remove the non-working path and its helper
so this PR ships only what works: double-click + right-click + Copy button,
all with the cursor toast. ⌘C is tracked with keyboard navigation in a
follow-up issue. Refs altic-dev#566
Make the entry list a real keyboard focus target: focusable + .onMoveCommand
(↑/↓ moves the selection and scrolls it into view) + .onCopyCommand (⌘C copies
the selected entry, respecting the search field). Clicking a row focuses the
list. Nav index math lives in HistoryCopy.nextIndex with unit tests.

Fixes the root cause behind both the non-firing ⌘C and arrow keys driving the
sidebar instead of the list. Refs altic-dev#566, altic-dev#567

[authored on non-Xcode host; pending build+test verification on studio]
@github-actions github-actions Bot added the needs screenshots Pull request needs screenshot or video evidence. label Jul 10, 2026
@github-actions

Copy link
Copy Markdown

The PR Policy check is blocking this PR because required template information is missing.

Please update the PR description with:

  • Screenshots / Video

Visual files detected:

  • Sources/Fluid/UI/CursorCopyToast.swift
  • Sources/Fluid/UI/HistoryCopy.swift
  • Sources/Fluid/UI/TranscriptionHistoryView.swift

Screenshots or video are required for UI, UX, settings, onboarding, overlay, menu bar, or visual behavior changes. If this PR has no visual changes, check the no-visual-change box in the template.

If this remains incomplete for 48 hours after opening, the PR may be closed.

@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown

Greptile Summary

Adds keyboard-navigable copy affordances to the Transcription History list: double-click, ⌘C, and ↑/↓ selection join the existing Copy button and right-click menu, all sharing a single cursor-following "Copied" toast. Previously-raised issues (dismiss-race, silent ⌘C with no visible selection, moveSelection fallback via selectedEntry) are all properly addressed in this revision.

  • CursorCopyToast introduces a generation counter that prevents the stale fade-out completion handler from hiding a re-shown panel; moveSelection resolves the current index directly from selectedEntryID.flatMap so a nil selection propagates correctly; copyCommandProviders guards on selectedEntryID != nil before treating any entry as a copy target.
  • HistoryCopy is extracted as a small, UI-free enum with full unit-test coverage (10 cases) for both text selection and navigation-index logic.

Confidence Score: 5/5

Safe to merge. All copy paths write correct text to the clipboard; the only finding is a cosmetic alpha flash in the toast when copies happen in rapid succession.

The new keyboard navigation and gesture wiring are straightforward and isolated to the history view. Previously-flagged correctness issues with the dismiss race, silent ⌘C, and navigation-index fallback are all addressed in this revision. The sole remaining concern is a brief visible flicker in CursorCopyToast when the toast is re-shown while still fully visible — purely cosmetic, no data impact.

No files require special attention. CursorCopyToast.swift has the minor alpha-reset issue but it does not affect clipboard correctness.

Reviews (3): Last reviewed commit: "fix(history): harmonize copy paths on cl..." | Re-trigger Greptile

Comment thread Sources/Fluid/UI/CursorCopyToast.swift
Comment thread Sources/Fluid/UI/TranscriptionHistoryView.swift Outdated
Comment thread Sources/Fluid/UI/TranscriptionHistoryView.swift
- CursorCopyToast: guard the fade-out completion with a generation
  counter so a copy fired during the 0.2s dismiss no longer hides the
  fresh confirmation toast.
- copyCommandProviders: ignore ⌘C when no row is explicitly
  selected (was silently copying the first entry after Clear All).
- flashCopied: drop the unused UUID parameter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread Sources/Fluid/UI/TranscriptionHistoryView.swift

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1e16dbf9a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +218 to +220
if let text = HistoryCopy.text(for: entry) {
self.copyToClipboard(text)
self.flashCopied()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep shortcut copy text consistent with Copy controls

This new double-click path copies HistoryCopy.text(for:), which trims whitespace and falls back to raw text, while the existing Copy button and context-menu Copy in this view still call copyToClipboard(entry.processedText) directly. For entries with leading/trailing whitespace, or restored/legacy entries whose processed text is empty but raw text exists, double-click/⌘C now place different content on the pasteboard than the visible Copy controls. Please route the button/menu through the same helper, or otherwise make all History copy paths use the same source.

Useful? React with 👍 / 👎.

- Route the detail-pane Copy button and context-menu Copy Text through
  the same clipboardText selection (altic-dev#450) already used by double-click,
  Cmd-C, and the Copy Last Transcript menu action. Fixes the Copy button
  copying raw, untrimmed processedText (and an empty string for
  transcriptions with no AI text).
- moveSelection: derive the current index from the explicit selection so
  the first arrow press with nothing selected lands on the first row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Fe2-O3

Fe2-O3 commented Jul 10, 2026

Copy link
Copy Markdown
Author

Re: the copy-text consistency feedback from the automated review — I've routed all the "copy this transcription" paths (double-click, ⌘C, the detail-pane Copy button, and the context-menu Copy Text) through the existing clipboardText selection introduced in #450 — the same text the Copy Last Transcript menu action already copies. Previously the History Copy button copied raw processedText, which didn't trim and, for a transcription that wasn't AI-processed, copied an empty string (empty processedText, non-empty rawText) — so Copy on a raw transcription put nothing on the clipboard. Routing through clipboardText (trimmed, processed-then-raw) fixes that and makes every copy affordance in the app agree on what "copy this entry" means. Copy Raw / Copy Both are unchanged.

Also addressed the other review notes in this push: guarded ⌘C so it no longer copies the first row when nothing is explicitly selected, fixed the same nil-selection fallback in arrow-key navigation (the first ↑/↓ now lands on the first row instead of skipping it), hardened the toast's dismiss so a fresh copy firing mid-fade isn't hidden, and dropped the unused UUID parameter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants