Skip to content

🎨 Palette: Add visual status spinner for recording and transcription#52

Open
Whamp wants to merge 1 commit intomainfrom
palette-ux-spinner-1523845753990020326
Open

🎨 Palette: Add visual status spinner for recording and transcription#52
Whamp wants to merge 1 commit intomainfrom
palette-ux-spinner-1523845753990020326

Conversation

@Whamp
Copy link
Copy Markdown
Owner

@Whamp Whamp commented Feb 1, 2026

User description

Implemented a visual status indicator using rich.console.Status to provide feedback during recording and transcription phases. This improves the user experience by clarifying the application state (Recording vs Transcribing vs Idle). Added unit tests to verify the behavior.


PR created automatically by Jules for task 1523845753990020326 started by @Whamp


PR Type

Enhancement, Tests


Description

  • Add visual status spinner for recording and transcription phases

  • Display "Recording..." during audio capture, "Transcribing..." during processing

  • Stop spinner when transcription completes or fails

  • Add comprehensive unit tests for UI status indicator behavior


Diagram Walkthrough

flowchart LR
  A["ChirpApp Init"] -- "creates status indicator" --> B["Status Indicator"]
  C["Start Recording"] -- "updates to Recording" --> B
  D["Stop Recording"] -- "updates to Transcribing" --> B
  E["Transcription Complete"] -- "stops spinner" --> B
  F["Transcription Error"] -- "stops spinner" --> B
Loading

File Walkthrough

Relevant files
Enhancement
main.py
Integrate status spinner into recording workflow                 

src/chirp/main.py

  • Initialize console and status_indicator in __init__ method
  • Update status to "Recording..." when _start_recording() is called and
    start spinner
  • Update status to "Transcribing..." when _stop_recording() is called
  • Add try-finally block in _transcribe_and_inject() to stop spinner on
    completion or error
  • Wrap transcription logic in try-finally to ensure spinner stops
    regardless of outcome
+26/-16 
Tests
test_ui_status.py
Add unit tests for UI status indicator                                     

tests/test_ui_status.py

  • Create new test file with comprehensive unit tests for UI status
    indicator
  • Test status initialization during app startup
  • Test status updates during recording start and stop phases
  • Test spinner stops after transcription completes or when recording
    ends
  • Mock all external dependencies including sounddevice and winsound
    modules
+106/-0 
Formatting
audio_feedback.py
Clean up unused type imports                                                         

src/chirp/audio_feedback.py

  • Remove unused imports Tuple and Union from typing module
+1/-1     
test_audio_feedback_cache.py
Remove unused import                                                                         

tests/test_audio_feedback_cache.py

  • Remove unused Path import from pathlib module
+0/-1     

This commit adds a `rich` status spinner to the CLI interface.
- Displays "Recording..." when recording starts.
- Displays "Transcribing..." when recording stops.
- Stops the spinner when transcription finishes or fails.
- Adds `tests/test_ui_status.py` to verify the UI logic.

Co-authored-by: Whamp <1115485+Whamp@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@qodo-code-review
Copy link
Copy Markdown

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🔴
Sensitive data logging

Description: The PR adds debug logging that records the full transcribed text
(self.logger.debug("Transcription: %s", text)), which can expose sensitive user speech
content in application logs if debug logging is enabled.
main.py [168-174]

Referred Code
duration = time.perf_counter() - start_time
self.logger.debug("Transcription finished in %.2fs (chars=%s)", duration, len(text))
if not text.strip():
    self.logger.info("Transcription empty; skipping paste")
    return
self.logger.debug("Transcription: %s", text)
self.text_injector.inject(text)
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Sensitive data in logs: The PR adds debug logging of the full transcription text, which may contain sensitive user
content and should not be written to logs.

Referred Code
self.logger.debug("Transcription: %s", text)
self.text_injector.inject(text)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Insecure data handling: The PR logs user-derived transcription content via self.logger.debug("Transcription:
%s", text), which risks persisting sensitive data without sanitization or
minimization.

Referred Code
self.logger.debug("Transcription: %s", text)
self.text_injector.inject(text)

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Prevent race condition in transcription

To prevent a race condition in _stop_recording, capture the recording state in a
local variable and pass it to the _transcribe_and_inject method when submitting
the transcription task.

src/chirp/main.py [143-154]

 def _stop_recording(self) -> None:
     if self._stop_timer:
         self._stop_timer.cancel()
         self._stop_timer = None
 
     self.logger.debug("Stopping audio capture")
     waveform = self.audio_capture.stop()
+    was_recording = self._recording
     self._recording = False
     self.audio_feedback.play_stop(self.config.stop_sound_path)
     self.status_indicator.update("Transcribing...", spinner="dots")
     self.logger.info("Recording stopped (%s samples)", waveform.size)
-    self._executor.submit(self._transcribe_and_inject, waveform)
+    self._executor.submit(self._transcribe_and_inject, waveform, was_recording)

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a subtle but significant race condition in the concurrent logic that could cause the UI spinner to get stuck, and proposes an effective solution.

Medium
Ensure spinner stops on error

In _start_recording, wrap the logic after starting the status indicator in a
try...finally block to ensure the indicator is stopped even if an error occurs.

src/chirp/main.py [119-137]

 def _start_recording(self) -> None:
     self.logger.debug("Starting audio capture")
     try:
         self.audio_capture.start()
     except Exception as exc:
         self.logger.error("Audio capture start failed: %s", exc)
         self.audio_feedback.play_error(self.config.error_sound_path)
         return
-    self._recording = True
-    self.audio_feedback.play_start(self.config.start_sound_path)
+
     self.status_indicator.update("Recording...", spinner="dots")
     self.status_indicator.start()
-    self.logger.info("Recording started")
+    try:
+        self._recording = True
+        self.audio_feedback.play_start(self.config.start_sound_path)
+        self.logger.info("Recording started")
 
-    if self.config.max_recording_duration > 0:
-        self._stop_timer = threading.Timer(
-            self.config.max_recording_duration, self._handle_timeout
-        )
-        self._stop_timer.start()
+        if self.config.max_recording_duration > 0:
+            self._stop_timer = threading.Timer(
+                self.config.max_recording_duration, self._handle_timeout
+            )
+            self._stop_timer.start()
+    except Exception:
+        self.status_indicator.stop()
+        raise

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a potential UI bug where an error during recording setup could leave the status spinner running indefinitely, and the proposed try...finally block is a robust solution.

Low
General
Reset status to Ready

In the finally block of _transcribe_and_inject, update the status indicator text
back to "Ready" after stopping the spinner to reflect the application's idle
state.

src/chirp/main.py [175-177]

 finally:
     if not self._recording:
         self.status_indicator.stop()
+        self.status_indicator.update("Ready", spinner="dots")
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: This is a good suggestion for UI consistency, ensuring the status message is reset to "Ready" after transcription, which improves the user experience.

Low
  • More

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant