Skip to content

RD-T39 Fixing PTT issue#93

Merged
ucswift merged 1 commit intomasterfrom
develop
Feb 6, 2026
Merged

RD-T39 Fixing PTT issue#93
ucswift merged 1 commit intomasterfrom
develop

Conversation

@ucswift
Copy link
Member

@ucswift ucswift commented Feb 6, 2026

Summary by CodeRabbit

  • New Features

    • Replaced mute controls with a PTT (Push-to-Talk) toggle for streamlined transmission.
    • Added auto-connect behavior: PTT toggle now automatically selects and connects to an available channel if not already connected.
  • Improvements

    • Enhanced web audio playback handling for remote audio tracks with improved cleanup to prevent resource leaks.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 6, 2026

📝 Walkthrough

Walkthrough

This PR contains documentation updates to agent rules and refactors PTT (Push-To-Talk) interface logic from separate press/release handlers to toggle-based control with auto-connect behavior, alongside web-specific audio track management enhancements in the LiveKit store for proper playback and cleanup.

Changes

Cohort / File(s) Summary
Agent Rules & Guidelines
.agent/rules/agent.md
Added YAML front matter and new editorial guidelines covering planning rigor, accuracy over politeness, first principles analysis, debugging protocols, anti-repetition, token efficiency, and pre-flight verification.
PTT Interface & Audio Management
src/components/dispatch-console/ptt-interface.tsx, src/stores/app/livekit-store.ts
Replaced mute-based UI with toggle-based PTT flow featuring auto-connect behavior; added web-specific audio element lifecycle management (attach on TrackSubscribed, cleanup on TrackUnsubscribed) with proper resource release in disconnectFromRoom.

Sequence Diagram

sequenceDiagram
    participant User
    participant PTTInterface as PTT Interface
    participant LiveKitStore as LiveKit Store
    participant WebAudio as Web Audio Elements

    User->>PTTInterface: Toggle PTT Button
    
    alt Not Connected
        PTTInterface->>LiveKitStore: Auto-connect (select default channel)
        LiveKitStore->>LiveKitStore: connectToRoom()
        LiveKitStore->>LiveKitStore: Register TrackSubscribed handler
    end
    
    PTTInterface->>LiveKitStore: Start transmitting
    
    Note over LiveKitStore: Remote audio track received
    LiveKitStore->>LiveKitStore: TrackSubscribed event fires
    LiveKitStore->>WebAudio: Create & attach HTMLAudioElement
    LiveKitStore->>WebAudio: Append to document.body
    WebAudio-->>User: Audio playback to user
    
    User->>PTTInterface: Toggle PTT Button (off)
    PTTInterface->>LiveKitStore: Stop transmitting
    
    Note over LiveKitStore: Remote audio track unsubscribed
    LiveKitStore->>WebAudio: TrackUnsubscribed event fires
    WebAudio->>WebAudio: Pause & remove element
    LiveKitStore->>LiveKitStore: Remove from webAudioElements Map
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #83: Modifies .agent/rules/agent.md with agent rule changes; overlaps with this PR's documentation updates to the same file.
  • PR #89: Modifies src/components/dispatch-console/ptt-interface.tsx and src/stores/app/livekit-store.ts for PTT toggle and web audio track management; directly related feature implementation.

Poem

🐰 Hop, toggle, and connect with care,
Audio whispers through the air,
Web tracks attach, then cleanup flows,
Push-to-talk where the signal goes!
First principles guide us true,
No loose ends when we're all through!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'RD-T39 Fixing PTT issue' is vague and does not provide specific details about what PTT issue was fixed or how it was addressed. Replace with a more descriptive title that specifies the PTT issue being addressed, such as 'Replace mute toggle with PTT toggle and add web audio playback' or 'Implement toggle-based PTT flow with auto-channel-connect'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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 docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

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.

Copy link
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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/dispatch-console/ptt-interface.tsx (1)

80-101: ⚠️ Potential issue | 🟠 Major

Double-tap can trigger concurrent connection attempts.

handlePTTToggle checks isConnected but not isConnecting. A rapid double-tap while disconnected will call connect() twice concurrently because isConnecting is set inside connectToRoom (after the first async tick), and this handler doesn't gate on it. This can result in two rooms being created or an error from the second connection attempt.

Proposed fix — guard on `isConnecting`
  const handlePTTToggle = useCallback(async () => {
+   if (isConnecting) return;
+
    if (!isConnected) {
      // If not connected, try to connect first

And add isConnecting to the dependency array:

- }, [isConnected, availableChannels, pttChannel, pttTransmitting, selectChannel, connect, startTransmitting, stopTransmitting]);
+ }, [isConnected, isConnecting, availableChannels, pttChannel, pttTransmitting, selectChannel, connect, startTransmitting, stopTransmitting]);
🤖 Fix all issues with AI agents
In `@src/stores/app/livekit-store.ts`:
- Around line 273-293: The forEach callback passed to track.detach() uses a
concise arrow that implicitly returns a value (el => el.remove()), which Biome
flags; change it to a block-bodied callback so it does not return anything—for
example in the RoomEvent.TrackUnsubscribed handler where
track.detach().forEach(...) replace the concise arrow with a block: (el) => {
el.remove(); } (or a function(el) { el.remove(); }) to ensure the callback
returns void and avoid the static analysis warning.
🧹 Nitpick comments (4)
src/components/dispatch-console/ptt-interface.tsx (3)

