Skip to content

Add ffmpeg wasm #1822

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: add-audio-ui
Choose a base branch
from
Draft

Add ffmpeg wasm #1822

wants to merge 2 commits into from

Conversation

myieye
Copy link
Contributor

@myieye myieye commented Jul 14, 2025

WIP/draft because loading ffmpeg was just slapped somewhere awkward to demonstrate that it works.

So, it works, but I'm not very happy with how loading the ffmpeg worker is set up.

In prod, we don't do anything fancy at all and it just works, yay!

But in dev:

  1. To load the worker from vite, we have to bundle the whole worker with all dependencies in a single file so it can be fetched as a blob or data URL to get around CORS
    • It took me forever to find something that worked and it's kind of weird. All the vite worker features that sound promising, didn't get me anywhere, because they're not expecting to be turned into a blob URL or anticipating CORS issues.
  2. Serve the worker from Blazor so CORS isn't an issue

I've implemented 1, but my implementation involves:

  • an additional vite config for bundling the worker
  • importing that bundle as a funky data url (which is the only way I found that avoided vite injecting imports that seemed to be breaking the worker)

So.... maybe 2 is better, because it would maybe only mean manually bundling the worker and then dev and prod might be able to be the same?
But then we'd be responsible to make sure browsers don't cache old versions, even in prod. (e.g. add a hash and figure out how to import it anyway).

Or maybe 3 is better, whatever 3 might be.

Copy link

coderabbitai bot commented Jul 14, 2025

📝 Walkthrough

Walkthrough

This update introduces a comprehensive audio upload, recording, and editing workflow to the frontend viewer. It adds new Svelte components for audio dialogs, waveform visualization, and recording, integrates FFmpeg and WaveSurfer.js, and extends the dialogs service. Supporting build scripts, dependencies, and utility modules are also added or updated to enable browser-based audio processing and UI enhancements.

Changes

Files/Groups Change Summary
frontend/package.json Added a trailing newline; no content changes.
frontend/viewer/package.json Added build script and dependencies for FFmpeg and WaveSurfer.js; introduced "build-ffmpeg-worker" script.
.../src/lib/DialogsProvider.svelte Imported and rendered new <AudioDialog/> component.
.../src/lib/components/audio/AudioDialog.svelte New Svelte component for audio upload/record/selection dialog; exposes async method for obtaining audio ID via dialogs service.
.../src/lib/components/audio/audio-editor.svelte New Svelte component for editing audio: displays waveform, controls playback, shows metadata, and allows discarding.
.../src/lib/components/audio/audio-provider.svelte New Svelte component for selecting or recording audio files; exposes callbacks for file selection and recording completion.
.../src/lib/components/audio/ffmpeg/bundled-ffmpeg-worker.js New web worker script for running FFmpeg operations in-browser; supports commands for media processing and virtual filesystem manipulation.
.../src/lib/components/audio/ffmpeg/ffmpeg-api.ts New module exporting functions to load FFmpeg and convert audio to WAV using WebAssembly in the browser.
.../src/lib/components/audio/ffmpeg/index.ts Re-exports all exports from ffmpeg-api.ts for centralized imports.
.../src/lib/components/audio/recorder/index.ts New module re-exporting recorder and trigger Svelte components under multiple aliases.
.../src/lib/components/audio/recorder/recorder-trigger.svelte New Svelte component for a button controlling audio recording, supporting walkie-talkie mode and keyboard/pointer events.
.../src/lib/components/audio/recorder/recorder.svelte New Svelte component for audio recording with waveform visualization and context-based state sharing; exposes recording hooks.
.../src/lib/components/audio/wavesurfer/waveform.svelte New Svelte component to render audio waveform using WaveSurfer.js; supports playback, timeline, and autoplay.
.../src/lib/components/audio/wavesurfer/wavesurfer-utils.ts New utility module for WaveSurfer.js integration; exports hook for creating and managing waveform instances with theme adaptation.
.../src/lib/components/field-editors/audio-input.svelte Enhanced audio input editor: integrates dialog-based audio selection, removal, and responsive menu for audio actions; updates prop binding.
.../src/lib/components/ui/button/button.svelte Updated icon button size variants with max size constraints; added 'xl-icon' size; improved icon alignment.
.../src/lib/components/ui/dialog/dialog-footer.svelte Added items-end and flex-wrap classes to dialog footer for improved alignment and wrapping.
.../src/lib/components/ui/format/format-duration.ts Added formatDigitalDuration function for digital-style duration formatting.
.../src/lib/sandbox/Sandbox.svelte Added function and button to preload FFmpeg for testing/demo purposes.
.../src/lib/services/dialogs-service.ts Extended dialogs service: added support for registering and invoking an audio dialog via async method.
frontend/viewer/vite.config.ffmpeg-worker.ts New Vite config for building the FFmpeg worker script; sets entry/output for worker bundling.

Suggested labels

📦 Lexbox

Poem

In fields of code where waveforms play,
A rabbit hops with joy today.
Audio flows, FFmpeg spins,
Record and edit—let the fun begin!
With dialogs bright and buttons neat,
Our frontend’s sound is now complete.
🐇🎵

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@myieye myieye marked this pull request as draft July 14, 2025 23:12
@github-actions github-actions bot added 💻 FW Lite issues related to the fw lite application, not miniLcm or crdt related 📦 Lexbox issues related to any server side code, fw-headless included labels Jul 14, 2025
Copy link

github-actions bot commented Jul 14, 2025

UI unit Tests

  1 files  ±0   38 suites  ±0   20s ⏱️ -1s
 80 tests ±0   80 ✅ ±0  0 💤 ±0  0 ❌ ±0 
111 runs  ±0  111 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 756df89. ± Comparison against base commit 8f80e94.

♻️ This comment has been updated with latest results.

