Skip to content

chore(admin): fix unsafe type assertions in Custom Sounds#38740

Open
NAME-ASHWANIYADAV wants to merge 8 commits intoRocketChat:developfrom
NAME-ASHWANIYADAV:chore/fix-unsafe-type-assertion-custom-sounds
Open

chore(admin): fix unsafe type assertions in Custom Sounds#38740
NAME-ASHWANIYADAV wants to merge 8 commits intoRocketChat:developfrom
NAME-ASHWANIYADAV:chore/fix-unsafe-type-assertion-custom-sounds

Conversation

@NAME-ASHWANIYADAV
Copy link
Contributor

@NAME-ASHWANIYADAV NAME-ASHWANIYADAV commented Feb 17, 2026

Proposed changes

This PR addresses unsafe type assertions (as any, FIXME) and improves type safety in the Custom Sounds administration components (AddCustomSound.tsx, EditSound.tsx, and lib.ts).

Key Fixes:

  1. AddCustomSound.tsx:

    • Updated component state sound to use File type instead of { name: string }, matching the useSingleFileInput hook return type.
    • Fixed saveAction to accept soundFile: File instead of any.
    • Removed unnecessary type casting on validate() return.
  2. EditSound.tsx:

    • Updated saveAction to accept union type EditSoundProps['data'] | File instead of any.
    • Implemented proper type narrowing using instanceof File to safely access properties like type and extension.
    • Fixed a bug where previousSound state was initialized with an empty object {} (invalid type) instead of data.
    • Replaced hardcoded 'none' string with t('None') for better internationalization support.
  3. lib.ts:

    • Updated ICustomSoundFile type definition to make type optional, supporting both file uploads (with type) and existing data (without type), as createSoundData function only requires name.

Issue(s)

#38739

How to test or reproduce

  1. Navigate to Administration > Custom Sounds.
  2. Add a new sound: Use the "Add custom sound" button, upload an mp3 file, set a name, and save. Verify success.
  3. Edit a sound: Click on an existing sound.
    • Change the name and save. Verify update.
    • Upload a new sound file replacing the old one and save. Verify update.
    • Delete the sound. Verify deletion.

Screenshots

No visual changes, purely refactoring for type safety.

Types of changes

  • Chore (maintainability, coding standards)
  • Bugfix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Documentation
  • Refactor (code change that neither fixes a bug nor adds a feature)

Summary by CodeRabbit

  • Bug Fixes

    • Improved file validation to tolerate missing file-type metadata.
    • Added an error notification when saving without selecting a sound file.
    • Fixed upload/save flow to correctly handle newly selected files, await async saves, and avoid duplicate processing.
  • Improvements

    • UI now shows a localized "None" label when no sound is present.
    • Upload progress and success messages shown when replacing sounds.

@NAME-ASHWANIYADAV NAME-ASHWANIYADAV requested a review from a team as a code owner February 17, 2026 06:51
@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Feb 17, 2026

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label
  • This PR is missing the required milestone or project

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Feb 17, 2026

⚠️ No Changeset found

Latest commit: 49d2504

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Sound handling now treats selected sounds as File objects when applicable; save/edit flows detect new File instances to branch into file-read/upload vs existing-data paths. Validation accepts File or existing data and tolerates missing MIME types. A guard dispatches an error if no file is selected.

Changes

Cohort / File(s) Summary
Add Custom Sound
apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx
Sound state changed from an object to File; saveAction parameter typed as File; removed an explicit validation cast; added a guard that dispatches an error toast (Sound_File_mp3) when no file is selected; dependencies updated to include close.
Edit Custom Sound
apps/meteor/client/views/admin/customSounds/EditSound.tsx
previousSound now derives directly from data; saveAction accepts existing data or File, detects isNewFile via instanceof File; validation call updated for file vs existing data; new-file branch reads via FileReader, uploads via uploadCustomSound, shows upload toast, closes early and returns; UI label displays t('None'); dependency array adjusted.
Type Helpers
apps/meteor/client/views/admin/customSounds/lib.ts
Made ICustomSoundFile.type optional and updated validation to skip FileType check when soundFile.type is undefined.
Release Metadata
.changeset/type-safe-custom-sounds.md
Adds a patch changeset noting a fix for unsafe type assertions in custom sounds (release metadata only).