189-189: Icons use gluestack <Icon as={...}> wrapper instead of lucide components directly.

Lines 189, 204, and 210 render icons via the gluestack Icon component. The project guidelines specify using lucide-react-native icons directly in the markup without the gluestack wrapper. As per coding guidelines: "Use lucide-react-native for icons and use those components directly in the markup and don't use the gluestack-ui icon component."

Example for one icon (apply pattern to all three)
-              <Icon as={isConnected ? Wifi : WifiOff} size="2xs" color={isConnected ? '#22c55e' : colorScheme === 'dark' ? '#6b7280' : '#9ca3af'} />
+              {isConnected ? (
+                <Wifi size={12} color="#22c55e" />
+              ) : (
+                <WifiOff size={12} color={colorScheme === 'dark' ? '#6b7280' : '#9ca3af'} />
+              )}

Also applies to: 204-204, 210-210


243-246: Remove dead mutedButton style.

This style is explicitly noted as no longer used. Keeping dead code with a "backwards compatibility" comment is misleading — styles are internal to this component and have no external consumers.

Proposed removal
-  // Note: mutedButton style kept for backwards compatibility but no longer used
-  mutedButton: {
-    backgroundColor: 'rgba(239, 68, 68, 0.1)',
-  },

131-158: Status indicator labels ("TX", "RX", "OFF", "...") are not wrapped in t() for translation.

These are user-visible text strings rendered in the connection indicator badges. Per coding guidelines, all text should use react-i18next for translations.

.agent/rules/agent.md (1)

91-92: Markdown formatting nit: colon on a new line after bold heading.

The pattern **Anti-Repetition Protocol**\n: (and similar on lines 95-96, 99-100) places the colon on a new line. In standard Markdown this won't render as a definition list — the colon will appear as a standalone character. Move it to the same line or use a different format.

Suggested fix (apply to all three)
-**Anti-Repetition Protocol**
-: If a previously suggested fix is reported as failed, ...
+**Anti-Repetition Protocol:** If a previously suggested fix is reported as failed, ...

Comment on lines +273 to +293
room.on(RoomEvent.TrackUnsubscribed, (track, publication, participant) => {
// On web, detach and remove audio elements
if (Platform.OS === 'web' && track.kind === Track.Kind.Audio) {
try {
track.detach().forEach((el) => el.remove());
const trackSid = track.sid || publication.trackSid;
if (trackSid) {
webAudioElements.delete(trackSid);
}
logger.debug({
message: 'Detached audio track',
context: { trackSid, participantIdentity: participant.identity },
});
} catch (err) {
logger.error({
message: 'Failed to detach audio track',
context: { error: err, trackSid: track.sid },
});
}
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Static analysis: forEach callback should not return a value.

el.remove() returns void, but the concise arrow (el) => el.remove() still constitutes an implicit return, which Biome flags as suspicious in an iterable callback. Use a block body.

Proposed fix
-            track.detach().forEach((el) => el.remove());
+            track.detach().forEach((el) => { el.remove(); });
📝 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
room.on(RoomEvent.TrackUnsubscribed, (track, publication, participant) => {
// On web, detach and remove audio elements
if (Platform.OS === 'web' && track.kind === Track.Kind.Audio) {
try {
track.detach().forEach((el) => el.remove());
const trackSid = track.sid || publication.trackSid;
if (trackSid) {
webAudioElements.delete(trackSid);
}
logger.debug({
message: 'Detached audio track',
context: { trackSid, participantIdentity: participant.identity },
});
} catch (err) {
logger.error({
message: 'Failed to detach audio track',
context: { error: err, trackSid: track.sid },
});
}
}
});
room.on(RoomEvent.TrackUnsubscribed, (track, publication, participant) => {
// On web, detach and remove audio elements
if (Platform.OS === 'web' && track.kind === Track.Kind.Audio) {
try {
track.detach().forEach((el) => { el.remove(); });
const trackSid = track.sid || publication.trackSid;
if (trackSid) {
webAudioElements.delete(trackSid);
}
logger.debug({
message: 'Detached audio track',
context: { trackSid, participantIdentity: participant.identity },
});
} catch (err) {
logger.error({
message: 'Failed to detach audio track',
context: { error: err, trackSid: track.sid },
});
}
}
});
🧰 Tools
🪛 Biome (2.3.13)

[error] 277-277: This callback passed to forEach() iterable method should not return a value.

Either remove this return or remove the returned value.

(lint/suspicious/useIterableCallbackReturn)

🤖 Prompt for AI Agents
In `@src/stores/app/livekit-store.ts` around lines 273 - 293, The forEach callback
passed to track.detach() uses a concise arrow that implicitly returns a value
(el => el.remove()), which Biome flags; change it to a block-bodied callback so
it does not return anything—for example in the RoomEvent.TrackUnsubscribed
handler where track.detach().forEach(...) replace the concise arrow with a
block: (el) => { el.remove(); } (or a function(el) { el.remove(); }) to ensure
the callback returns void and avoid the static analysis warning.

@ucswift
Copy link
Member Author

ucswift commented Feb 6, 2026

Approve

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

This PR is approved.

@ucswift ucswift merged commit 1be6c48 into master Feb 6, 2026
11 of 12 checks passed
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.

1 participant