Skip to content

🎨 Palette: Add persistent CLI status indicator#58

Open
Whamp wants to merge 1 commit intomainfrom
palette-ui-status-14825740349080691076
Open

🎨 Palette: Add persistent CLI status indicator#58
Whamp wants to merge 1 commit intomainfrom
palette-ui-status-14825740349080691076

Conversation

@Whamp
Copy link
Copy Markdown
Owner

@Whamp Whamp commented Feb 3, 2026

User description

This PR adds a persistent CLI status indicator to the Chirp application. Previously, the user only received log messages (INFO level) when recording started or stopped. Now, a spinner clearly indicates the current state: "Recording..." or "Transcribing...".

Key changes:

  • src/chirp/main.py: Added self.status_indicator to ChirpApp and integrated it into the recording/transcription loop.
  • tests/test_ui_status.py: Added comprehensive unit tests for the status logic, including race condition verification.

The implementation ensures that if a user starts a new recording while a previous transcription is finishing, the "Recording..." status is preserved.


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


PR Type

Enhancement


Description

  • Add persistent CLI status indicator with recording/transcribing states

  • Implement race condition protection for background transcription tasks

  • Update status display with visual spinners (red point, green dots)

  • Add comprehensive unit tests for status lifecycle and edge cases


Diagram Walkthrough

flowchart LR
  A["ChirpApp Init"] -- "creates" --> B["Status Indicator"]
  C["Start Recording"] -- "updates to" --> D["Recording... Red Point"]
  D -- "user stops" --> E["Stop Recording"]
  E -- "updates to" --> F["Transcribing... Green Dots"]
  F -- "background task" --> G["Check if Recording"]
  G -- "not recording" --> H["Stop Status"]
  G -- "recording started" --> I["Keep Status Active"]
Loading

File Walkthrough

Relevant files
Enhancement
main.py
Integrate persistent status indicator with race condition protection

src/chirp/main.py

  • Convert local console variable to instance variable self.console for
    persistent access
  • Add self.status_indicator initialized with "Idle" status on app
    startup
  • Update _start_recording() to display "Recording..." with red point
    spinner and start status
  • Update _stop_recording() to display "Transcribing..." with green dots
    spinner
  • Wrap _transcribe_and_inject() logic in try-finally block to guard
    status.stop() with if not self._recording check, preventing race
    conditions where background transcription clears status of newly
    started recording
+34/-21 
Tests
test_ui_status.py
Add unit tests for status indicator lifecycle                       

tests/test_ui_status.py

  • Create comprehensive unit test file for status indicator functionality
  • Mock external dependencies (sounddevice, keyboard, pyperclip,
    winsound) at module level
  • Test status lifecycle: initialization, recording start, recording
    stop, transcription completion
  • Verify race condition handling: confirm status is not stopped if
    recording restarts during transcription
  • Use MagicMock to verify correct status update calls with proper
    spinner types
+116/-0 
Documentation
palette.md
Document race condition handling pattern                                 

.jules/palette.md

  • Document learning about race conditions with rich.console.Status in
    event-driven apps with background threads
  • Record solution pattern: guard status.stop() calls with state checks
    to prevent clearing valid newer status
  • Capture architectural insight for future reference
+3/-0     

Implements a visual status indicator using `rich.console.Status` to provide feedback during recording and transcription phases.

- Updates `ChirpApp` to manage a persistent status spinner.
- Displays "Recording..." (red point) and "Transcribing..." (green dots).
- Guards against race conditions where background transcription completion could clear the status of a newly started recording.
- Adds `tests/test_ui_status.py` to verify lifecycle and race condition handling.

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
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
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: Security-First Input Validation and Data Handling

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

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 logged: The new debug log line outputs the full transcription text, which may contain sensitive
user speech content and violates secure logging practices.

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 on recording start

In _start_recording, move the status indicator update and start calls to before
setting self._recording = True to prevent a race condition.

src/chirp/main.py [118-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.status_indicator.update("[bold red]Recording...[/bold red]", spinner="point")
+    self.status_indicator.start()
+
     self._recording = True
     self.audio_feedback.play_start(self.config.start_sound_path)
     self.logger.info("Recording started")
-
-    self.status_indicator.update("[bold red]Recording...[/bold red]", spinner="point")
-    self.status_indicator.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()

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a minor race condition and provides a simple fix by reordering statements, which improves the application's robustness.

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