const l = async ({ coreURL: t, wasmURL: n, workerURL: e }) => {
const o = !r;
try {
t || (t = R), importScripts(t);

Check warning

Code scanning / CodeQL

Client-side URL redirect Medium

Untrusted URL redirection depends on a
user-provided value
.

Copilot Autofix

AI 1 day ago

To fix the issue, we need to validate the coreURL parameter (t) before using it in the importScripts function. A whitelist-based approach is recommended, where only URLs from a predefined list of trusted sources are allowed. If the provided URL does not match any trusted source, the code should either reject it or fall back to the default URL (R).

Steps to implement the fix:

  1. Define a list of trusted base URLs.
  2. Validate the coreURL parameter against this list.
  3. If the URL is not trusted, either throw an error or use the default URL (R).

Suggested changeset 1
frontend/viewer/src/lib/components/audio/ffmpeg/bundled-ffmpeg-worker.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/frontend/viewer/src/lib/components/audio/ffmpeg/bundled-ffmpeg-worker.js b/frontend/viewer/src/lib/components/audio/ffmpeg/bundled-ffmpeg-worker.js
--- a/frontend/viewer/src/lib/components/audio/ffmpeg/bundled-ffmpeg-worker.js
+++ b/frontend/viewer/src/lib/components/audio/ffmpeg/bundled-ffmpeg-worker.js
@@ -12,3 +12,8 @@
   try {
-    t || (t = R), importScripts(t);
+    const trustedSources = ["https://unpkg.com/@ffmpeg/core"]; // Add trusted base URLs here
+    const isTrusted = trustedSources.some((base) => t && t.startsWith(base));
+    if (!isTrusted) {
+      t = R; // Fallback to the default URL if the provided URL is not trusted
+    }
+    importScripts(t);
   } catch {
EOF
@@ -12,3 +12,8 @@
try {
t || (t = R), importScripts(t);
const trustedSources = ["https://unpkg.com/@ffmpeg/core"]; // Add trusted base URLs here
const isTrusted = trustedSources.some((base) => t && t.startsWith(base));
if (!isTrusted) {
t = R; // Fallback to the default URL if the provided URL is not trusted
}
importScripts(t);
} catch {
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link

C# Unit Tests

126 tests   126 ✅  12s ⏱️
 20 suites    0 💤
  1 files      0 ❌

Results for commit 756df89.

Copy link

@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: 11

🧹 Nitpick comments (11)
frontend/viewer/src/lib/components/audio/wavesurfer/wavesurfer-utils.ts (2)

58-61: Remove commented sample URLs.

The commented sample URLs should be removed from production code.

-    // sample audio
-    // url: 'https://cdn.freesound.org/previews/815/815388_16624953-lq.mp3', // 2.8s
-    // url: 'https://dl.espressif.com/dl/audio/gs-16b-2c-44100hz.aac', // 16s
-    // url: 'https://dl.espressif.com/dl/audio/ff-16b-2c-44100hz.aac', // 3m 7s

75-79: Simplify cleanup logic.

The cleanup logic is redundant - both onDestroy and wavesurfer.on('destroy') call the same unsubscribe function.

   onDestroy(() => {
     unsub();
     wavesurfer.destroy();
   });
-  wavesurfer.on('destroy', unsub);

The onDestroy callback already handles cleanup when the component is destroyed, making the wavesurfer destroy event listener unnecessary.

frontend/viewer/vite.config.ffmpeg-worker.ts (2)

4-5: Consider error handling for module resolution.

The require.resolve() call could fail if the FFmpeg package is not installed or the worker path changes. Consider adding error handling or validation.

const require = createRequire(import.meta.url);
-const ffmpegWorkerPath = require.resolve('@ffmpeg/ffmpeg/worker');
+let ffmpegWorkerPath: string;
+try {
+  ffmpegWorkerPath = require.resolve('@ffmpeg/ffmpeg/worker');
+} catch (error) {
+  throw new Error(`Failed to resolve FFmpeg worker: ${error.message}`);
+}

19-19: Consider making output directory configurable.

The hardcoded output path 'src/lib/components/audio/ffmpeg' makes the configuration less flexible. Consider using a variable or environment-based configuration.

+const outputDir = process.env.FFMPEG_WORKER_OUTPUT_DIR || 'src/lib/components/audio/ffmpeg';
+
export default defineConfig({
  // ...
  build: {
    // ...
-    outDir: 'src/lib/components/audio/ffmpeg',
+    outDir: outputDir,
    // ...
  },
});
frontend/viewer/src/lib/components/ui/format/format-duration.ts (1)

16-16: Consider clarifying smallest unit logic.

The smallest unit determination based on minutes seems logical, but consider documenting the reasoning or verifying that the formatDuration function properly handles this parameter as expected.

frontend/viewer/src/lib/components/audio/audio-editor.svelte (1)

56-57: Enhance audio control robustness.

The play/pause logic could be more robust by checking the current state before attempting operations.

-      onclick={() => (playing ? audioApi?.pause() : audioApi?.play())}
+      onclick={() => {
+        if (!audioApi) return;
+        if (playing) {
+          audioApi.pause();
+        } else {
+          audioApi.play().catch(console.error);
+        }
+      }}
frontend/viewer/src/lib/components/audio/ffmpeg/ffmpeg-api.ts (1)

36-42: Consider using a generic filename for input

The function accepts any audio Blob but uses 'input.mp3' as the filename, which could be misleading if other formats are provided.

Consider using a generic filename or deriving it from the blob type:

-  const inputName = 'input.mp3';
+  const inputName = 'input.audio'; // FFmpeg will auto-detect the format
frontend/viewer/src/lib/components/field-editors/audio-input.svelte (1)

162-170: Address TODO: Fake audio ID implementation

The TODO comment indicates that the audio ID implementation is incomplete. This should be addressed before merging.

Would you like me to help implement the proper audio ID handling or create an issue to track this task?

frontend/viewer/src/lib/components/audio/recorder/recorder.svelte (1)

146-146: Remove unnecessary semicolon

-    };
+    }
frontend/viewer/src/lib/components/audio/AudioDialog.svelte (2)

68-74: Replace mock upload implementation with actual audio upload.

The uploadAudio function is currently a simulation that generates a fake ID and doesn't actually upload audio. This needs to be replaced with real upload logic.

-async function uploadAudio() {
-  if (!audio) throw new Error('No file selected');
-  const name = (selectedFile?.name ?? audio.type);
-  const id = `audio-${name}-${Date.now()}`;
-  await delay(1000);
-  return id;
-}
+async function uploadAudio() {
+  if (!audio) throw new Error('No file selected');
+  // TODO: Implement actual audio upload to server
+  const formData = new FormData();
+  formData.append('audio', audio, selectedFile?.name || 'recording.wav');
+  
+  const response = await fetch('/api/audio/upload', {
+    method: 'POST',
+    body: formData
+  });
+  
+  if (!response.ok) {
+    throw new Error(`Upload failed: ${response.statusText}`);
+  }
+  
+  const result = await response.json();
+  return result.id;
+}

Would you like me to implement the actual audio upload functionality or open an issue to track this task?


49-52: Add memory cleanup for audio blobs.

Audio blobs can consume significant memory. Consider revoking object URLs and explicitly cleaning up blob references to prevent memory leaks.

 function clearAudio() {
+  // Clean up blob URLs if any were created
+  if (audio && audio instanceof File === false) {
+    // If audio is a blob created from recording, ensure cleanup
+  }
   audio = selectedFile = undefined;
   submitting = false;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f80e94 and 756df89.

⛔ Files ignored due to path filters (1)
  • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • frontend/package.json (1 hunks)
  • frontend/viewer/package.json (3 hunks)
  • frontend/viewer/src/lib/DialogsProvider.svelte (1 hunks)
  • frontend/viewer/src/lib/components/audio/AudioDialog.svelte (1 hunks)
  • frontend/viewer/src/lib/components/audio/audio-editor.svelte (1 hunks)
  • frontend/viewer/src/lib/components/audio/audio-provider.svelte (1 hunks)
  • frontend/viewer/src/lib/components/audio/ffmpeg/bundled-ffmpeg-worker.js (1 hunks)
  • frontend/viewer/src/lib/components/audio/ffmpeg/ffmpeg-api.ts (1 hunks)
  • frontend/viewer/src/lib/components/audio/ffmpeg/index.ts (1 hunks)
  • frontend/viewer/src/lib/components/audio/recorder/index.ts (1 hunks)
  • frontend/viewer/src/lib/components/audio/recorder/recorder-trigger.svelte (1 hunks)
  • frontend/viewer/src/lib/components/audio/recorder/recorder.svelte (1 hunks)
  • frontend/viewer/src/lib/components/audio/wavesurfer/waveform.svelte (1 hunks)
  • frontend/viewer/src/lib/components/audio/wavesurfer/wavesurfer-utils.ts (1 hunks)
  • frontend/viewer/src/lib/components/field-editors/audio-input.svelte (4 hunks)
  • frontend/viewer/src/lib/components/ui/button/button.svelte (2 hunks)
  • frontend/viewer/src/lib/components/ui/dialog/dialog-footer.svelte (1 hunks)
  • frontend/viewer/src/lib/components/ui/format/format-duration.ts (1 hunks)
  • frontend/viewer/src/lib/sandbox/Sandbox.svelte (3 hunks)
  • frontend/viewer/src/lib/services/dialogs-service.ts (2 hunks)
  • frontend/viewer/vite.config.ffmpeg-worker.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: myieye
PR: sillsdev/languageforge-lexbox#1817
File: frontend/viewer/src/lib/components/audio/wavesurfer/waveform.svelte:52-66
Timestamp: 2025-07-11T15:28:28.231Z
Learning: In the frontend/viewer audio components using WaveSurfer.js, errors from wavesurfer.load() and wavesurfer.loadBlob() methods are handled automatically by the system's error handling mechanism, so manual try-catch blocks are not needed.
Learnt from: myieye
PR: sillsdev/languageforge-lexbox#1817
File: frontend/viewer/src/lib/components/audio/wavesurfer/waveform.svelte:65-65
Timestamp: 2025-07-11T15:30:24.390Z
Learning: In the frontend/viewer audio components using WaveSurfer.js, the manual call to wavesurfer.play() after loading audio should be removed because WaveSurfer automatically handles playback when configured with the autoplay option, making manual play() calls redundant.
frontend/package.json (1)
Learnt from: rmunn
PR: sillsdev/languageforge-lexbox#1650
File: frontend/src/lib/i18n/locales/en.json:671-681
Timestamp: 2025-05-13T09:50:00.358Z
Learning: The Lexbox project uses the `\n\` pattern at the end of lines in JSON locale files to handle multiline strings, where the `\n` represents a newline character and the trailing backslash indicates line continuation. Standard JSON validators may flag this as invalid, but it's an established convention in the project that their tooling supports.
frontend/viewer/src/lib/DialogsProvider.svelte (1)
Learnt from: hahn-kev
PR: sillsdev/languageforge-lexbox#1710
File: frontend/viewer/src/project/browse/BrowseView.svelte:17-19
Timestamp: 2025-05-27T06:18:33.852Z
Learning: The NewEntryButton component in frontend/viewer/src/project/NewEntryButton.svelte already internally checks features.write permission and conditionally renders based on write access, so external disabled props are not needed.
frontend/viewer/src/lib/components/ui/button/button.svelte (1)
Learnt from: hahn-kev
PR: sillsdev/languageforge-lexbox#1710
File: frontend/viewer/src/project/browse/BrowseView.svelte:17-19
Timestamp: 2025-05-27T06:18:33.852Z
Learning: The NewEntryButton component in frontend/viewer/src/project/NewEntryButton.svelte already internally checks features.write permission and conditionally renders based on write access, so external disabled props are not needed.
frontend/viewer/src/lib/components/audio/wavesurfer/wavesurfer-utils.ts (2)
Learnt from: myieye
PR: sillsdev/languageforge-lexbox#1817
File: frontend/viewer/src/lib/components/audio/wavesurfer/waveform.svelte:65-65
Timestamp: 2025-07-11T15:30:24.390Z
Learning: In the frontend/viewer audio components using WaveSurfer.js, the manual call to wavesurfer.play() after loading audio should be removed because WaveSurfer automatically handles playback when configured with the autoplay option, making manual play() calls redundant.
Learnt from: myieye
PR: sillsdev/languageforge-lexbox#1817
File: frontend/viewer/src/lib/components/audio/wavesurfer/waveform.svelte:52-66
Timestamp: 2025-07-11T15:28:28.231Z
Learning: In the frontend/viewer audio components using WaveSurfer.js, errors from wavesurfer.load() and wavesurfer.loadBlob() methods are handled automatically by the system's error handling mechanism, so manual try-catch blocks are not needed.
frontend/viewer/src/lib/components/audio/audio-editor.svelte (3)
Learnt from: myieye
PR: sillsdev/languageforge-lexbox#1817
File: frontend/viewer/src/lib/components/audio/wavesurfer/waveform.svelte:52-66
Timestamp: 2025-07-11T15:28:28.231Z
Learning: In the frontend/viewer audio components using WaveSurfer.js, errors from wavesurfer.load() and wavesurfer.loadBlob() methods are handled automatically by the system's error handling mechanism, so manual try-catch blocks are not needed.
Learnt from: myieye
PR: sillsdev/languageforge-lexbox#1817
File: frontend/viewer/src/lib/components/audio/wavesurfer/waveform.svelte:65-65
Timestamp: 2025-07-11T15:30:24.390Z
Learning: In the frontend/viewer audio components using WaveSurfer.js, the manual call to wavesurfer.play() after loading audio should be removed because WaveSurfer automatically handles playback when configured with the autoplay option, making manual play() calls redundant.
Learnt from: hahn-kev
PR: sillsdev/languageforge-lexbox#1757
File: frontend/viewer/src/lib/components/field-editors/multi-select.svelte:130-136
Timestamp: 2025-06-18T05:13:00.591Z
Learning: In frontend/viewer/src/lib/components/field-editors/multi-select.svelte, the computeCommandScore function from 'bits-ui' handles empty filter strings appropriately and does not hide all options when the filter is empty, contrary to initial analysis assumptions.
frontend/viewer/src/lib/components/audio/recorder/recorder-trigger.svelte (1)
Learnt from: hahn-kev
PR: sillsdev/languageforge-lexbox#1710
File: frontend/viewer/src/project/browse/BrowseView.svelte:17-19
Timestamp: 2025-05-27T06:18:33.852Z
Learning: The NewEntryButton component in frontend/viewer/src/project/NewEntryButton.svelte already internally checks features.write permission and conditionally renders based on write access, so external disabled props are not needed.
frontend/viewer/src/lib/components/audio/ffmpeg/ffmpeg-api.ts (1)
Learnt from: myieye
PR: sillsdev/languageforge-lexbox#1817
File: frontend/viewer/src/lib/components/audio/wavesurfer/waveform.svelte:52-66
Timestamp: 2025-07-11T15:28:28.231Z
Learning: In the frontend/viewer audio components using WaveSurfer.js, errors from wavesurfer.load() and wavesurfer.loadBlob() methods are handled automatically by the system's error handling mechanism, so manual try-catch blocks are not needed.
frontend/viewer/src/lib/components/field-editors/audio-input.svelte (5)
Learnt from: myieye
PR: sillsdev/languageforge-lexbox#1817
File: frontend/viewer/src/lib/components/audio/wavesurfer/waveform.svelte:65-65
Timestamp: 2025-07-11T15:30:24.390Z
Learning: In the frontend/viewer audio components using WaveSurfer.js, the manual call to wavesurfer.play() after loading audio should be removed because WaveSurfer automatically handles playback when configured with the autoplay option, making manual play() calls redundant.
Learnt from: hahn-kev
PR: sillsdev/languageforge-lexbox#1757
File: frontend/viewer/src/lib/components/field-editors/multi-select.svelte:130-136
Timestamp: 2025-06-18T05:13:00.591Z
Learning: In frontend/viewer/src/lib/components/field-editors/multi-select.svelte, the computeCommandScore function from 'bits-ui' handles empty filter strings appropriately and does not hide all options when the filter is empty, contrary to initial analysis assumptions.
Learnt from: hahn-kev
PR: sillsdev/languageforge-lexbox#1710
File: frontend/viewer/src/project/browse/BrowseView.svelte:17-19
Timestamp: 2025-05-27T06:18:33.852Z
Learning: The NewEntryButton component in frontend/viewer/src/project/NewEntryButton.svelte already internally checks features.write permission and conditionally renders based on write access, so external disabled props are not needed.
Learnt from: myieye
PR: sillsdev/languageforge-lexbox#1802
File: frontend/viewer/src/project/NewEntryButton.svelte:36-36
Timestamp: 2025-07-04T17:00:57.368Z
Learning: In this codebase, `$props.id()` (Svelte rune) automatically returns a unique identifier per component instance, so components using it do not require an explicit `id` prop from parents.
Learnt from: myieye
PR: sillsdev/languageforge-lexbox#1817
File: frontend/viewer/src/lib/components/audio/wavesurfer/waveform.svelte:52-66
Timestamp: 2025-07-11T15:28:28.231Z
Learning: In the frontend/viewer audio components using WaveSurfer.js, errors from wavesurfer.load() and wavesurfer.loadBlob() methods are handled automatically by the system's error handling mechanism, so manual try-catch blocks are not needed.
frontend/viewer/src/lib/components/audio/AudioDialog.svelte (1)
Learnt from: myieye
PR: sillsdev/languageforge-lexbox#1817
File: frontend/viewer/src/lib/components/audio/wavesurfer/waveform.svelte:52-66
Timestamp: 2025-07-11T15:28:28.231Z
Learning: In the frontend/viewer audio components using WaveSurfer.js, errors from wavesurfer.load() and wavesurfer.loadBlob() methods are handled automatically by the system's error handling mechanism, so manual try-catch blocks are not needed.
frontend/viewer/src/lib/components/audio/recorder/recorder.svelte (1)
Learnt from: myieye
PR: sillsdev/languageforge-lexbox#1817
File: frontend/viewer/src/lib/components/audio/wavesurfer/waveform.svelte:52-66
Timestamp: 2025-07-11T15:28:28.231Z
Learning: In the frontend/viewer audio components using WaveSurfer.js, errors from wavesurfer.load() and wavesurfer.loadBlob() methods are handled automatically by the system's error handling mechanism, so manual try-catch blocks are not needed.
frontend/viewer/src/lib/components/audio/wavesurfer/waveform.svelte (2)
Learnt from: myieye
PR: sillsdev/languageforge-lexbox#1817
File: frontend/viewer/src/lib/components/audio/wavesurfer/waveform.svelte:65-65
Timestamp: 2025-07-11T15:30:24.390Z
Learning: In the frontend/viewer audio components using WaveSurfer.js, the manual call to wavesurfer.play() after loading audio should be removed because WaveSurfer automatically handles playback when configured with the autoplay option, making manual play() calls redundant.
Learnt from: myieye
PR: sillsdev/languageforge-lexbox#1817
File: frontend/viewer/src/lib/components/audio/wavesurfer/waveform.svelte:52-66
Timestamp: 2025-07-11T15:28:28.231Z
Learning: In the frontend/viewer audio components using WaveSurfer.js, errors from wavesurfer.load() and wavesurfer.loadBlob() methods are handled automatically by the system's error handling mechanism, so manual try-catch blocks are not needed.
🧬 Code Graph Analysis (2)
frontend/viewer/src/lib/components/ui/format/format-duration.ts (1)
frontend/viewer/src/lib/components/ui/format/index.ts (2)
  • normalizeDuration (10-10)
  • formatDuration (9-9)
frontend/viewer/src/lib/components/audio/ffmpeg/ffmpeg-api.ts (1)
frontend/viewer/src/lib/notifications/notifications.ts (1)
  • error (51-66)
🪛 GitHub Check: CodeQL
frontend/viewer/src/lib/components/audio/ffmpeg/bundled-ffmpeg-worker.js

[warning] 13-13: Client-side URL redirect
Untrusted URL redirection depends on a user-provided value.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Build UI / publish-ui
  • GitHub Check: Build API / publish-api
  • GitHub Check: check-and-lint
  • GitHub Check: Build FW Lite and run tests
  • GitHub Check: frontend
  • GitHub Check: frontend-component-unit-tests
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (32)
frontend/package.json (1)

122-122: Good formatting improvement.

Adding a newline at the end of the file follows standard conventions.

frontend/viewer/src/lib/DialogsProvider.svelte (2)

4-4: Good integration following established patterns.

The AudioDialog import follows the same pattern as the existing dialog imports.


9-9: Proper component integration.

Adding the AudioDialog component alongside existing dialogs maintains consistency with the dialogs provider pattern.

frontend/viewer/src/lib/components/audio/ffmpeg/index.ts (1)

1-1: Good module organization.

The re-export pattern centralizes access to FFmpeg functionality and follows standard module organization practices.

frontend/viewer/src/lib/sandbox/Sandbox.svelte (3)

36-36: Appropriate import for testing.

The FFmpeg import is correctly added for sandbox testing purposes.


129-133: Good testing implementation.

The async preload function properly awaits FFmpeg loading and provides useful logging for debugging. This is appropriate for sandbox testing.


145-145: Button integration follows sandbox patterns.

The FFmpeg load button follows the established pattern of other testing buttons in the sandbox.

frontend/viewer/src/lib/components/ui/dialog/dialog-footer.svelte (1)

16-16: LGTM! Clean flexbox enhancement for audio dialog support.

The addition of items-end and flex-wrap classes improves the dialog footer's layout flexibility to better accommodate the new audio dialog components.

frontend/viewer/src/lib/components/audio/recorder/index.ts (1)

1-10: LGTM! Clean barrel export pattern for recorder components.

The implementation follows standard TypeScript module patterns with clear component exports and meaningful aliases.

frontend/viewer/src/lib/components/ui/button/button.svelte (2)

25-31: LGTM! Improved icon button sizing with overflow protection.

The addition of max-height/width constraints prevents icon buttons from growing unexpectedly, and the new xl-icon variant provides needed size flexibility for the audio components.


82-84: LGTM! Consistent icon alignment with align-middle.

Adding align-middle to both loading and regular icons ensures consistent vertical alignment across all button states.

frontend/viewer/src/lib/components/ui/format/format-duration.ts (1)

8-24: LGTM! Well-implemented digital duration formatter.

The function correctly reuses existing utilities and provides appropriate digital formatting options. The conditional display logic for hours/minutes makes sense for a digital clock-style display.

frontend/viewer/package.json (3)

23-23: FFmpeg worker build configuration verified.

The file frontend/viewer/vite.config.ffmpeg-worker.ts exists and correctly configures both the esbuild banner/include and the library build settings (entry, format, filename, and output directory) for bundling the FFmpeg worker. No further changes needed here.


101-104: FFmpeg dependencies verified and up-to-date

I’ve checked Snyk’s vulnerability database and there are no known security issues in:

  • @ffmpeg/ffmpeg 0.12.15
  • @ffmpeg/util 0.12.2
  • @ffmpeg/core 0.12.10

These are all aligned 0.12.x releases from the ffmpeg.wasm project and are intended to work together. The CVE-2025-0518 out-of-bounds read in ffmpeg 7.1 affects the native binary and does not impact these WebAssembly builds.

Keep monitoring the ffmpeg.wasm changelog and security advisories for any future updates.


123-123: Confirm WaveSurfer.js v7.9.9 Suitability

WaveSurfer.js 7.9.9 is the latest stable release (published June 28, 2025) and includes a full TypeScript rewrite, improved performance, a typed API, and revamped plugin architecture. Before locking this version, please verify that none of the following known issues impact our use case:

• Spectrogram plugin zoom can lag (issue #4079)
• Incompatibility in certain browsers (e.g., WeChat in-app browser, issue #4069)
• Real-time waveform recording may drop data or resize erratically after ~1 minute (issue #4065)
• Videos without an audio track render no waveform (issue #4059)
• Scrollable waveform flickers in flexbox layouts (issue #4055)
• Chrome on macOS (v12–14.6) may distort audio tracks (issue #4052)
• Firefox rendering anomalies in some scenarios (issue #4051)
• CORS headers required for externally loaded audio
• Large-file decoding can exhaust browser memory—consider pre-decoded peaks
• Reliable streaming demands pre-decoded peaks and duration metadata

If any of these affect our feature set, adjust configuration or defer the upgrade. Otherwise, locking to ^7.9.9 is safe.

frontend/viewer/src/lib/services/dialogs-service.ts (2)

25-28: LGTM! Audio dialog integration follows established pattern.

The implementation correctly follows the same pattern as existing dialog methods with proper type safety and error handling.


45-48: AudioDialog integration confirmed

The AudioDialog.svelte component correctly registers itself with the service by setting:

  • File: frontend/viewer/src/lib/components/audio/AudioDialog.svelte
    dialogsService.invokeAudioDialog = getAudio;

No further changes are needed.

frontend/viewer/src/lib/components/audio/audio-editor.svelte (1)

49-50: Good use of CSS containment for waveform stability.

The CSS containment strategy to prevent WaveSurfer layout issues is well-documented and appropriately applied.

frontend/viewer/src/lib/components/audio/wavesurfer/waveform.svelte (2)

30-46: LGTM! Proper WaveSurfer lifecycle management.

The component correctly handles WaveSurfer instance creation, destruction, and recreation when container or audio changes. The event listeners for play/pause synchronization are appropriately set up.


55-67: Audio loading implementation follows best practices.

The loadAudio function correctly handles both URL strings and Blobs. Based on retrieved learnings, the error handling for wavesurfer.load() and wavesurfer.loadBlob() is handled automatically by the system, so no manual try-catch blocks are needed here.

frontend/viewer/src/lib/components/audio/recorder/recorder-trigger.svelte (4)

27-46: Good prevention of concurrent recording starts.

The implementation correctly prevents multiple concurrent recording starts using the waitingForRecorderToStart flag and proper async/await handling.


60-68: Proper mouse button filtering for pointer events.

The implementation correctly filters for left mouse button (button === 0) to avoid unintended triggers from other mouse buttons.


76-86: Good keyboard accessibility implementation.

The component properly handles both Enter and Space keys for recording control, with appropriate !event.repeat checks to prevent multiple triggers from held keys.


88-102: Dynamic styling and event handling well-implemented.

The derived props correctly handle visual state changes and event binding. The use of mergeProps provides good extensibility for consumers.

frontend/viewer/src/lib/components/audio/ffmpeg/ffmpeg-api.ts (1)

30-30: Confirm FFmpeg worker availability in production
The classWorkerURL is only set when import.meta.env.DEV is true, so production builds fall back to createFFmpeg’s default worker loader. Please verify that the Vite-built bundled-ffmpeg-worker.js is served correctly and that no CORS or 404 errors occur when loading FFmpeg in a production build.

• File: frontend/viewer/src/lib/components/audio/ffmpeg/ffmpeg-api.ts, Line 30
• Test a production build in the browser and inspect the Network/Console to ensure the worker script is fetched and instantiated without errors.

frontend/viewer/src/lib/components/audio/ffmpeg/bundled-ffmpeg-worker.js (1)

1-3: Generated file - limited review scope

This is a Vite-generated bundle file. Consider adding it to .gitignore if the build process can regenerate it, or ensure the source files are properly reviewed.

frontend/viewer/src/lib/components/audio/audio-provider.svelte (1)

34-46: Well-structured file validation

The file selection validation properly checks for audio files and handles errors gracefully by resetting the input before re-throwing.

frontend/viewer/src/lib/components/audio/recorder/recorder.svelte (2)

50-79: Well-implemented recording state management

The recording control logic properly validates state before operations and includes comprehensive error handling. The microphone preparation strategy to reduce latency is a good UX consideration.


96-103: Excellent resource cleanup implementation

The reset function properly cleans up all resources including media streams, preventing memory leaks and ensuring proper resource management.

frontend/viewer/src/lib/components/audio/AudioDialog.svelte (3)

13-15: LGTM: Clean integration with services and back handler.

The back handler integration and dialogs service setup are well implemented. The service method assignment pattern allows for clean separation of concerns.


34-36: LGTM: Proper cleanup on dialog close.

The watch function correctly handles cleanup when the dialog closes, ensuring state is reset properly.


98-118: LGTM: Clean conditional rendering and dialog structure.

The template structure is well-organized with appropriate conditional rendering. The dialog footer is correctly placed only when audio is present, and the loading state is properly handled.

Comment on lines 10 to 12
function getPrimaryColor() {
return `hsl(${getComputedStyle(document.documentElement).getPropertyValue('--primary').trim()})`;
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add SSR safety check for DOM access.

Direct access to document.documentElement will fail during server-side rendering.

Add a browser check:

 function getPrimaryColor() {
+  if (typeof document === 'undefined') return 'hsl(var(--primary))';
   return `hsl(${getComputedStyle(document.documentElement).getPropertyValue('--primary').trim()})`;
 };
📝 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
function getPrimaryColor() {
return `hsl(${getComputedStyle(document.documentElement).getPropertyValue('--primary').trim()})`;
};
function getPrimaryColor() {
if (typeof document === 'undefined') return 'hsl(var(--primary))';
return `hsl(${getComputedStyle(document.documentElement).getPropertyValue('--primary').trim()})`;
};
🤖 Prompt for AI Agents
In frontend/viewer/src/lib/components/audio/wavesurfer/wavesurfer-utils.ts
around lines 10 to 12, the function getPrimaryColor directly accesses
document.documentElement, which will cause errors during server-side rendering.
Add a check to ensure the code only runs in a browser environment by verifying
if window and document are defined before accessing document.documentElement. If
not in a browser, return a safe default value or handle accordingly to prevent
SSR failures.

Comment on lines 14 to 16
function darkenColor(color: string): string {
return `oklch(from ${color} calc(l * .50) c h)`;
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify browser compatibility for modern CSS features.

The oklch() and calc() functions with from syntax are modern CSS features that may not be supported in all browsers.

Please verify browser compatibility requirements for the project and consider adding fallbacks if needed.


🌐 Web query:

What is the browser support for CSS oklch() function and the "from" syntax in calc()?

💡 Result:

Browser support for the CSS oklch() function:

  • The oklch() color function is now widely supported in all major browsers:
    • Chrome: 111+
    • Edge: 111+
    • Firefox: 113+
    • Safari: 15.4+
    • Opera: 97+
      These versions support specifying colors using the oklch() syntax directly in CSS, and you don't need any polyfills for modern browsers[1][5][9].

Browser support for "from" syntax in calc() and oklch():

  • The "from" syntax (e.g., oklch(from var(--origin) l c h)) allows you to derive a new color by referencing and modifying the channels of another color[5].
  • This syntax is part of the CSS Color Module Level 5, which is still progressing through standardization and browser implementation[5].

Current support for "from" syntax:

  • While all major browsers support standard oklch() usage, full support for the "from" syntax in oklch() and "from" in calc() remains limited and experimental:
    • The relative color syntax (the "from" form) is available in some Chromium-based browsers behind a flag or in developer/beta builds, but is not yet enabled by default in stable releases[4][5].
    • There is no explicit mention in the search results of stable support for the "from" syntax in any browser as of mid-2025. This means you should not rely on this syntax for production without testing in your project's target browsers.

Summary Table:

Feature Chrome Edge Firefox Safari Opera
oklch() function 111+ 111+ 113+ 15.4+ 97+
oklch() "from" syntax Experimental Experimental Experimental Experimental Experimental
calc() with standard math 26+ 9+ 16+ 7+ 15+

Key Takeaways:

  • You can confidently use oklch() and calc() in all modern browsers[1][9][10].
  • The "from" color syntax in oklch/calc is experimental; check browser release notes and feature flags if you want to experiment, but do not depend on it for cross-browser code yet[4][5].

Citations:


Add a fallback for the experimental “from” color syntax

Based on browser support data, the plain oklch() function is well supported in all modern browsers (Chrome 111+, Edge 111+, Firefox 113+, Safari 15.4+, Opera 97+), but the relative‐color “from” syntax is still experimental and not enabled by default in stable releases. Relying on it without a fallback may cause styling breakage in unsupported browsers.

Please update the darkenColor util to detect support and provide an alternative:

• File: frontend/viewer/src/lib/components/audio/wavesurfer/wavesurfer-utils.ts (lines 14–16)
– Wrap the “from” call in a CSS.supports('color', …) check
– On unsupported browsers, compute a fallback (e.g., manual LCH or HSL darkening via a helper)

Example diff:

 function darkenColor(color: string): string {
-  return `oklch(from ${color} calc(l * .50) c h)`;
+  const fromSyntax = `oklch(from ${color} calc(l * .50) c h)`;
+  if (CSS.supports('color', fromSyntax)) {
+    return fromSyntax;
+  }
+  // fallback: manually reduce lightness by 50%
+  return fallbackDarken(color, 0.5);
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In frontend/viewer/src/lib/components/audio/wavesurfer/wavesurfer-utils.ts
around lines 14 to 16, the darkenColor function uses the experimental "from"
syntax in the oklch color function which lacks broad browser support. To fix
this, add a CSS.supports('color', ...) check to detect if the "from" syntax is
supported. If supported, use the current implementation; otherwise, provide a
fallback by manually darkening the color using a helper function that adjusts
the color in LCH or HSL space without the "from" syntax.

Comment on lines +25 to +28
void loadFFmpeg().then(async (ffmpeg) => {
if (!audio) return;
audio = await convertToWav(ffmpeg, audio);
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for FFmpeg loading and conversion.

The FFmpeg loading and audio conversion lacks error handling, which could lead to unhandled promise rejections if FFmpeg fails to load or conversion fails.

-  void loadFFmpeg().then(async (ffmpeg) => {
-    if (!audio) return;
-    audio = await convertToWav(ffmpeg, audio);
-  });
+  let conversionError = $state<string | null>(null);
+  
+  void loadFFmpeg().then(async (ffmpeg) => {
+    try {
+      if (!audio) return;
+      audio = await convertToWav(ffmpeg, audio);
+      conversionError = null;
+    } catch (error) {
+      console.error('Failed to convert audio:', error);
+      conversionError = error instanceof Error ? error.message : 'Audio conversion failed';
+    }
+  }).catch(error => {
+    console.error('Failed to load FFmpeg:', error);
+    conversionError = 'Failed to load audio processor';
+  });

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In frontend/viewer/src/lib/components/audio/audio-editor.svelte around lines 25
to 28, the code loading FFmpeg and converting audio lacks error handling,
risking unhandled promise rejections. Wrap the async operations in a try-catch
block inside the then callback to catch and handle any errors during FFmpeg
loading or audio conversion, and log or handle the errors appropriately.

Comment on lines 42 to 44
// 400 ms to opt out of walkie-talkie mode i.e. prevent pausing on release/up
releaseToStop = false;
walkedTalkieTimeout = setTimeout(() => releaseToStop = true, 400);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider clearing timeout on component cleanup.

The walkedTalkieTimeout should be cleared when the component is destroyed to prevent potential memory leaks.

Add cleanup in an onDestroy lifecycle:

+  import { onDestroy } from 'svelte';
+
+  onDestroy(() => {
+    clearTimeout(walkedTalkieTimeout);
+  });
📝 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
// 400 ms to opt out of walkie-talkie mode i.e. prevent pausing on release/up
releaseToStop = false;
walkedTalkieTimeout = setTimeout(() => releaseToStop = true, 400);
import { onDestroy } from 'svelte';
// 400 ms to opt out of walkie-talkie mode i.e. prevent pausing on release/up
releaseToStop = false;
walkedTalkieTimeout = setTimeout(() => releaseToStop = true, 400);
onDestroy(() => {
clearTimeout(walkedTalkieTimeout);
});
🤖 Prompt for AI Agents
In frontend/viewer/src/lib/components/audio/recorder/recorder-trigger.svelte
around lines 42 to 44, the timeout stored in walkedTalkieTimeout is not cleared
on component destruction, which can cause memory leaks. Add an onDestroy
lifecycle function to clear the walkedTalkieTimeout using clearTimeout to ensure
the timeout is properly cleaned up when the component is destroyed.

]);

// Read output file
const data = await ffmpeg.readFile(outputName) as Uint8Array;;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove duplicate semicolon

-  const data = await ffmpeg.readFile(outputName) as Uint8Array;;
+  const data = await ffmpeg.readFile(outputName) as Uint8Array;
📝 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
const data = await ffmpeg.readFile(outputName) as Uint8Array;;
const data = await ffmpeg.readFile(outputName) as Uint8Array;
🤖 Prompt for AI Agents
In frontend/viewer/src/lib/components/audio/ffmpeg/ffmpeg-api.ts at line 54,
there is a duplicate semicolon at the end of the statement. Remove the extra
semicolon so that the line ends with a single semicolon after the Uint8Array
type assertion.

Comment on lines 182 to 190
{#if $isDev}
<Button variant="secondary" icon="i-mdi-microphone-plus" size="sm" iconProps={{class: 'size-5'}} onclick={onGetAudioClick}>
{$t`Add audio`}
</Button>
{:else}
<div class="text-muted-foreground p-1">
{$t`No audio`}
</div>
{/if}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Production users cannot add audio

The "Add audio" button is only visible in development mode. Production users will only see "No audio" with no way to add audio content.

Is this intentional for the current draft, or should the button be available in production once the feature is complete?

🤖 Prompt for AI Agents
In frontend/viewer/src/lib/components/field-editors/audio-input.svelte around
lines 182 to 190, the "Add audio" button is conditionally rendered only for
development mode users, hiding it from production users who see only "No audio."
Clarify whether this is intentional for the current draft; if not, remove or
adjust the condition so the "Add audio" button is available to production users
once the feature is complete, ensuring all users can add audio content as
intended.


let recorderContainerElem = $state<HTMLElement>();
let recording = $state(false);
let walkieTalkieMode = $state<boolean>(null!);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid TypeScript non-null assertion with null

Using null! is a TypeScript anti-pattern that bypasses type safety.

Initialize with a proper boolean value:

-  let walkieTalkieMode = $state<boolean>(null!);
+  let walkieTalkieMode = $state<boolean>(false);
🤖 Prompt for AI Agents
In frontend/viewer/src/lib/components/audio/audio-provider.svelte at line 24,
the variable walkieTalkieMode is initialized using the TypeScript non-null
assertion with null (null!), which bypasses type safety. Replace the
initialization with a proper boolean value such as false or true to ensure type
safety and avoid using the non-null assertion operator.

import AudioProvider from './audio-provider.svelte';
import AudioEditor from './audio-editor.svelte';

let open = $state(true);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix initial dialog state to prevent unintended display.

The dialog opens immediately on component mount due to open being initialized to true. This could cause the dialog to appear unexpectedly when the component is first rendered.

-let open = $state(true);
+let open = $state(false);
📝 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
let open = $state(true);
let open = $state(false);
🤖 Prompt for AI Agents
In frontend/viewer/src/lib/components/audio/AudioDialog.svelte at line 12, the
variable `open` is initialized to `true`, causing the dialog to open immediately
on component mount. Change the initial value of `open` to `false` to prevent the
dialog from displaying unexpectedly when the component first renders.

Comment on lines 92 to 95
async function processAudio(blob: Blob): Promise<Blob> {
await delay(1000); // Simulate processing delay
return blob;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Implement actual audio processing or remove placeholder.

The processAudio function currently only simulates processing with a delay but doesn't perform any actual audio processing. Consider implementing format conversion, compression, or validation logic.

Based on the PR objectives mentioning FFmpeg integration, consider:

-async function processAudio(blob: Blob): Promise<Blob> {
-  await delay(1000); // Simulate processing delay
-  return blob;
-}
+async function processAudio(blob: Blob): Promise<Blob> {
+  // TODO: Integrate with FFmpeg for audio processing
+  // Example: Convert to standardized format, apply compression, etc.
+  try {
+    // Add actual audio processing logic here
+    return blob; // Return processed blob
+  } catch (error) {
+    console.error('Audio processing failed:', error);
+    throw new Error('Failed to process audio');
+  }
+}
📝 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
async function processAudio(blob: Blob): Promise<Blob> {
await delay(1000); // Simulate processing delay
return blob;
}
async function processAudio(blob: Blob): Promise<Blob> {
// TODO: Integrate with FFmpeg for audio processing
// Example: Convert to standardized format, apply compression, etc.
try {
// Add actual audio processing logic here
return blob; // Return processed blob
} catch (error) {
console.error('Audio processing failed:', error);
throw new Error('Failed to process audio');
}
}
🤖 Prompt for AI Agents
In frontend/viewer/src/lib/components/audio/AudioDialog.svelte around lines 92
to 95, the processAudio function currently only simulates a delay without
performing any real audio processing. To fix this, replace the placeholder delay
with actual audio processing logic such as format conversion, compression, or
validation, potentially using FFmpeg or another audio processing library as
indicated by the PR objectives.

Comment on lines 54 to 66
async function submitAudio() {
if (!audio) throw new Error('No audio to upload');
if (!requester) throw new Error('No requester');

submitting = true;
try {
const audioId = await uploadAudio();
requester.resolve(audioId);
close();
} finally {
submitting = false;
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling in submitAudio.

The function handles the finally block for loading state but doesn't handle errors properly. Consider adding user-friendly error handling.

 async function submitAudio() {
   if (!audio) throw new Error('No audio to upload');
   if (!requester) throw new Error('No requester');

   submitting = true;
   try {
     const audioId = await uploadAudio();
     requester.resolve(audioId);
     close();
+  } catch (error) {
+    console.error('Failed to submit audio:', error);
+    // TODO: Show user-friendly error message
+    // Consider adding toast notification or error state
   } finally {
     submitting = false;
   }
 }
📝 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
async function submitAudio() {
if (!audio) throw new Error('No audio to upload');
if (!requester) throw new Error('No requester');
submitting = true;
try {
const audioId = await uploadAudio();
requester.resolve(audioId);
close();
} finally {
submitting = false;
}
}
async function submitAudio() {
if (!audio) throw new Error('No audio to upload');
if (!requester) throw new Error('No requester');
submitting = true;
try {
const audioId = await uploadAudio();
requester.resolve(audioId);
close();
} catch (error) {
console.error('Failed to submit audio:', error);
// TODO: Show user-friendly error message
// Consider adding toast notification or error state
} finally {
submitting = false;
}
}
🤖 Prompt for AI Agents
In frontend/viewer/src/lib/components/audio/AudioDialog.svelte around lines 54
to 66, the submitAudio function lacks proper error handling for the uploadAudio
call. Enhance the function by adding a try-catch block around the await
uploadAudio() call to catch any errors, then display a user-friendly error
message or notification instead of letting the error propagate silently. Ensure
the submitting state is still reset in the finally block.

Copy link

argos-ci bot commented Jul 14, 2025

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ⚠️ Changes detected (Review) 4 changed, 4 removed Jul 14, 2025, 11:16 PM

@hahn-kev hahn-kev changed the base branch from develop to add-audio-ui July 15, 2025 03:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
💻 FW Lite issues related to the fw lite application, not miniLcm or crdt related 📦 Lexbox issues related to any server side code, fw-headless included
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant