Skip to content

Prevent accidental ESC from destroying recording#190

Open
michaellustig-shopify wants to merge 1 commit intokitlangton:mainfrom
michaellustig-shopify:feat/escape-confirmation
Open

Prevent accidental ESC from destroying recording#190
michaellustig-shopify wants to merge 1 commit intokitlangton:mainfrom
michaellustig-shopify:feat/escape-confirmation

Conversation

@michaellustig-shopify
Copy link
Copy Markdown

@michaellustig-shopify michaellustig-shopify commented Mar 18, 2026

Summary

  • First ESC while recording shows a confirmation overlay instead of immediately destroying
  • "Continue Recording" (green, C key) dismisses the dialog
  • "Destroy" (red, ESC again) cancels and deletes the recording
  • Compact, minimal UI matching Hex's dark aesthetic with bouncy transitions

Test plan

  • Start recording via double-tap lock, press ESC — confirmation dialog appears
  • Press ESC again — recording is destroyed
  • Start recording, press ESC, press C — dialog dismisses, recording continues
  • Start recording, press ESC, click "Continue Recording" — same as above
  • Start recording, press ESC, click "Destroy" — recording is destroyed
  • Verify transcription completing while dialog is open auto-dismisses it

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Escape key now triggers a confirmation dialog during recording. On first ESC, a confirmation overlay appears with "Continue Recording" and "Destroy" options. A second ESC press or clicking "Destroy" cancels the recording. Press C to dismiss the dialog and continue recording.

When ESC is pressed during recording, instead of immediately destroying
the recording, a compact confirmation overlay appears with two options:

- "Continue Recording" (green, press C) — dismisses the dialog
- "Destroy" (red, press ESC again) — cancels and deletes the recording

The confirmation state is shared via @shared(.isConfirmingCancel) so the
key event handler can gate the C-key shortcut. The dialog uses
.ultraThinMaterial with bouncy scale transitions matching Hex's style.

Also handles edge cases: transcription completing auto-dismisses the
dialog, hotkey press while confirming treats as confirmed cancel, and
re-pressing the hotkey after Continue correctly stops the recording.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

Introduces an escape confirmation dialog to prevent accidental recording cancellation. On first ESC press, a confirmation overlay displays with "Continue Recording" and "Destroy" options. Pressing ESC again or clicking Destroy cancels the recording; continuing resumes recording uninterrupted.

Changes

Cohort / File(s) Summary
Changeset Documentation
.changeset/escape-confirmation.md
Documents the new escape confirmation dialog feature as a UI/UX improvement.
Sound Effects
Hex/Clients/SoundEffect.swift
Adds new cancelWarning enum case to support audio feedback for the confirmation dialog.
State Management
Hex/Features/Settings/SettingsFeature.swift
Introduces isConfirmingCancel in-memory boolean flag to track confirmation dialog visibility state.
UI Component
Hex/Features/Transcription/CancelConfirmationView.swift
New SwiftUI view displaying "Continue Recording" and "Destroy" buttons with keycap badges and styled container, supporting two action closures.
Feature Logic
Hex/Features/Transcription/TranscriptionFeature.swift
Implements confirmation flow: first ESC triggers confirmCancel action (plays warning sound, shows dialog); second ESC or Destroy button invokes handleConfirmedCancel; pressing C or Continue button triggers dismissCancelConfirm to resume recording.

Sequence Diagram

sequenceDiagram
    participant User
    participant TranscriptionView
    participant TranscriptionFeature as Feature Logic
    participant SoundClient
    participant SettingsStore as State Store

    User->>TranscriptionView: Press ESC (first time)
    TranscriptionView->>TranscriptionFeature: .confirmCancel
    TranscriptionFeature->>SoundClient: Play cancelWarning sound
    TranscriptionFeature->>SettingsStore: Set isConfirmingCancel = true
    SettingsStore-->>TranscriptionView: Update state
    TranscriptionView->>TranscriptionView: Show CancelConfirmationView

    User->>TranscriptionView: Press ESC (second time) or Click Destroy
    TranscriptionView->>TranscriptionFeature: .confirmCancel again
    TranscriptionFeature->>TranscriptionFeature: handleConfirmedCancel
    TranscriptionFeature->>SettingsStore: Set isConfirmingCancel = false
    SettingsStore-->>TranscriptionView: Update state
    TranscriptionView->>TranscriptionView: Hide CancelConfirmationView & stop recording

    alt User clicks Continue Recording or presses C
        User->>TranscriptionView: Click Continue or Press C
        TranscriptionView->>TranscriptionFeature: .dismissCancelConfirm
        TranscriptionFeature->>SettingsStore: Set isConfirmingCancel = false
        SettingsStore-->>TranscriptionView: Update state
        TranscriptionView->>TranscriptionView: Hide CancelConfirmationView & resume recording
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A whisker-twitch away from loss,
We've added guards to cross the bridge,
One press: "Are you quite sure?" it asks,
With keycaps bright and buttons ready,
A second tap, or "Destroy" to go—
Or "Continue" hops you back to safety. 🎙️

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Prevent accidental ESC from destroying recording' directly and concisely describes the main change: adding an escape confirmation dialog to prevent accidental recording loss on first ESC press.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

Tip

You can disable sequence diagrams in the walkthrough.

Disable the reviews.sequence_diagrams setting to disable sequence diagrams in the walkthrough.

@michaellustig-shopify
Copy link
Copy Markdown
Author

Hey kit, this is certainly AI slop, but I'm using it and it works. Feel free to close it.

For super super super quick demo about this. I've done too many times with escape, and it said I've lost all my audio, and so I propped open this little. I tried to style it like you. I don't think it's going to be as good as you because you are the goat of styling and cute sound effects, and I love this app. But continue, and then I can escape, and I can escape again to close it. Let me go ahead and paste this in here.

I think the only thing you may want to change is the sound effect, but I don't know how you make your cute little noises, so all the best, sir.

@michaellustig-shopify
Copy link
Copy Markdown
Author

the demo - Well, I was recording that demo, but I realized that the floating pill does not show up in recordings, so here's a screenshot.

Screenshot 2026-03-18 at 5 44 22 PM

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/escape-confirmation.md:
- Line 5: Update the changeset summary line "Add escape confirmation dialog to
prevent accidental recording loss." to include the GitHub issue/PR tracking
token in the format "(... (`#NNN`))" (e.g., add " (`#190`)" or the actual PR/issue
number for this change) so the summary follows the guideline "Include
user-facing impact and GitHub issue/PR number".

In `@Hex/Features/Transcription/TranscriptionFeature.swift`:
- Around line 217-223: The SwiftLint `opening_brace` violation is caused by the
newline before the brace in the key handling conditional; update the conditional
that checks `keyEvent.key == .c, keyEvent.modifiers.isEmpty, isConfirmingCancel`
so the opening brace `{` is on the same line as the final condition (i.e., end
the `if` line with `{`), keeping the body that calls `Task { await
send(.dismissCancelConfirm) }` and `return true` unchanged; this touches the
`isConfirmingCancel` check and the `send(.dismissCancelConfirm)` call sites.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0602041b-1dbf-48f7-9ba0-14d41032c4c4

📥 Commits

Reviewing files that changed from the base of the PR and between c661ab8 and e5829c3.

⛔ Files ignored due to path filters (1)
  • Hex/Resources/Audio/cancelWarning.mp3 is excluded by !**/*.mp3
📒 Files selected for processing (5)
  • .changeset/escape-confirmation.md
  • Hex/Clients/SoundEffect.swift
  • Hex/Features/Settings/SettingsFeature.swift
  • Hex/Features/Transcription/CancelConfirmationView.swift
  • Hex/Features/Transcription/TranscriptionFeature.swift

"hex-app": minor
---

Add escape confirmation dialog to prevent accidental recording loss. First ESC shows a Destroy/Continue overlay; second ESC or clicking Destroy cancels the recording.
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.

⚠️ Potential issue | 🟡 Minor

Add the issue/PR reference to the changeset summary.

The summary should include the tracking reference token (e.g., (#190)).

Proposed edit
-Add escape confirmation dialog to prevent accidental recording loss. First ESC shows a Destroy/Continue overlay; second ESC or clicking Destroy cancels the recording.
+Add escape confirmation dialog to prevent accidental recording loss (`#190`). First ESC shows a Destroy/Continue overlay; second ESC or clicking Destroy cancels the recording.
As per coding guidelines, ".changeset/*.md: ... Include user-facing impact and GitHub issue/PR number in the format 'Improve Fn hotkey stability (`#89`)'."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Add escape confirmation dialog to prevent accidental recording loss. First ESC shows a Destroy/Continue overlay; second ESC or clicking Destroy cancels the recording.
Add escape confirmation dialog to prevent accidental recording loss (`#190`). First ESC shows a Destroy/Continue overlay; second ESC or clicking Destroy cancels the recording.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.changeset/escape-confirmation.md at line 5, Update the changeset summary
line "Add escape confirmation dialog to prevent accidental recording loss." to
include the GitHub issue/PR tracking token in the format "(... (`#NNN`))" (e.g.,
add " (`#190`)" or the actual PR/issue number for this change) so the summary
follows the guideline "Include user-facing impact and GitHub issue/PR number".

Comment on lines +217 to +223
// If "C" is pressed with no modifiers while confirming cancel, continue recording.
if keyEvent.key == .c, keyEvent.modifiers.isEmpty,
isConfirmingCancel
{
Task { await send(.dismissCancelConfirm) }
return true
}
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C2 'if keyEvent\.key == \.c, keyEvent\.modifiers\.isEmpty,' Hex/Features/Transcription/TranscriptionFeature.swift

Repository: kitlangton/Hex

Length of output: 277


Move opening brace to same line as condition to resolve SwiftLint opening_brace rule.

The opening brace on line 220 should be moved to the same line as the final condition on line 219.

Proposed edit
-          if keyEvent.key == .c, keyEvent.modifiers.isEmpty,
-             isConfirmingCancel
-          {
+          if keyEvent.key == .c, keyEvent.modifiers.isEmpty,
+             isConfirmingCancel {
             Task { await send(.dismissCancelConfirm) }
             return true
           }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// If "C" is pressed with no modifiers while confirming cancel, continue recording.
if keyEvent.key == .c, keyEvent.modifiers.isEmpty,
isConfirmingCancel
{
Task { await send(.dismissCancelConfirm) }
return true
}
if keyEvent.key == .c, keyEvent.modifiers.isEmpty,
isConfirmingCancel {
Task { await send(.dismissCancelConfirm) }
return true
}
🧰 Tools
🪛 SwiftLint (0.63.2)

[Warning] 220-220: Opening braces should be preceded by a single space and on the same line as the declaration

(opening_brace)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Hex/Features/Transcription/TranscriptionFeature.swift` around lines 217 -
223, The SwiftLint `opening_brace` violation is caused by the newline before the
brace in the key handling conditional; update the conditional that checks
`keyEvent.key == .c, keyEvent.modifiers.isEmpty, isConfirmingCancel` so the
opening brace `{` is on the same line as the final condition (i.e., end the `if`
line with `{`), keeping the body that calls `Task { await
send(.dismissCancelConfirm) }` and `return true` unchanged; this touches the
`isConfirmingCancel` check and the `send(.dismissCancelConfirm)` call sites.

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