Skip to content

Conversation

@sharanyamahajan
Copy link

@sharanyamahajan sharanyamahajan commented Jan 21, 2026

Proposed changes (including videos or screenshots)

This PR fixes an incorrect redirection that occurs after installing a private app.
Previously, the user was redirected to a marketplace URL that did not include the
app version, causing the router to misinterpret subsequent tab navigation (e.g.
Settings being treated as the version parameter).

The fix ensures that the redirect includes the app version and a default tab,
allowing all marketplace tabs (Details, Settings, etc.) to work correctly.

Issue(s)

Fixes #38215

Steps to test or reproduce

  1. Login as an Admin user
  2. Go to Marketplace → Private Apps
  3. Upload and install any private app (.zip)
  4. Observe the redirect URL after installation
  5. Confirm the URL includes the app version
  6. Click on the Settings tab and verify that the settings form loads correctly

Further comments

This change is limited to the post-install navigation logic and does not affect
the app installation process itself. It provides a safe and minimal fix to ensure
correct routing behavior in the marketplace UI.

Summary by CodeRabbit

  • New Features

    • Copy-to-clipboard button for code blocks with “Copied” feedback.
    • Loading skeleton for contextual lists.
  • Bug Fixes

    • Sidebar removal now avoids invalid state when items are missing.
    • Safer app installation flow and navigation when manifest/version is missing.
    • Improved MongoDB options parsing with clearer error handling.
    • Email send behavior adjusted for non-test environments.
  • Refactor

    • Streamlined account profile form layout and validation; inputs trimmed on change.

✏️ Tip: You can customize this high-level summary in your review settings.

@sharanyamahajan sharanyamahajan requested review from a team as code owners January 21, 2026 17:41
@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Jan 21, 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 Jan 21, 2026

⚠️ No Changeset found

Latest commit: 5285b28

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

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 21, 2026

Walkthrough

Fixes sidebar removal safety, streamlines profile form layout/validation, ensures marketplace install redirects include app version, adds a contextual list skeleton component, improves Mongo config parsing and options, adds copy-to-clipboard for code blocks, and simplifies BaseRaw data-access surface and deletion rollback.

Changes

Cohort / File(s) Summary
Sidebar Item Removal Safety
apps/meteor/client/lib/createSidebarItems.ts
Adds guard for missing index and uses items.splice(index, 1) instead of delete items[index]; type/signature formatting adjusted.
Profile Form refactor
apps/meteor/client/views/account/profile/AccountProfileForm.tsx
Simplifies validation/control flow, trims inputs on change, removes several inline error/hint blocks, and replaces verbose layout wrappers with compact inline Box layouts.
Marketplace install navigation
apps/meteor/client/views/marketplace/hooks/useInstallApp.tsx
Extracts and uses installed version for navigation, adds guard when version missing, and simplifies file/manifest handling during upload.
Contextual list skeleton component
apps/meteor/client/views/room/contextualBar/lib/ContextualListSkeleton.tsx
Adds new default-exported ContextualListSkeleton component rendering configurable skeleton rows (default 6).
Mongo config & Email sendAsync
apps/meteor/packages/rocketchat-mongo-config/server/index.js
Adds try/catch for MONGO_OPTIONS JSON parse with descriptive error, conditionally sets retryWrites: false, sets TLS ECDH curve, and wraps Email.sendAsync with stream in certain envs.
CodeBlock copy feature
packages/gazzodown/src/code/CodeBlock.tsx
Makes lines optional, adds copy-to-clipboard with transient "Copied" state and UI button, and adjusts highlighting/render fallback.
BaseRaw surface & deletion
packages/models/src/models/BaseRaw.ts
Consolidates findOneById implementation, significantly reduces exposed multi-method APIs, and implements a simplified findOneAndDelete with trash-path rollback/error logging.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

stat: ready to merge, stat: QA assured

Suggested reviewers

  • ggazzo

Poem

🐰 I hopped through code to mend and sew,

Trimmed the names and made installs go,
A sidebar guard to keep things right,
Copying code with one small bite,
Databases tidy—hop, delight! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning While most changes are focused on the core fix (useInstallApp.tsx), the PR includes refactoring in unrelated files (createSidebarItems.ts, AccountProfileForm.tsx, ContextualListSkeleton.tsx, rocketchat-mongo-config, CodeBlock.tsx, BaseRaw.ts) that exceed the scope of fixing the app installation redirect issue. Isolate the useInstallApp.tsx changes into a dedicated PR, and move unrelated refactoring (sidebar items, account profile, skeleton components, MongoDB config, code blocks, base models) to separate PRs focused on their respective improvements.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately reflects the main fix: including app version in the private app install redirect, which directly addresses the primary issue in the changeset.
Linked Issues check ✅ Passed The PR changes address issue #38215 by extracting app version in useInstallApp.tsx and adding a guard to prevent navigation when version is missing, ensuring correct redirect URLs with version parameters.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

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