Sequence Diagram(s)

sequenceDiagram
  rect rgba(200,200,255,0.5)
    participant User
  end
  rect rgba(200,255,200,0.5)
    participant UI as Add/Edit Component
    participant Validator
    participant FileReader
    participant Uploader
    participant Toasts
  end

  User->>UI: Select or edit sound (File or existing data)
  UI->>Validator: validate(soundData, soundFile)
  alt soundFile is File (new upload)
    UI->>FileReader: read file
    FileReader-->>UI: file data
    UI->>Uploader: uploadCustomSound(file data)
    Uploader-->>UI: upload result
    UI->>Toasts: show File_uploaded toast
    UI-->>User: close and return early
  else existing sound (no new file)
    UI->>UI: run saveAction with existing data
    UI->>Toasts: show success save toast
    UI-->>User: finish
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I found a byte beneath a log,

I sniffed the type and gave a jog.
If it's a file I'll read and send,
if not, I tweak the one I mend. 🎵

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore(admin): fix unsafe type assertions in Custom Sounds' accurately describes the main objective of the PR, which is to remove unsafe type assertions and improve type safety across multiple custom sounds administration components.

✏️ 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.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 3 files

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
apps/meteor/client/views/admin/customSounds/EditSound.tsx (1)

98-101: ⚠️ Potential issue | 🟠 Major

Missing await on saveActiononChange() fires before save completes.

saveAction is async, but line 99 doesn't await it. This means onChange() (which likely refreshes the sound list) runs immediately, potentially before the insert/upload finishes. Compare with AddCustomSound.tsx line 75 which correctly uses const result = await saveAction(...).

Proposed fix
 	const handleSave = useCallback(async () => {
-		saveAction(sound);
+		await saveAction(sound);
 		onChange();
 	}, [saveAction, sound, onChange]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/client/views/admin/customSounds/EditSound.tsx` around lines 98 -
101, The handleSave callback currently calls saveAction(sound) without awaiting
it, causing onChange() to run before the async save completes; update handleSave
(the useCallback that calls saveAction and onChange) to await the result of
saveAction (e.g., const result = await saveAction(sound)) before calling
onChange(), and optionally handle errors (try/catch) or use the result if needed
to ensure onChange runs only after the save finishes.
apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx (1)

73-84: ⚠️ Potential issue | 🟡 Minor

sound may be undefined but saveAction expects File.

sound is typed as File | undefined (line 22), but saveAction on line 34 declares soundFile: File. The call on line 75 (saveAction(name, sound)) would be a TypeScript error under strict null checks. Consider either guarding before the call or making the parameter optional.

Option: guard before calling saveAction
 	const handleSave = useCallback(async () => {
 		try {
+			if (!sound) {
+				dispatchToastMessage({ type: 'error', message: t('Required_field', { field: t('Sound File') }) });
+				return;
+			}
 			const result = await saveAction(name, sound);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx` around lines
73 - 84, The handleSave callback calls saveAction(name, sound) while sound is
typed File | undefined but saveAction expects a File; update to either (A) guard
before calling in handleSave: if (!sound) { dispatchToastMessage(...); return; }
then call saveAction(name, sound) so you only pass a File, or (B) change
saveAction's signature (the function declared as saveAction) to accept
soundFile?: File or soundFile: File | undefined and handle the undefined case
inside saveAction; pick one approach and update references to saveAction and the
handleSave logic accordingly.
🧹 Nitpick comments (2)
apps/meteor/client/views/admin/customSounds/lib.ts (1)

21-27: soundFile.type used without guarding for undefined.

Since type is now optional in ICustomSoundFile, soundFile.type could be undefined. While RegExp.test(undefined) coerces to "undefined" and correctly fails the check (pushing 'FileType'), this is implicit and fragile. Consider adding an explicit guard for clarity.

That said, current callers only pass File instances (which always have .type) or undefined, so this is safe in practice.

Optional: add explicit guard
 	if (soundFile) {
 		if (!soundData.previousSound || soundData.previousSound !== soundFile) {
-			if (!/audio\/mp3/.test(soundFile.type) && !/audio\/mpeg/.test(soundFile.type) && !/audio\/x-mpeg/.test(soundFile.type)) {
+			if (!soundFile.type || (!/audio\/mp3/.test(soundFile.type) && !/audio\/mpeg/.test(soundFile.type) && !/audio\/x-mpeg/.test(soundFile.type))) {
 				errors.push('FileType');
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/client/views/admin/customSounds/lib.ts` around lines 21 - 27, The
validation uses soundFile.type without an explicit undefined check; update the
sound file validation in the block that references soundFile, soundData, and
ICustomSoundFile so it first verifies soundFile.type exists (e.g., if
(!soundFile.type) { errors.push('FileType') }) before running the regex tests,
and only run the /audio\/.../.test(...) checks when soundFile.type is a
non-empty string to make the intent explicit and robust.
apps/meteor/client/views/admin/customSounds/EditSound.tsx (1)

54-93: Validation errors are dispatched even on success path.

Lines 88–93 run unconditionally after the if (validation.length === 0) block. When validation passes, the forEach is a no-op (empty array), so there's no user-visible bug—but it reads as though error handling was intended to be in an else branch. This is a pre-existing issue, not introduced by this PR.

Cleaner structure with early return
 		async (sound: EditSoundProps['data'] | File) => {
 			const isNewFile = sound instanceof File;
 			const extension = isNewFile ? undefined : sound.extension;
 			const soundData = createSoundData(sound, name, { previousName, previousSound, _id, extension: extension ?? '' });
 			const validation = validate(soundData, isNewFile ? sound : undefined);
+
+			if (validation.length > 0) {
+				validation.forEach((invalidFieldName) =>
+					dispatchToastMessage({
+						type: 'error',
+						message: t('Required_field', { field: t(invalidFieldName) }),
+					}),
+				);
+				return;
+			}
+
-			if (validation.length === 0) {
 				let soundId: string;
 				try {
 					soundId = await insertOrUpdateSound(soundData);
 				...
-			}
-
-			validation.forEach((invalidFieldName) =>
-				dispatchToastMessage({
-					type: 'error',
-					message: t('Required_field', { field: t(invalidFieldName) }),
-				}),
-			);
 		},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/client/views/admin/customSounds/EditSound.tsx` around lines 54 -
93, The validation error dispatch runs after the successful-save path in
saveAction which makes the control flow confusing; change saveAction so that
when validation.length === 0 you either return early after finishing the
success/upload logic (including after reader.onloadend branch completes/queues
work) or place the validation.forEach(...) inside an else block; locate the
saveAction callback and the validation.forEach call and implement the early
return or else branch so error messages are only dispatched when validation
failed.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 508b4a1 and d715918.

📒 Files selected for processing (3)
  • apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx
  • apps/meteor/client/views/admin/customSounds/EditSound.tsx
  • apps/meteor/client/views/admin/customSounds/lib.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx
  • apps/meteor/client/views/admin/customSounds/EditSound.tsx
  • apps/meteor/client/views/admin/customSounds/lib.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 36717
File: packages/ui-voip/src/providers/useCallSounds.ts:6-21
Timestamp: 2025-09-15T21:34:39.812Z
Learning: The voipSounds methods (playDialer, playRinger, playCallEnded) from useCustomSound return proper offCallbackHandler cleanup functions, not void as some type definitions might suggest.
📚 Learning: 2026-01-17T01:51:47.764Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38219
File: packages/core-typings/src/cloud/Announcement.ts:5-6
Timestamp: 2026-01-17T01:51:47.764Z
Learning: In packages/core-typings/src/cloud/Announcement.ts, the AnnouncementSchema.createdBy field intentionally overrides IBannerSchema.createdBy (object with _id and optional username) with a string enum ['cloud', 'system'] to match existing runtime behavior. This is documented as technical debt with a FIXME comment at apps/meteor/app/cloud/server/functions/syncWorkspace/handleCommsSync.ts:53 and should not be flagged as an error until the runtime behavior is corrected.

Applied to files:

  • apps/meteor/client/views/admin/customSounds/lib.ts
📚 Learning: 2026-02-10T16:32:42.586Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38528
File: apps/meteor/client/startup/roles.ts:14-14
Timestamp: 2026-02-10T16:32:42.586Z
Learning: In Rocket.Chat's Meteor client code, DDP streams use EJSON and Date fields arrive as Date objects; do not manually construct new Date() in stream handlers (for example, in sdk.stream()). Only REST API responses return plain JSON where dates are strings, so implement explicit conversion there if needed. Apply this guidance to all TypeScript files under apps/meteor/client to ensure consistent date handling in DDP streams and REST responses.

Applied to files:

  • apps/meteor/client/views/admin/customSounds/lib.ts
🧬 Code graph analysis (2)
apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx (1)
apps/meteor/client/views/admin/customSounds/lib.ts (2)
  • createSoundData (32-61)
  • validate (10-30)
apps/meteor/client/views/admin/customSounds/EditSound.tsx (1)
apps/meteor/client/views/admin/customSounds/lib.ts (2)
  • createSoundData (32-61)
  • validate (10-30)
⏰ 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). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: CodeQL-Build
🔇 Additional comments (4)
apps/meteor/client/views/admin/customSounds/lib.ts (1)

3-7: LGTM on making type optional.

This aligns with the updated caller pattern where File objects (which always carry .type) are passed for validation, while existing sound data objects (without .type) are not passed to validate at all.

apps/meteor/client/views/admin/customSounds/EditSound.tsx (2)

27-27: Good improvements: proper previousSound initialization and union type for sound state.

Using data directly instead of an empty object fallback, and the EditSoundProps['data'] | File union type, are clean improvements that enable proper type narrowing downstream.

Also applies to: 30-37


146-146: Good i18n fix — t('None') instead of hardcoded 'none'.

apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx (1)

22-22: Good type improvements — File state and removal of explicit cast on validate.

Using File directly aligns with what useSingleFileInput provides, and removing the cast on validate's return lets TypeScript enforce correctness naturally.

Also applies to: 33-36

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx`:
- Around line 73-84: The handleSave callback calls saveAction(name, sound) while
sound is typed File | undefined but saveAction expects a File; update to either
(A) guard before calling in handleSave: if (!sound) { dispatchToastMessage(...);
return; } then call saveAction(name, sound) so you only pass a File, or (B)
change saveAction's signature (the function declared as saveAction) to accept
soundFile?: File or soundFile: File | undefined and handle the undefined case
inside saveAction; pick one approach and update references to saveAction and the
handleSave logic accordingly.

In `@apps/meteor/client/views/admin/customSounds/EditSound.tsx`:
- Around line 98-101: The handleSave callback currently calls saveAction(sound)
without awaiting it, causing onChange() to run before the async save completes;
update handleSave (the useCallback that calls saveAction and onChange) to await
the result of saveAction (e.g., const result = await saveAction(sound)) before
calling onChange(), and optionally handle errors (try/catch) or use the result
if needed to ensure onChange runs only after the save finishes.

---

Nitpick comments:
In `@apps/meteor/client/views/admin/customSounds/EditSound.tsx`:
- Around line 54-93: The validation error dispatch runs after the
successful-save path in saveAction which makes the control flow confusing;
change saveAction so that when validation.length === 0 you either return early
after finishing the success/upload logic (including after reader.onloadend
branch completes/queues work) or place the validation.forEach(...) inside an
else block; locate the saveAction callback and the validation.forEach call and
implement the early return or else branch so error messages are only dispatched
when validation failed.

In `@apps/meteor/client/views/admin/customSounds/lib.ts`:
- Around line 21-27: The validation uses soundFile.type without an explicit
undefined check; update the sound file validation in the block that references
soundFile, soundData, and ICustomSoundFile so it first verifies soundFile.type
exists (e.g., if (!soundFile.type) { errors.push('FileType') }) before running
the regex tests, and only run the /audio\/.../.test(...) checks when
soundFile.type is a non-empty string to make the intent explicit and robust.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (3)
apps/meteor/client/views/admin/customSounds/EditSound.tsx (2)

100-103: ⚠️ Potential issue | 🟠 Major

onChange() is called even when saveAction encounters validation errors.

saveAction dispatches error toasts on validation failure (Lines 90-95) but neither throws nor returns a distinguishing value. Consequently, handleSave always calls onChange() on Line 102, triggering a data refresh even when nothing was saved.

Consider having saveAction return a boolean or throw on failure so handleSave can skip the onChange() call:

Proposed fix (return boolean from saveAction)

Inside saveAction, return true on success and false on validation failure:

 			close?.();
-			return;
+			return true;
 		}
 
 		validation.forEach((invalidFieldName) =>
@@ ...
 		);
+		return false;
 	},

Then in handleSave:

 const handleSave = useCallback(async () => {
-	await saveAction(sound);
-	onChange();
+	const saved = await saveAction(sound);
+	if (saved) {
+		onChange();
+	}
 }, [saveAction, sound, onChange]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/client/views/admin/customSounds/EditSound.tsx` around lines 100 -
103, handleSave currently always calls onChange() after awaiting
saveAction(sound), causing refreshes even when saveAction fails validation;
update saveAction to return a success boolean (true on success, false on
validation failure) or throw on error, and then change handleSave to await the
call and only call onChange() when saveAction succeeded (i.e., if (await
saveAction(sound)) onChange()). Reference the saveAction function and the
handleSave callback so you can update both the action to return/throw and the
handleSave logic to conditionally invoke onChange().

86-97: ⚠️ Potential issue | 🟡 Minor

close is used inside saveAction but missing from the dependency array.

close?.() is called on Line 86 but close is not included in the dependency array on Line 97. Same issue as in AddCustomSound.tsx.

Proposed fix
-		[_id, dispatchToastMessage, insertOrUpdateSound, name, previousName, previousSound, t, uploadCustomSound],
+		[_id, close, dispatchToastMessage, insertOrUpdateSound, name, previousName, previousSound, t, uploadCustomSound],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/client/views/admin/customSounds/EditSound.tsx` around lines 86 -
97, The saveAction callback uses close (calls close?.()) but close is not listed
in its dependency array; update the dependency array for the saveAction hook to
include close so the callback captures the current close reference. Locate the
saveAction definition and its dependency array (the array containing _id,
dispatchToastMessage, insertOrUpdateSound, name, previousName, previousSound, t,
uploadCustomSound) and add close to that array; mirror the same fix applied in
AddCustomSound where close was added to the dependencies.
apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx (1)

65-71: ⚠️ Potential issue | 🟡 Minor

close is used inside saveAction but missing from the dependency array.

close is called on Line 65 but is not listed in the dependency array on Line 71. This is a stale closure risk — if close ever changes identity across renders, the callback will reference the old value. While close likely doesn't change often, this is still a correctness issue for exhaustive deps.

Proposed fix
-		[dispatchToastMessage, insertOrUpdateSound, t, uploadCustomSound],
+		[close, dispatchToastMessage, insertOrUpdateSound, t, uploadCustomSound],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx` around lines
65 - 71, The saveAction callback references close but it is missing from its
dependency array (currently [dispatchToastMessage, insertOrUpdateSound, t,
uploadCustomSound]), which creates a stale-closure risk; update the useCallback
dependency list for saveAction to include close (or otherwise ensure close is
stable) so the latest close is captured when saveAction is recreated—look for
the saveAction function where close() is called inside the try block and add
close to the dependency array.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 153ea0a and 29b1dcb.

📒 Files selected for processing (2)
  • apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx
  • apps/meteor/client/views/admin/customSounds/EditSound.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • apps/meteor/client/views/admin/customSounds/EditSound.tsx
  • apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx
🧬 Code graph analysis (2)
apps/meteor/client/views/admin/customSounds/EditSound.tsx (1)
apps/meteor/client/views/admin/customSounds/lib.ts (2)
  • createSoundData (32-61)
  • validate (10-30)
apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx (1)
apps/meteor/client/views/admin/customSounds/lib.ts (2)
  • createSoundData (32-61)
  • validate (10-30)
⏰ 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). (4)
  • GitHub Check: 🔎 Code Check / TypeScript
  • GitHub Check: 🔎 Code Check / Code Lint
  • GitHub Check: 🔨 Test Unit / Unit Tests
  • GitHub Check: 📦 Meteor Build (coverage)
🔇 Additional comments (4)
apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx (2)

22-22: Good improvement: File type instead of untyped object.

Using useState<File>() properly reflects the useSingleFileInput callback signature and eliminates the previous unsafe typing.


74-77: Good guard for missing sound file.

The early return with an error toast when no file is selected prevents a runtime error that would otherwise occur when passing undefined to saveAction.

apps/meteor/client/views/admin/customSounds/EditSound.tsx (2)

55-59: Good use of instanceof File for type narrowing.

The instanceof File check cleanly discriminates between the two union branches, allowing safe property access without type assertions. The conditional validation call (Line 59) correctly skips file-type validation when no new file is uploaded.


148-148: Good i18n improvement — replacing hardcoded 'none' with t('None').

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx`:
- Around line 65-71: The saveAction callback references close but it is missing
from its dependency array (currently [dispatchToastMessage, insertOrUpdateSound,
t, uploadCustomSound]), which creates a stale-closure risk; update the
useCallback dependency list for saveAction to include close (or otherwise ensure
close is stable) so the latest close is captured when saveAction is
recreated—look for the saveAction function where close() is called inside the
try block and add close to the dependency array.

In `@apps/meteor/client/views/admin/customSounds/EditSound.tsx`:
- Around line 100-103: handleSave currently always calls onChange() after
awaiting saveAction(sound), causing refreshes even when saveAction fails
validation; update saveAction to return a success boolean (true on success,
false on validation failure) or throw on error, and then change handleSave to
await the call and only call onChange() when saveAction succeeded (i.e., if
(await saveAction(sound)) onChange()). Reference the saveAction function and the
handleSave callback so you can update both the action to return/throw and the
handleSave logic to conditionally invoke onChange().
- Around line 86-97: The saveAction callback uses close (calls close?.()) but
close is not listed in its dependency array; update the dependency array for the
saveAction hook to include close so the callback captures the current close
reference. Locate the saveAction definition and its dependency array (the array
containing _id, dispatchToastMessage, insertOrUpdateSound, name, previousName,
previousSound, t, uploadCustomSound) and add close to that array; mirror the
same fix applied in AddCustomSound where close was added to the dependencies.

@codecov
Copy link

codecov bot commented Feb 17, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.49%. Comparing base (508b4a1) to head (49d2504).

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #38740      +/-   ##
===========================================
- Coverage    70.51%   70.49%   -0.02%     
===========================================
  Files         3176     3176              
  Lines       111139   111139              
  Branches     20050    20038      -12     
===========================================
- Hits         78367    78348      -19     
- Misses       30721    30748      +27     
+ Partials      2051     2043       -8     
Flag Coverage Δ
e2e 60.43% <ø> (-0.04%) ⬇️
e2e-api 47.78% <ø> (+0.01%) ⬆️
unit 71.48% <ø> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
apps/meteor/client/views/admin/customSounds/EditSound.tsx (1)

100-103: ⚠️ Potential issue | 🟠 Major

onChange() fires after close(), risking a call on an unmounted component.

saveAction calls close() internally (line 86) before returning. Then handleSave unconditionally calls onChange() on line 102 — after the contextual bar is already closed and the component may be unmounted. Additionally, onChange() is called even when validation fails (saveAction shows toasts but doesn't throw).

Consider moving onChange() inside saveAction before the close() call (similar to how handleDeleteButtonClick handles it), or having saveAction return a success indicator.

Proposed fix: move onChange into saveAction
 	const saveAction = useCallback(
 		async (sound: EditSoundProps['data'] | File) => {
 			const isNewFile = sound instanceof File;
 			const extension = isNewFile ? undefined : sound.extension;
 			const soundData = createSoundData(sound, name, { previousName, previousSound, _id, extension: extension ?? '' });
 			const validation = validate(soundData, isNewFile ? sound : undefined);
 			if (validation.length === 0) {
 				let soundId: string;
 				try {
 					soundId = await insertOrUpdateSound(soundData);
 				} catch (error) {
 					dispatchToastMessage({ type: 'error', message: error });
 					return;
 				}

 				soundData._id = soundId;
 				soundData.random = Math.round(Math.random() * 1000);

 				if (isNewFile) {
 					dispatchToastMessage({ type: 'success', message: t('Uploading_file') });

 					const reader = new FileReader();
 					reader.readAsBinaryString(sound);
 					reader.onloadend = (): void => {
 						try {
 							uploadCustomSound(reader.result as string, sound.type, { ...soundData, _id: soundId });
 							return dispatchToastMessage({ type: 'success', message: t('File_uploaded') });
 						} catch (error) {
 							dispatchToastMessage({ type: 'error', message: error });
 						}
 					};
 				}
+				onChange();
 				close?.();
 				return;
 			}

 			validation.forEach((invalidFieldName) =>
 				dispatchToastMessage({
 					type: 'error',
 					message: t('Required_field', { field: t(invalidFieldName) }),
 				}),
 			);
 		},
-		[_id, close, dispatchToastMessage, insertOrUpdateSound, name, previousName, previousSound, t, uploadCustomSound],
+		[_id, close, dispatchToastMessage, insertOrUpdateSound, name, onChange, previousName, previousSound, t, uploadCustomSound],
 	);

 	const handleSave = useCallback(async () => {
 		await saveAction(sound);
-		onChange();
-	}, [saveAction, sound, onChange]);
+	}, [saveAction, sound]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/client/views/admin/customSounds/EditSound.tsx` around lines 100 -
103, handleSave currently calls onChange() after awaiting saveAction(sound) but
saveAction calls close() internally and may not indicate success (validation
failures show toasts but don't throw), risking onChange executing on an
unmounted component; either move the onChange() call into saveAction so it runs
before close() (mirroring handleDeleteButtonClick), or modify saveAction to
return a success indicator (e.g., boolean) and change handleSave to call
onChange() only when that indicator is true and before calling/after ensuring
close() timing; update references to handleSave, saveAction, onChange, and close
accordingly so onChange runs only on successful save and before the contextual
bar is closed.
🧹 Nitpick comments (2)
apps/meteor/client/views/admin/customSounds/EditSound.tsx (1)

27-27: useMemo(() => data, [data]) is a no-op.

This memo returns data unchanged and re-runs whenever data changes — it's equivalent to just using data directly. If the intent is to preserve a stable reference, data would need to be deeply compared or derived differently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/client/views/admin/customSounds/EditSound.tsx` at line 27, The
line using useMemo to create previousSound is a no-op because useMemo(() =>
data, [data]) just returns data directly; replace it with one of two fixes: (A)
if you only need the current data, drop useMemo and use data directly (remove
previousSound or set const previousSound = data), or (B) if you need a stable
previous reference, use a ref and effect to capture prior value (create const
previousSoundRef = useRef<typeof data | null>(null); update
previousSoundRef.current inside a useEffect that runs on data changes and read
previousSoundRef.current where needed), or (C) use a deep-compare memo helper
(e.g., useDeepCompareMemo or custom deepCompare) to memoize derived values —
update references to previousSound accordingly (replace previousSound creation
and any consumers).
apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx (1)

74-89: onChange() is called even when saveAction fails silently.

When insertOrUpdateSound throws inside saveAction, the error is caught internally and undefined is returned. Back in handleSave, goToNew is skipped (line 84) but onChange() on line 85 still fires, potentially triggering a list refresh despite nothing actually changing. This is pre-existing behavior, but since you're already touching this flow, consider guarding onChange behind result:

Proposed fix
 	const handleSave = useCallback(async () => {
 		if (!sound) {
 			return dispatchToastMessage({ type: 'error', message: t('Required_field', { field: t('Sound_File_mp3') }) });
 		}
 
 		try {
 			const result = await saveAction(name, sound);
 			if (result) {
 				dispatchToastMessage({ type: 'success', message: t('Custom_Sound_Saved_Successfully') });
+				goToNew(result);
+				onChange();
 			}
-			result && goToNew(result);
-			onChange();
 		} catch (error) {
 			dispatchToastMessage({ type: 'error', message: error });
 		}
 	}, [dispatchToastMessage, goToNew, name, onChange, saveAction, sound, t]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx` around lines
74 - 89, handleSave currently calls onChange() regardless of whether saveAction
returned a successful result (insertOrUpdateSound may swallow errors and return
undefined); change the flow in handleSave so onChange() is only invoked when
result is truthy (i.e., after the existing result && goToNew(result) line),
ensuring onChange is not called on failed or silent saveAction failures; keep
the existing try/catch and toast behavior intact and reference handleSave,
saveAction, insertOrUpdateSound, goToNew, and onChange to locate the change.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 29b1dcb and d7b991e.

📒 Files selected for processing (3)
  • apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx
  • apps/meteor/client/views/admin/customSounds/EditSound.tsx
  • apps/meteor/client/views/admin/customSounds/lib.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/meteor/client/views/admin/customSounds/lib.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx
  • apps/meteor/client/views/admin/customSounds/EditSound.tsx
🧬 Code graph analysis (2)
apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx (1)
apps/meteor/client/views/admin/customSounds/lib.ts (2)
  • createSoundData (35-64)
  • validate (10-33)
apps/meteor/client/views/admin/customSounds/EditSound.tsx (1)
apps/meteor/client/views/admin/customSounds/lib.ts (2)
  • createSoundData (35-64)
  • validate (10-33)
⏰ 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). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: CodeQL-Build
🔇 Additional comments (4)
apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx (2)

22-22: Good improvement: proper File typing for sound state.

Clean change from an untyped object to File, which correctly matches what useSingleFileInput provides.


34-36: LGTM — typed parameter and removed unnecessary cast.

The File parameter type and direct validate call without casting are cleaner.

apps/meteor/client/views/admin/customSounds/EditSound.tsx (2)

55-59: Type narrowing with instanceof File is clean and correct.

The instanceof File check properly narrows the union type, allowing safe access to sound.extension in the non-File branch and correct dispatch to validate/createSoundData.


148-148: Good i18n fix — t('None') instead of hardcoded 'none'.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@apps/meteor/client/views/admin/customSounds/EditSound.tsx`:
- Around line 100-103: handleSave currently calls onChange() after awaiting
saveAction(sound) but saveAction calls close() internally and may not indicate
success (validation failures show toasts but don't throw), risking onChange
executing on an unmounted component; either move the onChange() call into
saveAction so it runs before close() (mirroring handleDeleteButtonClick), or
modify saveAction to return a success indicator (e.g., boolean) and change
handleSave to call onChange() only when that indicator is true and before
calling/after ensuring close() timing; update references to handleSave,
saveAction, onChange, and close accordingly so onChange runs only on successful
save and before the contextual bar is closed.

---

Nitpick comments:
In `@apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx`:
- Around line 74-89: handleSave currently calls onChange() regardless of whether
saveAction returned a successful result (insertOrUpdateSound may swallow errors
and return undefined); change the flow in handleSave so onChange() is only
invoked when result is truthy (i.e., after the existing result &&
goToNew(result) line), ensuring onChange is not called on failed or silent
saveAction failures; keep the existing try/catch and toast behavior intact and
reference handleSave, saveAction, insertOrUpdateSound, goToNew, and onChange to
locate the change.

In `@apps/meteor/client/views/admin/customSounds/EditSound.tsx`:
- Line 27: The line using useMemo to create previousSound is a no-op because
useMemo(() => data, [data]) just returns data directly; replace it with one of
two fixes: (A) if you only need the current data, drop useMemo and use data
directly (remove previousSound or set const previousSound = data), or (B) if you
need a stable previous reference, use a ref and effect to capture prior value
(create const previousSoundRef = useRef<typeof data | null>(null); update
previousSoundRef.current inside a useEffect that runs on data changes and read
previousSoundRef.current where needed), or (C) use a deep-compare memo helper
(e.g., useDeepCompareMemo or custom deepCompare) to memoize derived values —
update references to previousSound accordingly (replace previousSound creation
and any consumers).

@KevLehman KevLehman added the contrib: valid A valid contribution where maintainers will review based on priority label Feb 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contrib: valid A valid contribution where maintainers will review based on priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants