Skip to content

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

Open
Fe2-O3 wants to merge 12 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 12 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

This PR adds double-click, ⌘C, and arrow-key interaction to the Transcription History list, routing all four copy paths through a single NSItemProvider/NSPasteboard strategy and a shared "Copied" cursor toast. The HistoryCopy enum cleanly separates copy logic from the view for unit testing, and the ScrollView is made keyboard-focusable via .focusable() + .onMoveCommand/.onCopyCommand.

  • Copy gestures: double-click, ⌘C (via onCopyCommand), the Copy button, and the right-click menu all reach the same clipboardText selection. The copyCommandProviders guard explicitly checks selectedEntryID != nil before falling through to selectedEntry, preventing silent copies when no row is highlighted.
  • Toast (CursorCopyToast): uses a generation counter to prevent a dismissed animation's completion handler from hiding a newly re-shown panel. One remaining issue: panel.alphaValue is reset to 0 unconditionally on every show() call, causing a visible flash when the user copies again before the previous toast has dismissed.
  • Unit tests: 10 new tests in the existing target cover all HistoryCopy.nextIndex and HistoryCopy.text branches.

Confidence Score: 4/5

Safe to merge with a follow-up fix; the toast flash during rapid copies is jarring but does not affect data correctness or clipboard integrity.

The copy paths, keyboard navigation, and focus handling are all correct. The one confirmed defect is in CursorCopyToast.show(): resetting panel.alphaValue to 0 on every call causes the toast to flash invisible then fade back in if the user copies a second time while the toast is still on screen. The feedback panel is the primary visual confirmation mechanism for this feature, so a flicker there is a real, observable regression introduced by this PR.

CursorCopyToast.swift — the unconditional alpha reset in show() needs a guard so rapid consecutive calls don't cause a visible flash.

Reviews (4): Last reviewed commit: "feat(history): inline Copied state on th..." | 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.

@github-actions

Copy link
Copy Markdown

This pull request has been marked stale because it has had no activity for 5 days. It will be closed in 2 days if there is no further activity. Add the keep-open or pinned label to prevent automatic closure.

@github-actions github-actions Bot added the stale label Jul 16, 2026
Per review feedback, the detail-pane copy buttons (Copy/Copy AI, Raw,
Both) now confirm inline — the button briefly becomes a checkmark and
'Copied' — instead of showing the cursor toast, so the feedback lands on
the control the user pressed.

The cursor toast stays on the paths that have no button to morph:
double-click, Cmd-C, and the right-click menu (which dismisses on click).

The copied state is scoped to its entry id, so the confirmation cannot
leak onto another entry when the selection changes.

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

Fe2-O3 commented Jul 16, 2026

Copy link
Copy Markdown
Author

Pushed 4074066 — the copy buttons now confirm inline instead of via the toast, per @grohith327's feedback in #566.

  • The detail-pane buttons (Copy / Copy AI, Raw, Both) briefly become a checkmark + Copied, matching the shadcn use-copy-to-clipboard pattern you linked, so the feedback lands on the control you actually pressed.
  • The cursor toast stays only on the paths that have no button to morph: double-click, ⌘C, and the right-click menu (which dismisses the moment it's clicked). Without it, those actions would be silent.
  • The copied state is scoped to its entry id, so the confirmation can't leak onto a different entry when the selection changes.

Also already in this PR from the earlier review round: all copy paths harmonized on the existing clipboardText selection (#450), ⌘C guarded when nothing is explicitly selected, the same nil-selection fallback fixed in arrow-key navigation, and the toast's dismiss hardened against a fresh copy firing mid-fade.

The substantive discussion of the remaining points (double-click, ⌘C) is over on #566 — happy to continue there or in a Discussion, whichever you prefer.

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

Labels

Projects

None yet

2 participants