⚠️ Outside diff range comments (3)
apps/meteor/packages/rocketchat-mongo-config/server/index.js (1)

20-29: Potential TypeError if MONGO_URL is undefined.

Line 22 calls process.env.MONGO_URL.includes(...) without first checking if MONGO_URL is defined. If it's undefined, this will throw a TypeError: Cannot read properties of undefined (reading 'includes').

🐛 Proposed fix
 const mongoConnectionOptions = {
 	// add retryWrites=false if not present in MONGO_URL
-	...(!process.env.MONGO_URL.includes('retryWrites') && { retryWrites: false }),
+	...(!process.env.MONGO_URL?.includes('retryWrites') && { retryWrites: false }),
 	ignoreUndefined: false,

Alternatively, if MONGO_URL is expected to always be set when this package loads, consider adding a guard at the top of the file that throws a descriptive error if it's missing.

apps/meteor/client/views/account/profile/AccountProfileForm.tsx (1)

75-86: Remove unused email confirmation code.

mutateConfirmationEmail and handleSendConfirmationEmail are dead code—they are defined but never called anywhere in the application. Remove both the mutation setup (lines 75-79) and the callback handler (lines 81-86).

packages/models/src/models/BaseRaw.ts (1)

172-195: Propagate FindOneAndDeleteOptions through the trash path and remove section banner comments.

The trash path ignores options (e.g., session, collation, hint, projection), which breaks transactional correctness and can return a different document than the delete would have targeted. Thread the relevant options into findOne, trash.updateOne, and deleteOne.

Additionally, remove the section banner comments (/* ===================== FIXED METHOD ===================== */ and /* ======================================================== */) as they violate the coding guideline to avoid code comments in TypeScript/JavaScript implementation.

🔧 Suggested fix
-		const doc = await this.col.findOne(filter);
+		const { session, projection, collation, hint } = options || {};
+		const doc = await this.col.findOne(filter, { session, projection, collation, hint });

 		const { _id, ...record } = doc;
 		const trash: TDeleted = {
 			...record,
 			_deletedAt: new Date(),
 			__collection__: this.name,
 		} as unknown as TDeleted;

-		await this.trash.updateOne({ _id } as Filter<TDeleted>, { $set: trash } as UpdateFilter<TDeleted>, {
-			upsert: true,
-		});
+		await this.trash.updateOne({ _id } as Filter<TDeleted>, { $set: trash } as UpdateFilter<TDeleted>, {
+			upsert: true,
+			session,
+		});

 		try {
-			await this.col.deleteOne({ _id } as Filter<T>);
+			await this.col.deleteOne({ _id } as Filter<T>, { session, collation, hint });
🤖 Fix all issues with AI agents
In `@apps/meteor/client/views/account/profile/AccountProfileForm.tsx`:
- Line 211: The StatusText, Nickname, and Bio input fields currently call
field.onChange(e.target.value.trim()) which prevents typing spaces; change each
field to call field.onChange(e.target.value) on onChange and perform the
trimming on onBlur instead (e.g., onBlur ->
field.onChange(e.target.value.trim()) or call a trim handler) so users can type
multi-word values; update the JSX for the StatusText, Nickname, and Bio fields
accordingly to move trim from onChange to onBlur.
- Line 170: The current onChange handler in AccountProfileForm.tsx calls
field.onChange(e.target.value.trim()), which strips trailing spaces and prevents
typing multi-word values; change the onChange to call
field.onChange(e.target.value) (preserving user input) and perform trimming
either in an onBlur handler for the same input (e.g., onBlur={() =>
field.onChange(currentValue.trim())}) or centrally in the form submit handler
(e.g., when building data in handleSubmit, call .trim() on the relevant fields
before sending), keeping references to the same field.onChange and the form's
submit logic so behavior is consistent.
- Line 266: The email field's validation uses validateEmail which returns only a
boolean so react-hook-form has no message to display; update the rules for the
email input (the rules prop containing validate: validateEmail) to return a
string on failure (e.g., replace validate: validateEmail with validate: (v) =>
validateEmail(v) || t('Invalid_email')) and also ensure the TextInput for the
email displays errors by passing error={errors.email?.message} (mirror how name
uses errors.name?.message) so users see a descriptive "Invalid email" message
when validation fails.

In `@apps/meteor/client/views/marketplace/hooks/useInstallApp.tsx`:
- Around line 45-60: The onSuccess callback in useInstallApp.tsx currently
returns silently when data.app.version is falsy; instead, call the hook's
existing install error handler (the same handler used for other install
failures—e.g., setInstallError or onError within this hook) with a clear message
or Error object like "Missing app version" and then return; replace the silent
return in onSuccess (which destructures id and version from data.app) with a
call to that install error handler so the user receives feedback before exiting
the function.

In `@apps/meteor/packages/rocketchat-mongo-config/server/index.js`:
- Around line 37-41: In the catch block that handles parsing of MONGO_OPTIONS
(the code using mongoOptionStr), stop logging the raw mongoOptionStr to avoid
exposing secrets; replace the console.error line that prints the provided value
with a safe indicator such as the string length or a masked/sanitized summary
(e.g., "Value length: " + mongoOptionStr.length or a hash/partial mask), keep
the parse error message (error.message) and the contextual log lines, and ensure
you only reference mongoOptionStr for non-sensitive metadata rather than its
full content.

In `@packages/gazzodown/src/code/CodeBlock.tsx`:
- Around line 29-37: The handleCopy function sets a timeout that calls
setCopied(false) after 1500ms, which can trigger a state update after the
component unmounts; to fix it, store the timeout id in a ref (e.g., timeoutRef)
inside the component and clear that timeout in a useEffect cleanup, or track
mounted state with an isMountedRef and check it before calling setCopied(false);
update the handleCopy callback to save the timer id to timeoutRef and ensure
useEffect returns a cleanup that clearsTimeout(timeoutRef.current) and/or sets
isMountedRef.current = false so no state updates occur after unmount.

In `@packages/models/src/models/BaseRaw.ts`:
- Around line 195-216: The rollback code in BaseRaw.findOneAndDelete can remove
the last copy if deleteOne failed after the server already deleted the original;
to fix it, extract session from the passed options and ensure you pass the
options (including session) to all MongoDB calls (the initial findOneAndDelete,
the insertion to trash and the rollback this.trash.deleteOne), and before
deleting the trash record check whether the original document still exists
(e.g., a findOne({_id}, {session}) or count)—only delete the trash if the
original is confirmed missing; also propagate options/session in all relevant
operations to support transactions.
🧹 Nitpick comments (3)
packages/gazzodown/src/code/CodeBlock.tsx (2)

49-52: Minor: Unnecessary fragment wrapper.

The fragment around head on line 51 is redundant.

Suggested simplification
 		return (
 			<>
-				<>{head}</>
+				{head}
 				{chunks.map((chunk, index) =>

81-96: Consider extracting inline styles and adding focus/hover states.

The inline styles reduce maintainability and the button lacks visible focus/hover states for keyboard navigation accessibility. Consider using a CSS class or the project's styling solution (e.g., @rocket.chat/fuselage components if available) to handle these styles and states consistently.

packages/models/src/models/BaseRaw.ts (1)

170-170: Remove section banner comments in implementation.

These banner comments violate the “no implementation comments” guideline for TS/JS. As per coding guidelines, please remove them.

🧹 Suggested cleanup
-	/* ===================== FIXED METHOD ===================== */
+	
@@
-	/* ======================================================== */
+	

Also applies to: 222-222

<TextInput
{...field}
id={nameId}
onChange={(e) => field.onChange(e.target.value.trim())}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Trimming on onChange prevents typing multi-word values.

Calling trim() on every change removes trailing spaces immediately, making it impossible for users to type multi-word names like "John Doe". When the user types "John " and presses space, the value is trimmed to "John", so the next character appends directly without a space.

Consider trimming on blur or on submit instead:

🐛 Proposed fix
 <TextInput
   {...field}
   id={nameId}
-  onChange={(e) => field.onChange(e.target.value.trim())}
+  onChange={(e) => field.onChange(e.target.value)}
+  onBlur={(e) => { field.onChange(e.target.value.trim()); field.onBlur(); }}
   error={errors.name?.message}
   disabled={!allowRealNameChange}
 />
📝 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
onChange={(e) => field.onChange(e.target.value.trim())}
<TextInput
{...field}
id={nameId}
onChange={(e) => field.onChange(e.target.value)}
onBlur={(e) => { field.onChange(e.target.value.trim()); field.onBlur(); }}
error={errors.name?.message}
disabled={!allowRealNameChange}
/>
🤖 Prompt for AI Agents
In `@apps/meteor/client/views/account/profile/AccountProfileForm.tsx` at line 170,
The current onChange handler in AccountProfileForm.tsx calls
field.onChange(e.target.value.trim()), which strips trailing spaces and prevents
typing multi-word values; change the onChange to call
field.onChange(e.target.value) (preserving user input) and perform trimming
either in an onBlur handler for the same input (e.g., onBlur={() =>
field.onChange(currentValue.trim())}) or centrally in the form submit handler
(e.g., when building data in handleSubmit, call .trim() on the relevant fields
before sending), keeping references to the same field.onChange and the form's
submit logic so behavior is consistent.

{...field}
id={statusTextId}
error={errors?.statusText?.message}
onChange={(e) => field.onChange(e.target.value.trim())}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Same trim issue affects StatusText, Nickname, and Bio fields.

These fields also use trim() on onChange, which will prevent users from typing multi-word values. Apply the same fix (trim on blur instead of change) to these fields.

Lines affected:

  • Line 211: StatusText
  • Line 235: Nickname
  • Line 252: Bio

Also applies to: 235-235, 252-252

🤖 Prompt for AI Agents
In `@apps/meteor/client/views/account/profile/AccountProfileForm.tsx` at line 211,
The StatusText, Nickname, and Bio input fields currently call
field.onChange(e.target.value.trim()) which prevents typing spaces; change each
field to call field.onChange(e.target.value) on onChange and perform the
trimming on onBlur instead (e.g., onBlur ->
field.onChange(e.target.value.trim()) or call a trim handler) so users can type
multi-word values; update the JSX for the StatusText, Nickname, and Bio fields
accordingly to move trim from onChange to onBlur.

required: t('Required_field', { field: t('Email') }),
validate: { validateEmail: (email) => (validateEmail(email) ? undefined : t('error-invalid-email-address')) },
}}
rules={{ required: t('Required_field', { field: t('Email') }), validate: validateEmail }}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find validateEmail in the tools package
find . -path "*/packages/tools/src/*" -name "*.ts" -o -name "*.js" | head -20

Repository: RocketChat/Rocket.Chat

Length of output: 1010


🏁 Script executed:

# Search for validateEmail definition
rg -n "validateEmail" packages/tools/src/ -A 5 -B 2

Repository: RocketChat/Rocket.Chat

Length of output: 882


🏁 Script executed:

# Also check if there's an index file that exports it
fd -e ts -e js "index" packages/tools/src/ | xargs grep -l "validateEmail" 2>/dev/null || echo "Not found in index files"

Repository: RocketChat/Rocket.Chat

Length of output: 93


🏁 Script executed:

# Read the complete validateEmail function
cat packages/tools/src/validateEmail.ts

Repository: RocketChat/Rocket.Chat

Length of output: 527


🏁 Script executed:

# Check the AccountProfileForm.tsx file around line 266 and surrounding validation logic
cat -n apps/meteor/client/views/account/profile/AccountProfileForm.tsx | sed -n '250,280p'

Repository: RocketChat/Rocket.Chat

Length of output: 1195


🏁 Script executed:

# Check if there's any custom error message handling or field-level error display
cat -n apps/meteor/client/views/account/profile/AccountProfileForm.tsx | sed -n '1,50p'

Repository: RocketChat/Rocket.Chat

Length of output: 2204


🏁 Script executed:

# Check if there's error rendering for the email field and how FieldError is used
cat -n apps/meteor/client/views/account/profile/AccountProfileForm.tsx | sed -n '260,290p'

Repository: RocketChat/Rocket.Chat

Length of output: 1052


🏁 Script executed:

# Search for FieldError usage in this file to understand error display pattern
rg -n "FieldError" apps/meteor/client/views/account/profile/AccountProfileForm.tsx -B 2 -A 2

Repository: RocketChat/Rocket.Chat

Length of output: 137


🏁 Script executed:

# Check other field validations in the form to see the error handling pattern
cat -n apps/meteor/client/views/account/profile/AccountProfileForm.tsx | grep -A 10 "FieldError" | head -30

Repository: RocketChat/Rocket.Chat

Length of output: 369


🏁 Script executed:

# Look for all Controller usages to see if any show error display pattern
ast-grep --pattern 'Controller
  control={$_}
  name={$_}
  rules={$_}
  render={({ field, fieldState }) => (
    $$$
  )}
/'

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Search more broadly for error handling in the Controller renders
rg -n "fieldState|formState" apps/meteor/client/views/account/profile/AccountProfileForm.tsx -B 5 -A 5

Repository: RocketChat/Rocket.Chat

Length of output: 356


🏁 Script executed:

# Check if there are other fields showing error messages with FieldError
cat -n apps/meteor/client/views/account/profile/AccountProfileForm.tsx | sed -n '100,200p'

Repository: RocketChat/Rocket.Chat

Length of output: 3633


🏁 Script executed:

# Look at a larger section to see if any field uses FieldError to display validation errors
rg -n "FieldError" apps/meteor/client/views/account/profile/AccountProfileForm.tsx -B 10 -A 3

Repository: RocketChat/Rocket.Chat

Length of output: 298


🏁 Script executed:

# Check validateUsername to see if it has similar issues
rg -n "validateUsername" packages/tools/src/ -A 5 -B 2

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Check if there's a pattern in how other fields handle validation with boolean returns
rg -n "validate:" apps/meteor/client/views/account/profile/AccountProfileForm.tsx -B 2 -A 5

Repository: RocketChat/Rocket.Chat

Length of output: 752


🏁 Script executed:

# Check if there's global error handling or if errors are displayed elsewhere
cat -n apps/meteor/client/views/account/profile/AccountProfileForm.tsx | sed -n '260,290p'

Repository: RocketChat/Rocket.Chat

Length of output: 1052


🏁 Script executed:

# Look at the entire render to understand if email field error is displayed somewhere
rg -n "email" apps/meteor/client/views/account/profile/AccountProfileForm.tsx | grep -i error

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Check react-hook-form behavior when validate returns false without custom message
# by looking at how the form uses formState errors
cat -n apps/meteor/client/views/account/profile/AccountProfileForm.tsx | sed -n '60,75p'

Repository: RocketChat/Rocket.Chat

Length of output: 594


Provide a descriptive error message for invalid emails.

The validate callback for the email field uses validateEmail which returns only a boolean. When validation fails, react-hook-form has no error message to display. Additionally, the email field's TextInput doesn't have an error prop to show validation feedback, unlike the name field which displays errors.name?.message. Users won't know why their email was rejected.

Consider either:

  1. Wrapping validateEmail to return a string error message (e.g., (email) => validateEmail(email) || t('Invalid_email'))
  2. Adding error display to the email TextInput with error={errors.email?.message}
🤖 Prompt for AI Agents
In `@apps/meteor/client/views/account/profile/AccountProfileForm.tsx` at line 266,
The email field's validation uses validateEmail which returns only a boolean so
react-hook-form has no message to display; update the rules for the email input
(the rules prop containing validate: validateEmail) to return a string on
failure (e.g., replace validate: validateEmail with validate: (v) =>
validateEmail(v) || t('Invalid_email')) and also ensure the TextInput for the
email displays errors by passing error={errors.email?.message} (mirror how name
uses errors.name?.message) so users see a descriptive "Invalid email" message
when validation fails.

Comment on lines 45 to 60
onSuccess: (data: { app: App }) => {
const { id, version } = data.app;

if (!version) {
return;
}

router.navigate({
name: 'marketplace',
params: {
context: 'private',
page: 'info',
id: data.app.id,
id,
version,
tab: 'details',
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle missing version with user feedback.

If version is absent, the flow silently returns, which can leave the user without guidance. Consider surfacing an error via the existing install error handler so the UX isn’t a no-op.

Proposed fix
-			if (!version) {
-				return;
-			}
+			if (!version) {
+				handleInstallError(new Error('Installed app version missing'));
+				return;
+			}
📝 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
onSuccess: (data: { app: App }) => {
const { id, version } = data.app;
if (!version) {
return;
}
router.navigate({
name: 'marketplace',
params: {
context: 'private',
page: 'info',
id: data.app.id,
id,
version,
tab: 'details',
},
onSuccess: (data: { app: App }) => {
const { id, version } = data.app;
if (!version) {
handleInstallError(new Error('Installed app version missing'));
return;
}
router.navigate({
name: 'marketplace',
params: {
context: 'private',
page: 'info',
id,
version,
tab: 'details',
},
🤖 Prompt for AI Agents
In `@apps/meteor/client/views/marketplace/hooks/useInstallApp.tsx` around lines 45
- 60, The onSuccess callback in useInstallApp.tsx currently returns silently
when data.app.version is falsy; instead, call the hook's existing install error
handler (the same handler used for other install failures—e.g., setInstallError
or onError within this hook) with a clear message or Error object like "Missing
app version" and then return; replace the silent return in onSuccess (which
destructures id and version from data.app) with a call to that install error
handler so the user receives feedback before exiting the function.

Comment on lines 37 to 41
} catch (error) {
console.error('Failed to parse MONGO_OPTIONS environment variable');
console.error('Provided value:', mongoOptionStr);
console.error('Error:', error.message);
console.error('Please ensure MONGO_OPTIONS contains valid JSON');
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid logging potentially sensitive configuration values.

Line 39 logs the raw MONGO_OPTIONS value which could contain sensitive information such as credentials, API keys, or connection strings. This could expose secrets in logs.

🔒 Proposed fix
 	} catch (error) {
 		console.error('Failed to parse MONGO_OPTIONS environment variable');
-		console.error('Provided value:', mongoOptionStr);
 		console.error('Error:', error.message);
 		console.error('Please ensure MONGO_OPTIONS contains valid JSON');

If debugging is needed, consider logging only the length or a sanitized indicator (e.g., console.error('Value length:', mongoOptionStr.length)).

📝 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
} catch (error) {
console.error('Failed to parse MONGO_OPTIONS environment variable');
console.error('Provided value:', mongoOptionStr);
console.error('Error:', error.message);
console.error('Please ensure MONGO_OPTIONS contains valid JSON');
} catch (error) {
console.error('Failed to parse MONGO_OPTIONS environment variable');
console.error('Error:', error.message);
console.error('Please ensure MONGO_OPTIONS contains valid JSON');
🤖 Prompt for AI Agents
In `@apps/meteor/packages/rocketchat-mongo-config/server/index.js` around lines 37
- 41, In the catch block that handles parsing of MONGO_OPTIONS (the code using
mongoOptionStr), stop logging the raw mongoOptionStr to avoid exposing secrets;
replace the console.error line that prints the provided value with a safe
indicator such as the string length or a masked/sanitized summary (e.g., "Value
length: " + mongoOptionStr.length or a hash/partial mask), keep the parse error
message (error.message) and the contextual log lines, and ensure you only
reference mongoOptionStr for non-sensitive metadata rather than its full
content.

Comment on lines +29 to +37
const handleCopy = useCallback(async () => {
try {
await navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
} catch (error) {
console.error('Failed to copy code block', error);
}
}, [code]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential state update on unmounted component.

The setTimeout in handleCopy isn't cleared if the component unmounts before the 1500ms elapses. This can cause a React warning and a no-op state update.

Proposed fix using a ref to track mounted state
 const CodeBlock = ({ lines = [], language }: CodeBlockProps): ReactElement => {
 	const ref = useRef<HTMLElement>(null);
 	const [copied, setCopied] = useState(false);
+	const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
 
 	const { highlightRegex } = useContext(MarkupInteractionContext);
 
 	const code = useMemo(() => lines.map((line) => line.value.value).join('\n'), [lines]);
 
 	const handleCopy = useCallback(async () => {
 		try {
 			await navigator.clipboard.writeText(code);
 			setCopied(true);
-			setTimeout(() => setCopied(false), 1500);
+			timeoutRef.current = setTimeout(() => setCopied(false), 1500);
 		} catch (error) {
 			console.error('Failed to copy code block', error);
 		}
 	}, [code]);
+
+	useEffect(() => {
+		return () => {
+			if (timeoutRef.current) {
+				clearTimeout(timeoutRef.current);
+			}
+		};
+	}, []);
🤖 Prompt for AI Agents
In `@packages/gazzodown/src/code/CodeBlock.tsx` around lines 29 - 37, The
handleCopy function sets a timeout that calls setCopied(false) after 1500ms,
which can trigger a state update after the component unmounts; to fix it, store
the timeout id in a ref (e.g., timeoutRef) inside the component and clear that
timeout in a useEffect cleanup, or track mounted state with an isMountedRef and
check it before calling setCopied(false); update the handleCopy callback to save
the timer id to timeoutRef and ensure useEffect returns a cleanup that
clearsTimeout(timeoutRef.current) and/or sets isMountedRef.current = false so no
state updates occur after unmount.

Comment on lines +195 to +216
} catch (originalError) {
try {
const rollbackResult = await this.trash.deleteOne({ _id } as Filter<TDeleted>);

if (!rollbackResult || rollbackResult.deletedCount !== 1) {
console.error('BaseRaw.findOneAndDelete: Rollback failed – orphaned trash record', {
collection: this.name,
_id,
rollbackResult,
originalError,
});
}
} catch (rollbackError) {
console.error('BaseRaw.findOneAndDelete: Rollback threw exception', {
collection: this.name,
_id,
rollbackError,
originalError,
});
}
return this.col.deleteMany(filter);
}

const cursor = this.find<ResultFields<T, C>>(filter, { session: options?.session });

const ids: T['_id'][] = [];
for await (const doc of cursor) {
const { _id, ...record } = doc as T;

const trash: TDeleted = {
...record,
_deletedAt: new Date(),
__collection__: this.name,
} as unknown as TDeleted;

ids.push(_id as T['_id']);

// since the operation is not atomic, we need to make sure that the record is not already deleted/inserted
await this.trash?.updateOne({ _id } as Filter<TDeleted>, { $set: trash } as UpdateFilter<TDeleted>, {
upsert: true,
session: options?.session,
});

void options?.onTrash?.(doc);
}

if (options) {
return this.col.deleteMany({ _id: { $in: ids } } as unknown as Filter<T>, options);
}
return this.col.deleteMany({ _id: { $in: ids } } as unknown as Filter<T>);
}

// Trash
trashFind<P extends TDeleted>(
query: Filter<TDeleted>,
options?: FindOptions<P extends TDeleted ? TDeleted : P>,
): FindCursor<WithId<TDeleted>> | undefined {
if (!this.trash) {
return undefined;
}

if (options) {
return this.trash.find(
{
__collection__: this.name,
...query,
},
options,
);
throw originalError;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the file
fd -t f "BaseRaw.ts" --exact

Repository: RocketChat/Rocket.Chat

Length of output: 290


🏁 Script executed:

# Look at the full findOneAndDelete method with context
sed -n '150,250p' packages/models/src/models/BaseRaw.ts | cat -n

Repository: RocketChat/Rocket.Chat

Length of output: 3686


🏁 Script executed:

# Also check the file structure to understand the class definition
head -100 packages/models/src/models/BaseRaw.ts | cat -n

Repository: RocketChat/Rocket.Chat

Length of output: 3593


🏁 Script executed:

# Check the type definition for FindOneAndDeleteOptions
rg "FindOneAndDeleteOptions" -A 3 --type ts

Repository: RocketChat/Rocket.Chat

Length of output: 1438


🏁 Script executed:

# Look for session usage patterns in the file
rg "session" packages/models/src/models/BaseRaw.ts -B 2 -A 2

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Check if options parameter contains session elsewhere
rg "options.*session" packages/models/src/models/BaseRaw.ts -B 1 -A 1

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Check MongoDB type definition - see if FindOneAndDeleteOptions has session
rg "interface FindOneAndDeleteOptions|type FindOneAndDeleteOptions" -B 2 -A 10

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Check how other methods use session in this file, search broader
grep -n "session" packages/models/src/models/BaseRaw.ts

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Let's check the MongoDB driver's FindOneAndDeleteOptions type 
# by looking at node_modules or type stubs
find . -name "*.d.ts" -type f -exec grep -l "FindOneAndDeleteOptions" {} \; 2>/dev/null | head -5

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Check the actual usage of options parameter in the method
sed -n '23,71p' packages/models/src/models/BaseRaw.ts | grep -n "options"

Repository: RocketChat/Rocket.Chat

Length of output: 374


🏁 Script executed:

# Look at MongoDB's FindOneAndDeleteOptions to understand session support
# Check if there are type definitions in node_modules
ls -la node_modules/mongodb/lib/typings/*.d.ts 2>/dev/null | head -10

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Try to find where FindOneAndDeleteOptions is defined
fd "index.d.ts" node_modules/mongodb --type f 2>/dev/null | head -1 | xargs grep -A 20 "interface FindOneAndDeleteOptions"

Repository: RocketChat/Rocket.Chat

Length of output: 50


🏁 Script executed:

# Look at how deleteOne is called with options in the method
sed -n '40,46p' packages/models/src/models/BaseRaw.ts | cat -n

Repository: RocketChat/Rocket.Chat

Length of output: 279


🏁 Script executed:

# Check if there are other methods in the file that pass options to MongoDB operations
rg "await this\.(col|trash)\." packages/models/src/models/BaseRaw.ts -A 1 | head -40

Repository: RocketChat/Rocket.Chat

Length of output: 418


🏁 Script executed:

# Search for how session is passed in other operations
rg "\{ session" packages/models/src/models/BaseRaw.ts -B 2 -A 2

Repository: RocketChat/Rocket.Chat

Length of output: 48


🏁 Script executed:

# Check the actual MongoDB package.json to understand version
cat package.json | grep -A 2 '"mongodb"'

Repository: RocketChat/Rocket.Chat

Length of output: 117


🏁 Script executed:

# Check MongoDB 6.10.0 types - specifically FindOneAndDeleteOptions
cd node_modules/mongodb && find . -name "*.d.ts" -type f | xargs grep -l "FindOneAndDeleteOptions" | head -1 | xargs cat | grep -A 30 "interface FindOneAndDeleteOptions"

Repository: RocketChat/Rocket.Chat

Length of output: 136


🏁 Script executed:

# Let's verify the actual concern - can a deleteOne fail after succeeding on server?
# Check MongoDB documentation references in comments or issues
rg "deleteOne|delete.*error" packages/models/src/models/BaseRaw.ts -B 1 -A 1

Repository: RocketChat/Rocket.Chat

Length of output: 246


🏁 Script executed:

# Check if there are any tests for this method
fd -t f "*.test.ts" -o "*.spec.ts" | xargs grep -l "findOneAndDelete" 2>/dev/null | head -3

Repository: RocketChat/Rocket.Chat

Length of output: 203


🏁 Script executed:

# Find test files for this class
fd -type f -name "*.test.*" | xargs grep -l "findOneAndDelete" 2>/dev/null | head -3

Repository: RocketChat/Rocket.Chat

Length of output: 236


🏁 Script executed:

# Check if there's MongoDB documentation or comments about deleteOne error handling
rg "transient|network|stepdown|race condition" packages/models/src/models/ -i

Repository: RocketChat/Rocket.Chat

Length of output: 189


🏁 Script executed:

# Let's verify the suggested fix - check if options parameter can have session
# by looking at how deleteOne is called elsewhere
rg "deleteOne.*options" packages/models/src/models/ -B 1 -A 1

Repository: RocketChat/Rocket.Chat

Length of output: 649


🏁 Script executed:

# Check the actual line numbers mentioned in the review to match with current code
wc -l packages/models/src/models/BaseRaw.ts

Repository: RocketChat/Rocket.Chat

Length of output: 107


🏁 Script executed:

# The file has 241 lines total. Let's look at the actual lines 195-216 mentioned in the review
sed -n '185,225p' packages/models/src/models/BaseRaw.ts | cat -n

Repository: RocketChat/Rocket.Chat

Length of output: 1428


🏁 Script executed:

# Check the full findOneAndDelete method implementation to see how options are used
sed -n '165,225p' packages/models/src/models/BaseRaw.ts | cat -n

Repository: RocketChat/Rocket.Chat

Length of output: 2226


🏁 Script executed:

# Check MongoDB documentation about session support in DeleteResult
# and verify what the race condition scenario looks like
rg "options" packages/models/src/models/BaseRaw.ts | grep -A 2 -B 2 "deleteOne\|findOne"

Repository: RocketChat/Rocket.Chat

Length of output: 1419


🏁 Script executed:

# Verify how the method should pass options to the MongoDB operations
rg "col\.deleteOne|trash\.deleteOne" packages/models/src/models/ -B 1 -A 1

Repository: RocketChat/Rocket.Chat

Length of output: 1131


Rollback can drop the last copy on ambiguous delete errors.

If deleteOne throws after the server actually deleted the document (e.g., transient network/primary stepdown), the rollback deletes the trash record and data is lost. Additionally, the method receives an options parameter but never passes it to any MongoDB operations, preventing session/transaction support. Consider verifying the document still exists before rolling back, passing options to all operations, and extracting the session from options if available.

🔧 Suggested approach

Check that the document still exists before deleting the trash record. Extract session from the options parameter and pass it to MongoDB operations:

 		try {
-			await this.col.deleteOne({ _id } as Filter<T>);
+			await this.col.deleteOne({ _id } as Filter<T>, options || {});
 		} catch (originalError) {
 			try {
+				const stillExists = await this.col.findOne({ _id } as Filter<T>, options || {});
+				if (stillExists) {
-				const rollbackResult = await this.trash.deleteOne({ _id } as Filter<TDeleted>);
+					const rollbackResult = await this.trash.deleteOne({ _id } as Filter<TDeleted>);
-
-				if (!rollbackResult || rollbackResult.deletedCount !== 1) {
-					console.error('BaseRaw.findOneAndDelete: Rollback failed – orphaned trash record', {
+					if (!rollbackResult || rollbackResult.deletedCount !== 1) {
+						console.error('BaseRaw.findOneAndDelete: Rollback failed – orphaned trash record', {
+							collection: this.name,
+							_id,
+							rollbackResult,
+							originalError,
+						});
+					}
+				} else {
+					console.error('BaseRaw.findOneAndDelete: Delete outcome uncertain; keeping trash record', {
 						collection: this.name,
 						_id,
-					rollbackResult,
 						originalError,
 					});
+				}
🤖 Prompt for AI Agents
In `@packages/models/src/models/BaseRaw.ts` around lines 195 - 216, The rollback
code in BaseRaw.findOneAndDelete can remove the last copy if deleteOne failed
after the server already deleted the original; to fix it, extract session from
the passed options and ensure you pass the options (including session) to all
MongoDB calls (the initial findOneAndDelete, the insertion to trash and the
rollback this.trash.deleteOne), and before deleting the trash record check
whether the original document still exists (e.g., a findOne({_id}, {session}) or
count)—only delete the trash if the original is confirmed missing; also
propagate options/session in all relevant operations to support transactions.

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 7 files

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect redirection URL after installing a Private App

2 participants