Skip to content

✨(frontend) add Picture-in-Picture recorder - closes #96#104

Open
prince0xdev wants to merge 2 commits into
suitenumerique:mainfrom
prince0xdev:feat/pip-recorder-web
Open

✨(frontend) add Picture-in-Picture recorder - closes #96#104
prince0xdev wants to merge 2 commits into
suitenumerique:mainfrom
prince0xdev:feat/pip-recorder-web

Conversation

@prince0xdev

@prince0xdev prince0xdev commented Jun 10, 2026

Copy link
Copy Markdown

Purpose

When a user starts a recording and switches to another tab, they lose sight of the recording interface — no way to see the timer, the sound level, or pause/stop.

This PR implements the Document Picture-in-Picture API (Chrome 116+) to show a small floating window that stays visible on top of all tabs while recording. Based on @FloChehab's PoC feat/pip-recorder.

Proposal

pip-dictaphone
  • Add a floating PiP window with real-time sound level meter, timer, mic source selector, and pause/stop buttons

  • Add tab audio capture support via getDisplayMedia (mic + tab audio mixed via AudioContext)

  • PiP button is hidden on unsupported browsers (Firefox, Safari) — no regression

  • Fix pre-existing crash on recordings page (useModals must be used within a ModalProvider)

  • Fix dev-mode 403 on language switch (add localhost:3001 to CSRF_TRUSTED_ORIGINS)

  • Open the app in Chrome 116+, start a recording → "Open mini recorder" button appears

  • Click it → PiP window opens and stays visible when switching tabs

  • Timer increments, signal meter reacts to mic

  • Pause/resume and stop work from the PiP window

  • Stop redirects to the recordings list

  • Firefox/Safari: PiP button is absent (no regression)

Closes #96

Summary by CodeRabbit

Release Notes

  • New Features
    • Tab audio capture support—record audio from browser tabs alongside microphone input
    • Picture-in-Picture floating recorder UI for convenient mini control and monitoring during recording
    • Tab audio management controls for adding/removing tab audio capture
  • Chores
    • Added localization strings for new features in English and French

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@prince0xdev, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 43 minutes and 37 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 328b97cf-6588-4567-a380-4fb22136a1f8

📥 Commits

Reviewing files that changed from the base of the PR and between c91ebea and feeec0d.

📒 Files selected for processing (11)
  • .gitignore
  • src/frontend/src/features/recordings/components/RecordComponent.scss
  • src/frontend/src/features/recordings/components/RecordComponent.tsx
  • src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.scss
  • src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.tsx
  • src/frontend/src/features/recordings/hooks/useDocumentPictureInPicture.tsx
  • src/frontend/src/features/recordings/hooks/useRecordingController.ts
  • src/frontend/src/features/recordings/recorder/RecorderManager.ts
  • src/frontend/src/features/recordings/recorder/TabAudioInputManager.ts
  • src/frontend/src/locales/en-US/record.json
  • src/frontend/src/locales/fr-FR/record.json
📝 Walkthrough

Walkthrough

This PR adds tab audio capture and Document Picture-in-Picture (PiP) recording controls to the dictaphone app. It introduces TabAudioInputManager for capturing browser tab audio, refactors RecorderManager to manage dual audio sources, extends the recording controller with tab audio state and methods, implements a new PiP hook and recording panel component, and integrates these into RecordComponent. Configuration and localization strings are also updated.

Changes

Tab Audio and Picture-in-Picture Recording Enhancements

Layer / File(s) Summary
Tab Audio Input Manager Foundation
src/frontend/src/features/recordings/recorder/TabAudioInputManager.ts
New TabAudioInputManager class acquires tab audio via getDisplayMedia with constraints disabling audio processing, validates browser support, stops video tracks immediately, and provides stream lifecycle management (requestStream, stopActiveStream).
RecorderManager Audio Source Architecture
src/frontend/src/features/recordings/recorder/RecorderManager.ts (imports, fields, public methods)
Refactored from single-source to dual-source model using AudioSourceKind and per-kind sourceNodes/sourceStreams maps; adds TabAudioInputManager integration, public tab query methods (supportsTabAudioCapture, isTabAudioActive, getTabAudioLabel), and onTabAudioStateChange callback for React state sync.
RecorderManager Lifecycle and Track Management
src/frontend/src/features/recordings/recorder/RecorderManager.ts (lifecycle, track handlers, cleanup)
Track-ended handlers split by source kind: microphone recovery via replaceSourceStream, tab detachment via handleTabTrackEnded and active stream stoppage; start(), switchInput(), and dispose() updated to manage per-kind initialization, enable/disable methods, and cleanup via new source replacement model.
useRecordingController Tab Audio Integration
src/frontend/src/features/recordings/hooks/useRecordingController.ts
Controller hook extends with tab audio state (tabAudioRequested, tabAudioSupported, tabAudioActive, tabAudioLabel) and control methods (setTabAudioRequested, enableTabAudioCapture, disableTabAudioCapture); initialization wires manager callback for state sync; startRecording conditionally enables tab capture based on request flag.
useDocumentPictureInPicture Hook and Portal
src/frontend/src/features/recordings/hooks/useDocumentPictureInPicture.tsx
New hook manages PiP window lifecycle: detects support, asynchronously requests window with stylesheet cloning and CSS class setup, closes on disable, listens to lifecycle events, and returns memoized portal rendering children into PiP document body.
RecordingPictureInPicturePanel Component and Styling
src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.tsx, src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.scss
New component renders recording UI in PiP window with signal meter, duration timer, audio device selector (disabled when empty), optional tab audio info block, and pause/resume/stop controls. SCSS provides Marianne font, column layout, meter/typography styling, select field formatting, and tab header layout.
RecordComponent Tab Audio and PiP Integration
src/frontend/src/features/recordings/components/RecordComponent.tsx, src/frontend/src/features/recordings/components/RecordComponent.scss
RecordComponent integrates new features: pulls controller state/methods, computes durationLabel for reuse, implements handleTabAudioAction, sets up PiP hook, renders panel with recording state/handlers, adds footer UI (tab toggle, PiP open button, active label), includes portal output. SCSS reorganized with new tab audio styles and repositioned controls layout.
Development Configuration and Localization
.gitignore, src/frontend/src/locales/en-US/record.json, src/frontend/src/locales/fr-FR/record.json
.gitignore updated with npm/yarn/pnpm lock and workspace files; English and French locale files receive tabAudio and pip translation namespaces for tab audio controls, active labels, microphone source, and mini recorder UI.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • suitenumerique/dictaphone#56: Refactored the core RecordComponent and useRecordingController initialization flow that this PR extends with tab-audio enable/disable state and PiP window integration.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes include some potentially out-of-scope modifications: CSRF settings adjustment (localhost:3001 to CSRF_TRUSTED_ORIGINS), .gitignore updates, and fixes for a pre-existing useModals crash that are tangential to the PiP feature. Isolate the PiP-specific changes into focused commits; move unrelated fixes (CSRF, useModals crash, .gitignore) to separate PRs or clearly document their necessity in commit messages.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the main feature addition—Picture-in-Picture recorder for the frontend—and references the linked issue.
Linked Issues check ✅ Passed The code changes fully implement the Picture-in-Picture recorder with tab audio capture and sound level monitoring as required by issue #96.

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


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
src/frontend/src/locales/fr-FR/record.json (1)

33-33: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Verify usage of pip.tabAudio translation key.

