Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions client/src/components/PeakLevelMeter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import React, { useEffect, useRef, useState } from "react";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | πŸ—οΈ Heavy lift

The "real-time audio peak meter" never samples real audio in either page it's integrated into. PeakLevelMeter only samples actual audio when given analyserNode or audioElementRef; otherwise, whenever isActive is true it synthesizes maxVal = 0.2 + Math.random() * 0.45 every animation frame. Both integration sites pass only isActive={true}, so the meter permanently displays fabricated levels unrelated to any synthesized speech, defeating the linked issue's goal of visually surfacing real clipping (notably for users who can't rely on hearing).

  • client/src/components/PeakLevelMeter.jsx#L74-97: this is the root cause β€” the else if (isActive) random fallback should not silently stand in for real sampling; consider requiring/warning when isActive is set without a real analyser, or clearly labeling the meter as "demo mode" in that case.
  • client/src/pages/Onboarding.jsx#L152-156: pass a real analyserNode/audioElementRef tied to the TTS preview/test audio output instead of bare isActive={true}.
  • client/src/pages/Settings.jsx#L325-332: same β€” wire this to the actual voice-synthesis preview audio element/analyser rather than isActive={true} alone.
πŸ€– 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 `@client/src/components/PeakLevelMeter.jsx` at line 1, Remove the random level
generation fallback in PeakLevelMeter and require real audio input through
analyserNode or audioElementRef; when isActive is provided without either
source, warn or explicitly mark the component as demo mode instead of reporting
fabricated levels. Update the Onboarding and Settings integrations to pass the
analyser or audio-element reference for their actual TTS preview/test output
while preserving active-state behavior.

import { Volume2, VolumeX, AlertTriangle, Activity } from "lucide-react";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: Volume2 and VolumeX are unused imports, adding dead code and causing a no-unused-vars failure in configurations that lint this client. Removing them would keep the import aligned with the component.

Prompt for AI agents
Check if this issue is valid β€” if so, understand the root cause and fix it. At client/src/components/PeakLevelMeter.jsx, line 2:

<comment>`Volume2` and `VolumeX` are unused imports, adding dead code and causing a no-unused-vars failure in configurations that lint this client. Removing them would keep the import aligned with the component.</comment>

<file context>
@@ -0,0 +1,175 @@
+import React, { useEffect, useRef, useState } from "react";
+import { Volume2, VolumeX, AlertTriangle, Activity } from "lucide-react";
+
+/**
</file context>


/**
* PeakLevelMeter
* Displays a real-time VU Peak Level Meter (-60 dB to 0 dB+) driven by Web Audio API.
* Features an active clipping warning indicator when peak volume exceeds 0 dB (or 0.95 peak amplitude).
*/
export function PeakLevelMeter({
audioElementRef,
analyserNode,
isActive = false,
testLevel = null,
showLabels = true,
className = "",
}) {
const [peakDb, setPeakDb] = useState(-60);
const [isClipping, setIsClipping] = useState(false);
const [maxPeakDb, setMaxPeakDb] = useState(-60);
const animationFrameRef = useRef(null);
const internalAnalyserRef = useRef(null);
const audioCtxRef = useRef(null);

useEffect(() => {
// If a direct numeric test level is provided, use it directly (useful for tests and static demos)
if (testLevel !== null) {
const level = Math.max(-60, Math.min(6, testLevel));
setPeakDb(level);
setIsClipping(level >= 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Clip threshold is inconsistent between testLevel mode and live sampling. Here, clipping triggers at level >= 0, but in the live path (line 93) it triggers at db >= -0.5 || maxVal >= 0.95. A testLevel value like -0.3 won't show the clip warning even though the same peak would trigger it in the live path, making tests and demos misrepresent actual behavior.

Consider aligning the thresholds, e.g. setIsClipping(level >= -0.5).

Prompt for AI agents
Check if this issue is valid β€” if so, understand the root cause and fix it. At client/src/components/PeakLevelMeter.jsx, line 29:

<comment>Clip threshold is inconsistent between `testLevel` mode and live sampling. Here, clipping triggers at `level >= 0`, but in the live path (line 93) it triggers at `db >= -0.5 || maxVal >= 0.95`. A `testLevel` value like `-0.3` won't show the clip warning even though the same peak would trigger it in the live path, making tests and demos misrepresent actual behavior.

Consider aligning the thresholds, e.g. `setIsClipping(level >= -0.5)`.</comment>

<file context>
@@ -0,0 +1,175 @@
+    if (testLevel !== null) {
+      const level = Math.max(-60, Math.min(6, testLevel));
+      setPeakDb(level);
+      setIsClipping(level >= 0);
+      setMaxPeakDb((prev) => Math.max(prev, level));
+      return;
</file context>

setMaxPeakDb((prev) => Math.max(prev, level));
return;
}
Comment on lines +26 to +32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟑 Minor | ⚑ Quick win

Clip threshold differs between testLevel mode and live sampling.

testLevel mode clips at level >= 0 (Line 29) while live sampling clips at db >= -0.5 || maxVal >= 0.95 (Line 93). Since testLevel is documented as "useful for tests and static demos," a demo value like -0.3 won't show the clip warning even though the same peak would trigger it in live mode, making demos/tests misrepresent production behavior.

Also applies to: 90-94

🧰 Tools
πŸͺ› ast-grep (0.44.1)

[warning] 27-27: Avoid using the initial state variable in setState
Context: setPeakDb(level)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

πŸ€– 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 `@client/src/components/PeakLevelMeter.jsx` around lines 26 - 32, Align the
clipping logic in the testLevel branch of PeakLevelMeter with the live-sampling
thresholds: treat test values at or above -0.5 dB as clipping, while preserving
the existing level clamping, peak tracking, and live maxVal threshold behavior.


let analyser = analyserNode;

// Connect to audioElementRef if provided and analyserNode is not directly passed
if (!analyser && audioElementRef?.current) {
try {
const audioEl = audioElementRef.current;
if (!audioEl._vfSource) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Passing an audio element already managed by AudioProcessor attempts to create a second MediaElementSource, after which this component has no analyser and falls back to fake activity. Reusing the existing audio graph/analyser or sharing one source marker would avoid this integration failure.

Prompt for AI agents
Check if this issue is valid β€” if so, understand the root cause and fix it. At client/src/components/PeakLevelMeter.jsx, line 40:

<comment>Passing an audio element already managed by `AudioProcessor` attempts to create a second `MediaElementSource`, after which this component has no analyser and falls back to fake activity. Reusing the existing audio graph/analyser or sharing one source marker would avoid this integration failure.</comment>

<file context>
@@ -0,0 +1,175 @@
+    if (!analyser && audioElementRef?.current) {
+      try {
+        const audioEl = audioElementRef.current;
+        if (!audioEl._vfSource) {
+          const AudioContext = window.AudioContext || window.webkitAudioContext;
+          if (AudioContext) {
</file context>

const AudioContext = window.AudioContext || window.webkitAudioContext;
if (AudioContext) {
const ctx = new AudioContext();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: The AudioContext created in the useEffect is never closed when the component unmounts. The cleanup function only cancels the animation frame (cancelAnimationFrame) but does not call audioCtxRef.current?.close(). In React Strict Mode (dev), effects fire twice, which can create and leak two AudioContext instances β€” and browsers impose a hard limit (~6 per document) on open AudioContexts, so repeated mounts quickly exhaust them. Add if (audioCtxRef.current) { audioCtxRef.current.close(); } to the effect's cleanup function.

Prompt for AI agents
Check if this issue is valid β€” if so, understand the root cause and fix it. At client/src/components/PeakLevelMeter.jsx, line 43:

<comment>The AudioContext created in the `useEffect` is never closed when the component unmounts. The cleanup function only cancels the animation frame (`cancelAnimationFrame`) but does not call `audioCtxRef.current?.close()`. In React Strict Mode (dev), effects fire twice, which can create and leak two AudioContext instances β€” and browsers impose a hard limit (~6 per document) on open AudioContexts, so repeated mounts quickly exhaust them. Add `if (audioCtxRef.current) { audioCtxRef.current.close(); }` to the effect's cleanup function.</comment>

<file context>
@@ -0,0 +1,175 @@
+        if (!audioEl._vfSource) {
+          const AudioContext = window.AudioContext || window.webkitAudioContext;
+          if (AudioContext) {
+            const ctx = new AudioContext();
+            audioCtxRef.current = ctx;
+            const source = ctx.createMediaElementSource(audioEl);
</file context>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: An audio-element-backed meter can remain at silence when the context starts suspended because this effect never resumes it. Resuming from an allowed user/play event, or accepting a caller-owned running context/analyser, would make the optional audio path reliable.

Prompt for AI agents
Check if this issue is valid β€” if so, understand the root cause and fix it. At client/src/components/PeakLevelMeter.jsx, line 43:

<comment>An audio-element-backed meter can remain at silence when the context starts suspended because this effect never resumes it. Resuming from an allowed user/play event, or accepting a caller-owned running context/analyser, would make the optional audio path reliable.</comment>

<file context>
@@ -0,0 +1,175 @@
+        if (!audioEl._vfSource) {
+          const AudioContext = window.AudioContext || window.webkitAudioContext;
+          if (AudioContext) {
+            const ctx = new AudioContext();
+            audioCtxRef.current = ctx;
+            const source = ctx.createMediaElementSource(audioEl);
</file context>

audioCtxRef.current = ctx;
const source = ctx.createMediaElementSource(audioEl);
const node = ctx.createAnalyser();
node.fftSize = 256;
source.connect(node);
node.connect(ctx.destination);
audioEl._vfSource = source;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: AudioNode references are stored directly on the DOM element via _vfSource and _vfAnalyser custom properties. This mutates DOM nodes outside React's control, which could conflict with React reconciliation if the element is replaced. These properties are also never cleaned up when the component unmounts, creating stale references. Consider managing these AudioNodes via refs scoped to the component instance instead.

Prompt for AI agents
Check if this issue is valid β€” if so, understand the root cause and fix it. At client/src/components/PeakLevelMeter.jsx, line 50:

<comment>AudioNode references are stored directly on the DOM element via `_vfSource` and `_vfAnalyser` custom properties. This mutates DOM nodes outside React's control, which could conflict with React reconciliation if the element is replaced. These properties are also never cleaned up when the component unmounts, creating stale references. Consider managing these AudioNodes via refs scoped to the component instance instead.</comment>

<file context>
@@ -0,0 +1,175 @@
+            node.fftSize = 256;
+            source.connect(node);
+            node.connect(ctx.destination);
+            audioEl._vfSource = source;
+            audioEl._vfAnalyser = node;
+          }
</file context>

audioEl._vfAnalyser = node;
}
}
analyser = audioEl._vfAnalyser || null;
} catch (err) {
// Fallback for media element already connected or blocked cross-origin
console.debug("PeakLevelMeter Web Audio connection fallback:", err);
}
}

internalAnalyserRef.current = analyser;

if (!isActive && !analyser) {
setPeakDb(-60);
setIsClipping(false);
return;
}

const dataArray = new Uint8Array(analyser ? analyser.frequencyBinCount : 128);

const updateMeter = () => {
let maxVal = 0;

if (analyser) {
analyser.getByteTimeDomainData(dataArray);
for (let i = 0; i < dataArray.length; i++) {
const sample = Math.abs((dataArray[i] - 128) / 128);
if (sample > maxVal) maxVal = sample;
}
} else if (isActive) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P0: The VU meter shows simulated random data instead of actual audio levels when no audio source is connected. When isActive={true} is passed without an audioElementRef or analyserNode (as in both Onboarding.jsx and Settings.jsx), the updateMeter loop falls through to maxVal = 0.2 + Math.random() * 0.45, generating a bouncing meter that looks like real audio processing but is just random noise. Users tuning voice synthesis sliders will see a live-animated meter that does not reflect any actual volume, which is misleading and undermines the feature's purpose. Consider either wiring the meter to an actual audio element/stream, disabling the simulation path, or explicitly labeling the display as "simulated" when no audio source is connected.

Prompt for AI agents
Check if this issue is valid β€” if so, understand the root cause and fix it. At client/src/components/PeakLevelMeter.jsx, line 80:

<comment>The VU meter shows simulated random data instead of actual audio levels when no audio source is connected. When `isActive={true}` is passed without an `audioElementRef` or `analyserNode` (as in both Onboarding.jsx and Settings.jsx), the `updateMeter` loop falls through to `maxVal = 0.2 + Math.random() * 0.45`, generating a bouncing meter that looks like real audio processing but is just random noise. Users tuning voice synthesis sliders will see a live-animated meter that does not reflect any actual volume, which is misleading and undermines the feature's purpose. Consider either wiring the meter to an actual audio element/stream, disabling the simulation path, or explicitly labeling the display as "simulated" when no audio source is connected.</comment>

<file context>
@@ -0,0 +1,175 @@
+          const sample = Math.abs((dataArray[i] - 128) / 128);
+          if (sample > maxVal) maxVal = sample;
+        }
+      } else if (isActive) {
+        // Simulated ambient meter activity when active without direct media stream
+        maxVal = 0.2 + Math.random() * 0.45;
</file context>

// Simulated ambient meter activity when active without direct media stream
maxVal = 0.2 + Math.random() * 0.45;
}

// Convert amplitude (0 to 1) to Decibels (-60 dB to +3 dB)
let db = -60;
if (maxVal > 0.0001) {
db = 20 * Math.log10(maxVal);
}
db = Math.max(-60, Math.min(3, db));

setPeakDb(db);
const clippingDetected = db >= -0.5 || maxVal >= 0.95;
setIsClipping(clippingDetected);
setMaxPeakDb((prev) => Math.max(prev, db));

animationFrameRef.current = requestAnimationFrame(updateMeter);
Comment on lines +74 to +97

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | πŸ—οΈ Heavy lift

Meter always shows simulated random data, never real audio, when only isActive is set.

When no analyserNode/audioElementRef is supplied (exactly how both Onboarding.jsx and Settings.jsx currently invoke this component), updateMeter falls into the else if (isActive) branch and fabricates maxVal = 0.2 + Math.random() * 0.45 every frame. The "VU Peak Level Meter" never actually samples any real audio in either integration β€” it's a random animation. This conflicts with the linked issue's requirement to "Sample audio peaks through a Web Audio AnalyserNode" and to give users (including those who can't rely on hearing) real visual feedback about actual clipping.

See consolidated comment for the cross-file fix (wiring a real AnalyserNode/audio element into both pages, or gating the simulated fallback behind an explicit demo/testLevel-only mode).

🧰 Tools
πŸͺ› ast-grep (0.44.1)

[warning] 91-91: Avoid using the initial state variable in setState
Context: setPeakDb(db)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)


[warning] 93-93: Avoid using the initial state variable in setState
Context: setIsClipping(clippingDetected)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

πŸ€– 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 `@client/src/components/PeakLevelMeter.jsx` around lines 74 - 97, Update
PeakLevelMeter’s updateMeter flow so normal isActive usage never generates
random levels without real audio; wire a real AnalyserNode or audioElementRef
into the Onboarding and Settings integrations, or restrict the simulated
fallback to an explicit demo/testLevel mode. Ensure the meter samples actual
audio through the analyser and preserves simulation only when that mode is
intentionally enabled.

};

animationFrameRef.current = requestAnimationFrame(updateMeter);

return () => {
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
}
};
}, [audioElementRef, analyserNode, isActive, testLevel]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: If the referenced media element is mounted or replaced after this effect runs, the meter never reconnects because the ref object remains unchanged; with isActive, it can stay on the fallback path indefinitely. Passing the element/analyser as reactive state or using a callback ref would track that lifecycle.

Prompt for AI agents
Check if this issue is valid β€” if so, understand the root cause and fix it. At client/src/components/PeakLevelMeter.jsx, line 107:

<comment>If the referenced media element is mounted or replaced after this effect runs, the meter never reconnects because the ref object remains unchanged; with `isActive`, it can stay on the fallback path indefinitely. Passing the element/analyser as reactive state or using a callback ref would track that lifecycle.</comment>

<file context>
@@ -0,0 +1,175 @@
+        cancelAnimationFrame(animationFrameRef.current);
+      }
+    };
+  }, [audioElementRef, analyserNode, isActive, testLevel]);
+
+  // Map peakDb (-60dB to 0dB) to a percentage (0% to 100%)
</file context>


// Map peakDb (-60dB to 0dB) to a percentage (0% to 100%)
const percentage = Math.max(0, Math.min(100, ((peakDb + 60) / 60) * 100));

function resetMaxPeak() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: resetMaxPeak is dead code, and no user-facing path can reset the max-peak hold despite the helper being present. Exposing a reset control/callback or removing the unused helper would keep the component behavior and API intentional.

Prompt for AI agents
Check if this issue is valid β€” if so, understand the root cause and fix it. At client/src/components/PeakLevelMeter.jsx, line 112:

<comment>`resetMaxPeak` is dead code, and no user-facing path can reset the max-peak hold despite the helper being present. Exposing a reset control/callback or removing the unused helper would keep the component behavior and API intentional.</comment>

<file context>
@@ -0,0 +1,175 @@
+  // Map peakDb (-60dB to 0dB) to a percentage (0% to 100%)
+  const percentage = Math.max(0, Math.min(100, ((peakDb + 60) / 60) * 100));
+
+  function resetMaxPeak() {
+    setMaxPeakDb(-60);
+    setIsClipping(false);
</file context>

setMaxPeakDb(-60);
setIsClipping(false);
}

return (
<div className={`rounded-lg border border-ink/10 bg-white p-3.5 shadow-soft dark:border-border dark:bg-surface dark:shadow-soft-dk ${className}`}>
<div className="flex items-center justify-between gap-2 mb-2">
<div className="flex items-center gap-2">
<Activity size={16} className={isClipping ? "text-red-500 animate-pulse" : "text-moss dark:text-glow"} aria-hidden="true" />
<span className="text-xs font-bold uppercase tracking-wider text-ink dark:text-neutral-200">
VU Peak Level Meter
</span>
</div>

{/* Digital Clipping Warning Indicator Badge */}
<div className="flex items-center gap-1.5">
{isClipping ? (
<div
role="alert"
aria-live="assertive"
className="inline-flex items-center gap-1 rounded bg-red-600 px-2 py-0.5 text-[11px] font-bold uppercase text-white shadow animate-bounce"
>
<AlertTriangle size={12} aria-hidden="true" />
<span>CLIP WARNING!</span>
</div>
) : (
<span className="text-[11px] font-mono text-ink/60 dark:text-neutral-400">
{peakDb <= -60 ? "-∞ dB" : `${peakDb.toFixed(1)} dB`}
</span>
)}
</div>
</div>

{/* Meter Bar Container */}
<div className="relative h-4 w-full overflow-hidden rounded-full bg-ink/10 dark:bg-black/40 p-0.5 border border-ink/5 dark:border-border">
{/* Animated Fill Bar with Color Gradient (Green -> Yellow -> Red) */}
<div
className="h-full rounded-full transition-all duration-75 bg-gradient-to-r from-emerald-500 via-amber-400 to-red-500"
style={{ width: `${percentage}%` }}
/>

{/* Max Peak Hold Indicator Line */}
{maxPeakDb > -60 && (
<div
className="absolute top-0 bottom-0 w-0.5 bg-ink dark:bg-white shadow"
style={{ left: `${Math.max(0, Math.min(99, ((maxPeakDb + 60) / 60) * 100))}%` }}
title={`Max Peak: ${maxPeakDb.toFixed(1)} dB`}
/>
)}
</div>

{showLabels && (
<div className="mt-1.5 flex justify-between text-[10px] font-mono text-ink/50 dark:text-neutral-400">
<span>-60 dB</span>
<span>-36 dB</span>
<span>-18 dB</span>
<span>-6 dB</span>
<span className={isClipping ? "font-bold text-red-500" : ""}>0 dB</span>
</div>
)}
</div>
);
}
8 changes: 8 additions & 0 deletions client/src/components/PeakLevelMeter.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { describe, it, expect } from "vitest";
import { PeakLevelMeter } from "./PeakLevelMeter";

describe("PeakLevelMeter component", () => {
it("exports PeakLevelMeter function component", () => {
expect(typeof PeakLevelMeter).toBe("function");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The test only verifies that PeakLevelMeter exports as a function. For a component that manages Web Audio APIs, animated state, and clipping logic, this provides essentially no behavioral coverage. Consider adding render tests (with @testing-library/react) that verify the meter bar updates when testLevel changes, the clipping warning appears at expected thresholds, and the component handles mount/unmount without errors.

Prompt for AI agents
Check if this issue is valid β€” if so, understand the root cause and fix it. At client/src/components/PeakLevelMeter.test.js, line 6:

<comment>The test only verifies that `PeakLevelMeter` exports as a function. For a component that manages Web Audio APIs, animated state, and clipping logic, this provides essentially no behavioral coverage. Consider adding render tests (with `@testing-library/react`) that verify the meter bar updates when `testLevel` changes, the clipping warning appears at expected thresholds, and the component handles mount/unmount without errors.</comment>

<file context>
@@ -0,0 +1,8 @@
+
+describe("PeakLevelMeter component", () => {
+  it("exports PeakLevelMeter function component", () => {
+    expect(typeof PeakLevelMeter).toBe("function");
+  });
+});
</file context>

});
});
7 changes: 7 additions & 0 deletions client/src/pages/Onboarding.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import React from "react";
import { CheckCircle2, Loader2, CircleAlert, ArrowRight, RotateCcw } from "lucide-react";
import VoiceRecorder from "../components/VoiceRecorder.jsx";
import useVoiceClone from "../hooks/useVoiceClone.js";
import { COLOR_TAGS, AVATAR_ICONS } from "../components/ProfileCard.jsx";
import { PeakLevelMeter } from "../components/PeakLevelMeter.jsx";
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
import { useToast, ToastContainer } from "../components/useToast.jsx";

import {
Expand Down Expand Up @@ -147,6 +149,11 @@ function Step2VoiceSettings({ onBack, onContinue }) {
/>
</div>

{/* Audio Peak Level VU Meter & Clipping Warning */}
<div className="mt-5 pt-3 border-t border-ink/10 dark:border-border">
<PeakLevelMeter isActive={true} />
</div>

Comment on lines +152 to +156

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major

Meter mounted with no real audio source (see consolidated comment).

πŸ€– 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 `@client/src/pages/Onboarding.jsx` around lines 152 - 156, Update the
onboarding audio section containing PeakLevelMeter so it is not mounted with a
hardcoded active state when no real audio source is available. Pass the actual
audio activity/source state used by the onboarding flow, or conditionally render
the meter only when that source exists; preserve the surrounding
clipping-warning layout.

{/* ── Info note ── */}
<p className="mt-4 text-xs text-ink/50 dark:text-muted">
These values are also adjustable in the{" "}
Expand Down
7 changes: 6 additions & 1 deletion client/src/pages/Settings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { saveProfile } from "../utils/db.js";
import { ProfileCard } from "../components/ProfileCard.jsx";
import { ShareProfileModal } from "../components/ShareProfileModal.jsx";
import { ReceiveProfileModal } from "../components/ReceiveProfileModal.jsx";
import { AudioOutputSelector } from "../components/AudioOutputSelector.jsx";
import { PeakLevelMeter } from "../components/PeakLevelMeter.jsx";

function AudioPlayback({ blob }) {
const [audioUrl, setAudioUrl] = React.useState(null);
Expand Down Expand Up @@ -572,6 +572,11 @@ export default function Settings() {
</p>
</div>
</div>

{/* Audio Peak Level VU Meter & Clipping Warning */}
<div className="mt-5 pt-4 border-t border-ink/10 dark:border-border">
<PeakLevelMeter isActive={true} />
</div>
</section>

{/* ── Language & Region ─────────────────────────────────────────── */}
Expand Down
Loading