Skip to content

🎨 Palette: Add visual status indicator#55

Open
Whamp wants to merge 1 commit intomainfrom
palette-add-status-indicator-12855048616214424341
Open

🎨 Palette: Add visual status indicator#55
Whamp wants to merge 1 commit intomainfrom
palette-add-status-indicator-12855048616214424341

Conversation

@Whamp
Copy link
Copy Markdown
Owner

@Whamp Whamp commented Feb 2, 2026

User description

This PR adds a visual status indicator to the Chirp CLI application. Previously, the user only received log messages ("Recording started", "Recording stopped"). Now, a persistent spinner provides immediate feedback on the application's state (Recording vs. Transcribing/Idle).

Changes:

  • Modified ChirpApp in src/chirp/main.py to initialize and manage a self.status_indicator using rich.console.Status.
  • Updated _start_recording, _stop_recording, and _transcribe_and_inject to update the status text and spinner style.
  • Added tests/test_ui_status.py to verify the UI lifecycle and ensure thread safety logic works as expected.

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


PR Type

Enhancement


Description

  • Add persistent visual status indicator with spinner to CLI

  • Show recording state in red and transcribing state in green

  • Handle concurrent recording sessions with proper spinner lifecycle

  • Add comprehensive unit tests for status indicator behavior


Diagram Walkthrough

flowchart LR
  A["ChirpApp Init"] -->|"Create status_indicator"| B["Status Ready"]
  C["Start Recording"] -->|"Update to Recording"| D["Red Spinner Point"]
  E["Stop Recording"] -->|"Update to Transcribing"| F["Green Spinner Dots"]
  G["Transcribe Complete"] -->|"Check _recording flag"| H["Stop Spinner"]
  H -->|"If not recording"| I["Status Stopped"]
  H -->|"If recording"| J["Keep Running"]
Loading

File Walkthrough

Relevant files
Enhancement
main.py
Add status indicator lifecycle management to recording workflow

src/chirp/main.py

  • Convert local console variable to instance variable self.console for
    persistent access
  • Initialize self.status_indicator with "Ready" status at startup
  • Update _start_recording() to display red "Recording..." spinner with
    "point" style
  • Update _stop_recording() to display green "Transcribing..." spinner
    with "dots" style
  • Wrap _transcribe_and_inject() logic in try-finally block to stop
    spinner when transcription completes and no new recording is active
+30/-21 
Tests
test_ui_status.py
Add unit tests for status indicator lifecycle and concurrency

tests/test_ui_status.py

  • Create comprehensive unit test suite for status indicator
    functionality
  • Mock all dependencies including ConfigManager, ParakeetManager,
    AudioCapture, and AudioFeedback
  • Verify status indicator initialization with "Ready" state
  • Test status updates during recording start with red spinner
  • Test status updates during recording stop with green spinner
  • Test spinner lifecycle: stops when transcription completes if not
    recording, continues if new recording started
+100/-0 
Documentation
palette.md
Document learnings on CLI visual feedback and testing       

.jules/palette.md

  • Document learning about persistent visual feedback in CLI applications
  • Record insights on color-coding modal states (red for recording, green
    for processing)
  • Document testing approach for rich components focusing on lifecycle
    verification
  • Capture best practices for mocking RichHandler with real class
    instantiation
+9/-0     

Adds a persistent `rich` status spinner to the CLI.
- Shows "[bold red]Recording...[/bold red]" with a "point" spinner when recording.
- Shows "[bold green]Transcribing...[/bold green]" with a "dots" spinner when processing.
- Handles concurrency to ensure the spinner is stopped correctly even if a new recording session starts immediately.
- Includes unit tests in `tests/test_ui_status.py`.

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 in logs

Description: The new/modified transcription flow logs the full transcribed text via
self.logger.debug("Transcription: %s", text), which can expose sensitive spoken content in
logs (e.g., passwords, API keys, or personal data) if debug logs are enabled or collected
centrally.
main.py [168-173]

Referred Code
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: 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 text logged: The PR logs the full transcription content via self.logger.debug("Transcription:
%s", text), which may contain sensitive user data 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:
Unredacted user input: The PR handles external/user-derived input (text from speech transcription) by logging it
verbatim, which is insecure data handling and increases risk of sensitive data exposure.

Referred Code
    text = self.parakeet.transcribe(waveform, sample_rate=16_000, language=self.config.language)
except Exception as exc:
    self.logger.exception("Transcription failed: %s", exc)
    self.audio_feedback.play_error(self.config.error_sound_path)
    return
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)

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

Generic: Comprehensive Audit Trails

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

Status:
Audit scope unclear: The new recording/transcription state changes add UI updates but do not indicate whether
these actions must be captured as "critical actions" in an audit trail with user
context, which cannot be verified from the diff alone.

Referred Code
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.status_indicator.update("[bold red]Recording...[/bold red]", spinner="point")
    self.status_indicator.start()
    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()

def _handle_timeout(self) -> None:


 ... (clipped 38 lines)

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
General
Reset status instead of stopping it

In _transcribe_and_inject, update the status indicator to "Ready" instead of
stopping it to maintain a persistent status display.

src/chirp/main.py [174-176]

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

__

Why: This suggestion correctly aligns with the PR's goal of a persistent status indicator by proposing to reset the status to "Ready" instead of stopping it, which improves the user experience.

Medium
  • 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