Same concern as the English locale: the pip.tabAudio key is defined but usage is not visible in the provided context. Verify whether this key is actually referenced in the PiP panel UI.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/src/locales/fr-FR/record.json` at line 33, The translation key
pip.tabAudio is present in the French locale but may be unused; search the
codebase for references to "pip.tabAudio" (e.g., in PiP panel components,
templates, or i18n lookup calls) and either update the PiP panel UI to use this
key where the audio tab label is rendered (e.g., in the component that renders
PiP tabs) or remove the unused key from the locale to avoid dead translations;
if you add usage, ensure the i18n lookup matches the exact key name and fallback
behavior is handled.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.gitignore:
- Around line 50-53: Remove the entries that ignore dependency lock and
workspace files from .gitignore so they remain versioned: delete the lines
listing package-lock.json, yarn.lock, pnpm-lock.yaml, and pnpm-workspace.yaml
(the ignore patterns shown in the diff) so those lockfiles are tracked and
deterministic dependency resolution and supply-chain auditing are preserved.

In
`@src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.scss`:
- Line 2: The font-family declaration "font-family: 'Marianne', sans-serif;"
violates the font-family-name-quotes rule; update it to use double quotes
instead of single quotes (i.e. "font-family: \"Marianne\", sans-serif;") so the
declaration for Marianne conforms to the stylelint rule while preserving the
fallback sans-serif.

In
`@src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.tsx`:
- Around line 88-95: The current render in RecordingPictureInPicturePanel hides
the tab-audio action when tabAudioLabel is falsy; change the conditional so the
action button is shown whenever tabAudioSupported is true (regardless of
tabAudioLabel) — keep showing the <strong> label only when tabAudioLabel exists,
but always render the Button using tabAudioButtonLabel and onTabAudioAction;
update the conditional that currently uses "tabAudioSupported && tabAudioLabel"
to use "tabAudioSupported" and adjust JSX accordingly.
- Around line 73-77: The label element in RecordingPictureInPicturePanel has no
visible text so the select (source selector) lacks a proper accessible name;
update the JSX in RecordingPictureInPicturePanel.tsx by moving the accessible
name onto the select (e.g., add aria-label or aria-labelledby to the select
using the existing sourceLabel) or add visible/visually-hidden label text inside
the <label> (a span with a screen-reader-only class) and ensure the select is
associated via htmlFor/id; target the <label> and <select> around
selectedAudioInputId/onSelectAudioInput to implement this change.

---

Duplicate comments:
In `@src/frontend/src/locales/fr-FR/record.json`:
- Line 33: The translation key pip.tabAudio is present in the French locale but
may be unused; search the codebase for references to "pip.tabAudio" (e.g., in
PiP panel components, templates, or i18n lookup calls) and either update the PiP
panel UI to use this key where the audio tab label is rendered (e.g., in the
component that renders PiP tabs) or remove the unused key from the locale to
avoid dead translations; if you add usage, ensure the i18n lookup matches the
exact key name and fallback behavior is handled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d2dc0168-0d6f-410a-8fd1-2227deeeb235

📥 Commits

Reviewing files that changed from the base of the PR and between 841945e and ce4680b.

📒 Files selected for processing (13)
  • .gitignore
  • src/backend/dictaphone/settings.py
  • src/frontend/src/features/recordings/components/RecordComponent.scss
  • src/frontend/src/features/recordings/components/RecordComponent.tsx
  • src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.scss
  • src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.tsx
  • src/frontend/src/features/recordings/hooks/useDocumentPictureInPicture.tsx
  • src/frontend/src/features/recordings/hooks/useRecordingController.ts
  • src/frontend/src/features/recordings/recorder/RecorderManager.ts
  • src/frontend/src/features/recordings/recorder/TabAudioInputManager.ts
  • src/frontend/src/locales/en-US/record.json
  • src/frontend/src/locales/fr-FR/record.json
  • src/frontend/src/main.tsx

Comment thread .gitignore
Comment on lines +50 to +53
package-lock.json
yarn.lock
pnpm-lock.yaml
pnpm-workspace.yaml

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep dependency lock/workspace files tracked.

Lines 50-53 ignore all JS lock/workspace manifests, which weakens deterministic dependency resolution and supply-chain auditability. These files should remain versioned.

Suggested fix
 # npm / yarn / pnpm
 node_modules
-package-lock.json
-yarn.lock
-pnpm-lock.yaml
-pnpm-workspace.yaml
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.gitignore around lines 50 - 53, Remove the entries that ignore dependency
lock and workspace files from .gitignore so they remain versioned: delete the
lines listing package-lock.json, yarn.lock, pnpm-lock.yaml, and
pnpm-workspace.yaml (the ignore patterns shown in the diff) so those lockfiles
are tracked and deterministic dependency resolution and supply-chain auditing
are preserved.

@@ -0,0 +1,67 @@
.record-pip-panel {
font-family: 'Marianne', sans-serif;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix stylelint violation for font-family.

Line 2 violates font-family-name-quotes and may fail style checks.

Proposed fix
-  font-family: 'Marianne', sans-serif;
+  font-family: Marianne, sans-serif;
📝 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
font-family: 'Marianne', sans-serif;
font-family: Marianne, sans-serif;
🧰 Tools
🪛 Stylelint (17.12.0)

[error] 2-2: Expected no quotes around "Marianne" (font-family-name-quotes)

(font-family-name-quotes)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.scss`
at line 2, The font-family declaration "font-family: 'Marianne', sans-serif;"
violates the font-family-name-quotes rule; update it to use double quotes
instead of single quotes (i.e. "font-family: \"Marianne\", sans-serif;") so the
declaration for Marianne conforms to the stylelint rule while preserving the
fallback sans-serif.

Source: Linters/SAST tools

Comment on lines +73 to +77
<label className="record-pip-panel__field" aria-label={sourceLabel}>
<select
value={selectedAudioInputId}
onChange={(event) => onSelectAudioInput(event.target.value)}
disabled={audioInputs.length === 0}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Programmatically label the source selector.

On Line 73 and Line 74, the <label> has no text content, so the <select> can be announced without a proper name. Put the accessible label directly on the <select> (or add visible/visually-hidden label text).

Proposed fix
-      <label className="record-pip-panel__field" aria-label={sourceLabel}>
+      <label className="record-pip-panel__field">
         <select
+          aria-label={sourceLabel}
           value={selectedAudioInputId}
           onChange={(event) => onSelectAudioInput(event.target.value)}
           disabled={audioInputs.length === 0}
         >
📝 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
<label className="record-pip-panel__field" aria-label={sourceLabel}>
<select
value={selectedAudioInputId}
onChange={(event) => onSelectAudioInput(event.target.value)}
disabled={audioInputs.length === 0}
<label className="record-pip-panel__field">
<select
aria-label={sourceLabel}
value={selectedAudioInputId}
onChange={(event) => onSelectAudioInput(event.target.value)}
disabled={audioInputs.length === 0}
>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.tsx`
around lines 73 - 77, The label element in RecordingPictureInPicturePanel has no
visible text so the select (source selector) lacks a proper accessible name;
update the JSX in RecordingPictureInPicturePanel.tsx by moving the accessible
name onto the select (e.g., add aria-label or aria-labelledby to the select
using the existing sourceLabel) or add visible/visually-hidden label text inside
the <label> (a span with a screen-reader-only class) and ensure the select is
associated via htmlFor/id; target the <label> and <select> around
selectedAudioInputId/onSelectAudioInput to implement this change.

@FloChehab

FloChehab commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Hello @prince0xdev, thanks for your PR, to ease with reviewing, could you please split the changes in separate commits and avoid unecessary changes.
For instance : Fix pre-existing crash on recordings page (useModals must be used within a ModalProvider), well there is no useModals in the code.
You could also be explicit about the change in the settings file in a commit message (I am not sure about those).

My branch was heavily based on LLM generated / Codex. I feel yours is a bit too much, but open to move forward or hear otherwise :)

@prince0xdev prince0xdev force-pushed the feat/pip-recorder-web branch from ce4680b to c91ebea Compare June 11, 2026 15:57

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (2)
src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.tsx (1)

88-95: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Render the tab-audio action whenever support exists.

Line 88 currently requires tabAudioLabel, which hides the PiP tab-audio action when tab audio is supported but not yet active. That prevents enabling tab audio directly from the PiP panel.

Proposed fix
-      {tabAudioSupported && tabAudioLabel && (
+      {tabAudioSupported && (
         <div className="record-pip-panel__tab-audio">
-          <strong title={tabAudioLabel}>{tabAudioLabel}</strong>
+          {tabAudioLabel && <strong title={tabAudioLabel}>{tabAudioLabel}</strong>}
           <Button size="nano" color="neutral" onClick={onTabAudioAction}>
             {tabAudioButtonLabel}
           </Button>
         </div>
       )}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.tsx`
around lines 88 - 95, The PiP tab-audio action is gated on tabAudioLabel which
hides the control when tab audio is supported but not active; update the
rendering in RecordingPictureInPicturePanel to base visibility only on
tabAudioSupported (remove the tabAudioLabel check) so the action/button always
appears when supported, but keep using tabAudioLabel optionally for the <strong>
text and its title (rendering a fallback or empty title if label is falsy);
ensure onTabAudioAction and tabAudioButtonLabel remain used as-is.
src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.scss (1)

2-2: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix font-family-name-quotes violation.

Line 2 uses quotes around Marianne, which violates the configured stylelint rule.

Proposed fix
-  font-family: 'Marianne', sans-serif;
+  font-family: Marianne, sans-serif;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.scss`
at line 2, The stylelint complaint is about quoted font-family names in
RecordingPictureInPicturePanel.scss; update the font-family declaration used in
that stylesheet (the rule setting 'Marianne') to remove the quotes so it reads
an unquoted font family followed by the fallback (i.e., use Marianne, sans-serif
instead of 'Marianne', sans-serif) to satisfy the font-family-name-quotes rule.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/frontend/src/features/recordings/recorder/RecorderManager.ts`:
- Around line 387-389: When stopping recording, ensure the tab capture is
released in the mediaRecorder stop-completion path by invoking the same cleanup
used in start/dispose: call this.detachSource('tab', true) and
this.tabAudioInputManager.stopActiveStream() when mediaRecorder.stop() finishes
(e.g. in the mediaRecorder.onstop handler or immediately after awaiting the stop
promise). Add these calls alongside the existing stop-completion logic so
sourceStreams no longer retains 'tab' and TabAudioInputManager.activeStream is
cleared.

In `@src/frontend/src/features/recordings/recorder/TabAudioInputManager.ts`:
- Around line 13-15: isSupported currently only checks for
navigator.mediaDevices?.getDisplayMedia but that can return streams without
audio; change the support gating to reflect actual audio-track availability by
(a) making TabAudioInputManager.isSupported() return a promise/async boolean
that attempts a silent getDisplayMedia({audio:true,video:true}) or otherwise
queries requestStream() and resolves true only if the returned MediaStream has
stream.getAudioTracks().length > 0, (b) update
RecorderManager.supportsTabAudioCapture() to await the new async isSupported or
use a cached flag, and (c) on requestStream() failure with "No tab audio source
was selected." downgrade the cached support flag so subsequent UI
(useRecordingController/RecordComponent) hides/disables the tab-audio control
until a successful capture occurs; reference TabAudioInputManager.isSupported,
TabAudioInputManager.requestStream, and RecorderManager.supportsTabAudioCapture
when implementing this behavior.

In `@src/frontend/src/locales/fr-FR/record.json`:
- Line 29: The translation string keyed by "activeLabel" is terse; update its
value from "Audio onglet : {{label}}" to a more natural French phrasing such as
"Audio de l'onglet : {{label}}". Locate the "activeLabel" entry in
src/frontend/src/locales/fr-FR/record.json and replace the string while
preserving the {{label}} interpolation token exactly.

---

Duplicate comments:
In
`@src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.scss`:
- Line 2: The stylelint complaint is about quoted font-family names in
RecordingPictureInPicturePanel.scss; update the font-family declaration used in
that stylesheet (the rule setting 'Marianne') to remove the quotes so it reads
an unquoted font family followed by the fallback (i.e., use Marianne, sans-serif
instead of 'Marianne', sans-serif) to satisfy the font-family-name-quotes rule.

In
`@src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.tsx`:
- Around line 88-95: The PiP tab-audio action is gated on tabAudioLabel which
hides the control when tab audio is supported but not active; update the
rendering in RecordingPictureInPicturePanel to base visibility only on
tabAudioSupported (remove the tabAudioLabel check) so the action/button always
appears when supported, but keep using tabAudioLabel optionally for the <strong>
text and its title (rendering a fallback or empty title if label is falsy);
ensure onTabAudioAction and tabAudioButtonLabel remain used as-is.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 516db41c-3537-4314-b8c6-32b35bf6ec6d

📥 Commits

Reviewing files that changed from the base of the PR and between ce4680b and c91ebea.

📒 Files selected for processing (11)
  • .gitignore
  • src/frontend/src/features/recordings/components/RecordComponent.scss
  • src/frontend/src/features/recordings/components/RecordComponent.tsx
  • src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.scss
  • src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.tsx
  • src/frontend/src/features/recordings/hooks/useDocumentPictureInPicture.tsx
  • src/frontend/src/features/recordings/hooks/useRecordingController.ts
  • src/frontend/src/features/recordings/recorder/RecorderManager.ts
  • src/frontend/src/features/recordings/recorder/TabAudioInputManager.ts
  • src/frontend/src/locales/en-US/record.json
  • src/frontend/src/locales/fr-FR/record.json

Comment thread src/frontend/src/features/recordings/recorder/RecorderManager.ts
Comment on lines +13 to +15
public isSupported() {
return !!navigator.mediaDevices?.getDisplayMedia
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Is checking navigator.mediaDevices.getDisplayMedia alone a reliable feature detect for browser tab-audio capture? Which browsers or capture surfaces expose getDisplayMedia but still return no audio tracks for tab capture?

💡 Result:

Checking navigator.mediaDevices.getDisplayMedia alone is not a reliable feature detection method for tab-audio capture [1][2]. While the presence of the method indicates support for screen sharing, it does not guarantee that the browser supports audio capture or will actually return an audio track when requested [3][4][2]. Under the W3C specification, browsers are permitted to return a video track without an audio track, even if audio is requested, and the user agent is not required to provide audio support [4]. Consequently, relying solely on the existence of the API will lead to false positives [2]. Browsers that expose getDisplayMedia but do not return audio tracks for tab capture include: 1. Mozilla Firefox: Firefox supports getDisplayMedia for video capture but ignores the audio constraint [2][5][6]. It will not return a tab audio track [2][6]. 2. Apple Safari: Safari (on macOS) supports screen capture but similarly ignores the audio constraint, resulting in no audio tracks being returned [2][6]. 3. Mobile Browsers: Mobile browsers (e.g., Safari on iOS, Chrome on Android) generally do not support getDisplayMedia for screen or audio capture, and on some platforms/versions, they may have historically exposed the method while always failing with an error [2][5][7][8]. To handle this, you should not attempt complex feature detection. Because browsers are designed to silently ignore unknown constraints in getDisplayMedia, you can safely pass audio-related constraints (such as { audio: true }) without first performing feature detection [1][6]. If a browser does not support audio capture, it will simply return a stream containing only a video track [4][9]. Your application logic should verify the existence of an audio track in the returned MediaStream (e.g., by checking stream.getAudioTracks.length > 0) rather than attempting to predict support before the capture request [3][2][9].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the full implementation of TabAudioInputManager to confirm isSupported/requestStream behavior
FILE="src/frontend/src/features/recordings/recorder/TabAudioInputManager.ts"
if [ -f "$FILE" ]; then
  echo "===== $FILE (head) ====="
  head -n 220 "$FILE" | nl -ba | sed -n '1,220p'
else
  echo "Missing file: $FILE"
fi

# Confirm how supportsTabAudioCapture() maps into tabAudioSupported
HOOK="src/frontend/src/features/recordings/hooks/useRecordingController.ts"
if [ -f "$HOOK" ]; then
  echo "===== $HOOK (relevant excerpt) ====="
  # Print around the referenced line range if it exists
  nl -ba "$HOOK" | sed -n '150,260p'
else
  echo "Missing file: $HOOK"
fi

# Confirm where RecordComponent uses tabAudioSupported to show the UI action
COMP="src/frontend/src/features/recordings/components/RecordComponent.tsx"
if [ -f "$COMP" ]; then
  echo "===== $COMP (relevant excerpt) ====="
  nl -ba "$COMP" | sed -n '120,260p'
else
  echo "Missing file: $COMP"
fi

# Search for the specific runtime error string
rg -n "No tab audio source was selected" -S src/frontend/src/features/recordings || true

# Search for supportsTabAudioCapture usage
rg -n "supportsTabAudioCapture" -S src/frontend/src/features/recordings || true

Repository: suitenumerique/dictaphone

Length of output: 219


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/frontend/src/features/recordings/recorder/TabAudioInputManager.ts"
if [ -f "$FILE" ]; then
  echo "===== $FILE (first 220 lines) ====="
  head -n 220 "$FILE" | cat -n | sed -n '1,220p'
else
  echo "Missing file: $FILE"
fi

HOOK="src/frontend/src/features/recordings/hooks/useRecordingController.ts"
if [ -f "$HOOK" ]; then
  echo "===== $HOOK (lines 150-260) ====="
  sed -n '150,260p' "$HOOK" | cat -n
else
  echo "Missing file: $HOOK"
fi

COMP="src/frontend/src/features/recordings/components/RecordComponent.tsx"
if [ -f "$COMP" ]; then
  echo "===== $COMP (lines 120-260) ====="
  sed -n '120,260p' "$COMP" | cat -n
else
  echo "Missing file: $COMP"
fi

echo "===== Error string search ====="
rg -n "No tab audio source was selected" -S src/frontend/src/features/recordings || true

echo "===== supportsTabAudioCapture usage search ====="
rg -n "supportsTabAudioCapture" -S src/frontend/src/features/recordings || true

Repository: suitenumerique/dictaphone

Length of output: 12185


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect RecorderManager and the tab-audio control flow
FILE="src/frontend/src/features/recordings/recorder/RecorderManager.ts"
if [ -f "$FILE" ]; then
  echo "===== $FILE (relevant excerpt) ====="
  # Print around supportsTabAudioCapture and where requestStream is used
  sed -n '1,220p' "$FILE" | cat -n
else
  echo "Missing file: $FILE"
fi

HOOK="src/frontend/src/features/recordings/hooks/useRecordingController.ts"
if [ -f "$HOOK" ]; then
  echo "===== $HOOK (tab-audio related excerpt) ====="
  # Show around the matches we saw (enable/disable paths)
  rg -n "enableTabAudioCapture|disableTabAudioCapture|tabAudioRequested|requestStream\\(" "$HOOK" || true
  # show larger window around the tab-audio request logic
  sed -n '360,580p' "$HOOK" | cat -n
else
  echo "Missing file: $HOOK"
fi

# Find where RecordingPictureInPicturePanel uses tabAudioSupported
PIP="src/frontend/src/features/recordings/components/RecordingPictureInPicturePanel.tsx"
if [ -f "$PIP" ]; then
  echo "===== $PIP (tab-audio props usage) ====="
  rg -n "tabAudioSupported|onTabAudioAction|tabAudioButtonLabel" "$PIP" || true
  sed -n '1,240p' "$PIP" | cat -n
else
  echo "Missing file: $PIP"
fi

Repository: suitenumerique/dictaphone

Length of output: 21227


🏁 Script executed:

#!/bin/bash
set -euo pipefail

HOOK="src/frontend/src/features/recordings/recorder/RecorderManager.ts"
if [ -f "$HOOK" ]; then
  rg -n "enableTabAudioCapture|disableTabAudioCapture|requestStream\\(|getDisplayMedia\\(|tabAudioLabel" "$HOOK" || true
  echo "----- enable/disable excerpt (approx. 220-420) -----"
  sed -n '220,520p' "$HOOK" | cat -n
fi

# Also check if enableTabAudioCapture sets tabAudioLabel
rg -n "tabAudioLabel\\s*=" src/frontend/src/features/recordings/recorder/RecorderManager.ts || true

Repository: suitenumerique/dictaphone

Length of output: 10769


🏁 Script executed:

#!/bin/bash
set -euo pipefail
HOOK="src/frontend/src/features/recordings/hooks/useRecordingController.ts"
rg -n "tabAudioLabel|getTabAudioLabel|tabAudioActive|notifyTabAudioState|onTabAudioStateChange" "$HOOK" || true

# Also inspect where tabAudioLabel is set in the hook's state initialization
rg -n "tabAudioLabel" "$HOOK" || true

Repository: suitenumerique/dictaphone

Length of output: 424


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "tabAudioSupported|tabAudioLabel|tabAudioButtonLabel|onTabAudioAction" src/frontend/src/features/recordings/components src/frontend/src/features/recordings/hooks src/frontend/src/features/recordings -S

Repository: suitenumerique/dictaphone

Length of output: 7046


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/frontend/src/features/recordings/components/RecordComponent.tsx"
if [ -f "$FILE" ]; then
  echo "===== $FILE (lines 140-240) ====="
  sed -n '140,240p' "$FILE" | cat -n
  echo "===== $FILE (lines 260-360) ====="
  sed -n '260,380p' "$FILE" | cat -n
else
  echo "Missing file: $FILE"
fi

Repository: suitenumerique/dictaphone

Length of output: 8927


Fix tab-audio “supported” gating to reflect real audio-track availability.

TabAudioInputManager.isSupported() only checks for navigator.mediaDevices?.getDisplayMedia (lines 13-15), but requestStream() throws No tab audio source was selected. when the captured MediaStream contains no audio tracks (lines 38-41). RecorderManager.supportsTabAudioCapture() directly proxies that predicate (line 121-123), useRecordingController maps it to tabAudioSupported, and RecordComponent shows the tab-audio enable button whenever tabAudioSupported is true—so users can be offered a control that will fail at runtime and surface recordingError.

Update the support signal to be based on actual successful capture (e.g., hide/disable the button until a capture yields stream.getAudioTracks().length > 0, and/or downgrade support after the “No tab audio source…” failure), rather than assuming getDisplayMedia implies usable tab audio.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/src/features/recordings/recorder/TabAudioInputManager.ts` around
lines 13 - 15, isSupported currently only checks for
navigator.mediaDevices?.getDisplayMedia but that can return streams without
audio; change the support gating to reflect actual audio-track availability by
(a) making TabAudioInputManager.isSupported() return a promise/async boolean
that attempts a silent getDisplayMedia({audio:true,video:true}) or otherwise
queries requestStream() and resolves true only if the returned MediaStream has
stream.getAudioTracks().length > 0, (b) update
RecorderManager.supportsTabAudioCapture() to await the new async isSupported or
use a cached flag, and (c) on requestStream() failure with "No tab audio source
was selected." downgrade the cached support flag so subsequent UI
(useRecordingController/RecordComponent) hides/disables the tab-audio control
until a successful capture occurs; reference TabAudioInputManager.isSupported,
TabAudioInputManager.requestStream, and RecorderManager.supportsTabAudioCapture
when implementing this behavior.

"tabAudio": {
"add": "Ajouter l'audio d'un onglet",
"remove": "Retirer l'audio d'un onglet",
"activeLabel": "Audio onglet : {{label}}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider more grammatically complete French phrasing.

"Audio onglet : {{label}}" is understandable but slightly terse. Consider "Audio de l'onglet : {{label}}" for more natural French.

✨ Optional refinement
-    "activeLabel": "Audio onglet : {{label}}"
+    "activeLabel": "Audio de l'onglet : {{label}}"
📝 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
"activeLabel": "Audio onglet : {{label}}"
"activeLabel": "Audio de l'onglet : {{label}}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/src/locales/fr-FR/record.json` at line 29, The translation
string keyed by "activeLabel" is terse; update its value from "Audio onglet :
{{label}}" to a more natural French phrasing such as "Audio de l'onglet :
{{label}}". Locate the "activeLabel" entry in
src/frontend/src/locales/fr-FR/record.json and replace the string while
preserving the {{label}} interpolation token exactly.

@prince0xdev prince0xdev force-pushed the feat/pip-recorder-web branch from c91ebea to feeec0d Compare June 11, 2026 16:13
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.

As a WebApp User, I'd like to have PiP in Chrome when recording

2 participants