feat: Add multiple file upload support to message composer#39425
feat: Add multiple file upload support to message composer#39425
Conversation
|
Looks like this PR is ready to merge! 🎉 |
🦋 Changeset detectedLatest commit: f72836d The changes in this PR will be included in the next version bump. This PR includes changesets to release 43 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
WalkthroughAdds multi-file attachment support integrated into the message composer: introduces thread-specific upload stores, a class-based UploadsStore API, encrypted and non-encrypted upload flows, composer-integrated UI components and hooks, REST API extensions to accept fileName/fileContent, and extensive e2e test updates. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Composer as Message Composer
participant UploadStore as UploadsStore
participant Flow as processMessageUploads
participant Server as API Server
participant Chat as Chat Server
User->>Composer: Attach/select files
Composer->>UploadStore: set / add uploads
UploadStore-->>Composer: emit update
Composer->>Composer: render MessageComposerFiles
User->>Composer: Click Send
Composer->>Flow: processMessageUploads(message)
Flow->>UploadStore: get uploads (choose thread/channel)
alt failed uploads exist
Flow->>User: show warning modal
User->>Flow: confirm send
end
loop per confirmed file
Flow->>Server: POST /v1/rooms.mediaConfirm/{rid}/{fileId} (include fileName/fileContent if provided)
Server-->>Flow: mediaConfirm response
Flow->>UploadStore: removeUpload on success / update on failure
end
Flow->>Chat: POST chat.postMessage with attachments
Chat-->>User: message persisted
Composer->>Composer: clear()
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Suggested labels
Suggested reviewers
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #39425 +/- ##
===========================================
- Coverage 70.96% 70.92% -0.05%
===========================================
Files 3197 3209 +12
Lines 113337 113879 +542
Branches 20513 20696 +183
===========================================
+ Hits 80430 80763 +333
- Misses 30856 31062 +206
- Partials 2051 2054 +3
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
cdac5f8 to
580716d
Compare
c4ed586 to
7848583
Compare
7848583 to
219806a
Compare
219806a to
260aba4
Compare
260aba4 to
0ea35d2
Compare
0ea35d2 to
7060f96
Compare
There was a problem hiding this comment.
11 issues found across 54 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/meteor/client/views/room/composer/messageBox/MessageComposerFileLoader.tsx">
<violation number="1" location="apps/meteor/client/views/room/composer/messageBox/MessageComposerFileLoader.tsx:22">
P2: `...props` currently overrides the loader animation class. Merge the incoming `className` and apply it after the spread so the spinner animation is preserved.</violation>
</file>
<file name="apps/meteor/app/ui/client/lib/ChatMessages.ts">
<violation number="1" location="apps/meteor/app/ui/client/lib/ChatMessages.ts:127">
P2: Uploads are cleared before checking whether the message can be edited, which can discard pending files on failed edit attempts.</violation>
</file>
<file name="apps/meteor/client/views/room/body/UploadProgress/UploadProgressIndicator.tsx">
<violation number="1" location="apps/meteor/client/views/room/body/UploadProgress/UploadProgressIndicator.tsx:24">
P2: Progress percentage is averaged using completed uploads while the label count only includes active uploads, producing misleading progress values.</violation>
</file>
<file name="apps/meteor/tests/e2e/fixtures/responses/mediaResponse.ts">
<violation number="1" location="apps/meteor/tests/e2e/fixtures/responses/mediaResponse.ts:3">
P2: Escape the dot in the `rooms.media` URL matcher so `waitForResponse` only resolves for the intended endpoint.</violation>
</file>
<file name="apps/meteor/tests/e2e/file-upload.spec.ts">
<violation number="1" location="apps/meteor/tests/e2e/file-upload.spec.ts:122">
P1: The max-files test validates the wrong file name (`number6.png`), so it can pass without actually verifying that the 11th upload is rejected.</violation>
</file>
<file name="apps/meteor/client/lib/chats/flows/processMessageUploads.ts">
<violation number="1" location="apps/meteor/client/lib/chats/flows/processMessageUploads.ts:61">
P2: Memory leak: the blob URL created by `URL.createObjectURL` is never revoked after the image dimensions are read. Add `URL.revokeObjectURL(dataURL)` in both the `onload` and `onerror` callbacks of `getHeightAndWidthFromDataUrl`.</violation>
</file>
<file name="apps/meteor/tests/e2e/e2e-encryption/e2ee-file-encryption.spec.ts">
<violation number="1" location="apps/meteor/tests/e2e/e2e-encryption/e2ee-file-encryption.spec.ts:135">
P2: Capture the composer files after `dragAndDropTxtFile()` so the readonly assertion actually checks the newly added (blocked) file.</violation>
</file>
<file name="apps/meteor/tests/e2e/page-objects/fragments/composer.ts">
<violation number="1" location="apps/meteor/tests/e2e/page-objects/fragments/composer.ts:33">
P3: Use `exact: true` for this name-based button locator to avoid matching multiple files with similar names.</violation>
</file>
<file name="apps/meteor/client/lib/chats/flows/uploadFiles.ts">
<violation number="1" location="apps/meteor/client/lib/chats/flows/uploadFiles.ts:67">
P2: The `performContinuously('uploading')` action is started unconditionally but is never stopped in this file. If all file encryptions fail (returning `null`), no uploads are added to the store, so `processMessageUploads` exits early without calling `action.stop('uploading')`, leaving the typing/activity indicator stuck on 'uploading'. Move the `stop` call into a `finally`-like position after `Promise.allSettled` to cover this path.</violation>
</file>
<file name="apps/meteor/client/lib/chats/flows/sendMessage.ts">
<violation number="1" location="apps/meteor/client/lib/chats/flows/sendMessage.ts:31">
P2: File upload messages now bypass `onClientBeforeSendMessage`, so E2E mention parsing never runs for upload captions. Mentions in encrypted rooms won’t be detected/notify when the message includes files.</violation>
</file>
<file name="apps/meteor/app/api/server/v1/rooms.ts">
<violation number="1" location="apps/meteor/app/api/server/v1/rooms.ts:294">
P2: `fileName`/`fileContent` are only removed when truthy, so falsy values can leak into `msgData` and fail `sendFileMessage` validation.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
apps/meteor/client/views/room/composer/messageBox/MessageComposerFileLoader.tsx
Outdated
Show resolved
Hide resolved
apps/meteor/client/views/room/body/UploadProgress/UploadProgressIndicator.tsx
Show resolved
Hide resolved
apps/meteor/tests/e2e/e2e-encryption/e2ee-file-encryption.spec.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 19
🧹 Nitpick comments (11)
apps/meteor/tests/e2e/e2e-encryption/e2ee-file-encryption.spec.ts (1)
71-71: Consider extracting the encryption icon locator to the Page Object.The CSS selector
.rcx-icon--name-keyis used multiple times throughout the tests (lines 71, 108, 125, 140, 160, 171). As per coding guidelines, commonly used locators should be stored in variables/constants for reuse, and the Page Object Model pattern should be followed consistently.Consider adding a reusable locator to the page object (e.g.,
poHomeChannel.content.encryptionIconor similar) to improve maintainability. If the icon has an accessible attribute likearia-labelortitle, prefer usinggetByLabel()orgetByTitle()for a more semantic approach.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/tests/e2e/e2e-encryption/e2ee-file-encryption.spec.ts` at line 71, Extract the repeated CSS selector into the HomeChannel page object: add a locator property (e.g., poHomeChannel.content.encryptionIcon) that wraps the current selector ('.rcx-icon--name-key') and replace all inline uses (lines referencing poHomeChannel.content.locator('.rcx-icon--name-key')) with poHomeChannel.content.encryptionIcon; if the DOM exposes a semantic attribute like aria-label or title, prefer implementing the locator using getByLabel()/getByTitle() in the new property instead of the raw CSS selector.apps/meteor/tests/e2e/page-objects/fragments/modals/file-upload-modal.ts (1)
33-35: Add anOkaction method instead of exposing the raw button.
btnOkis the only dismissal path here that callers can trigger withoutwaitForDismissal(). That makes this modal API inconsistent withcancel()andconfirmSend()and can introduce flaky tests when the dialog closes asynchronously. Prefer a private locator plus a helper likeconfirm().♻️ Proposed refactor
- get btnOk() { - return this.root.getByRole('button', { name: 'Ok' }); - } + private get okButton() { + return this.root.getByRole('button', { name: 'Ok' }); + } get btnSendAnyway() { return this.root.getByRole('button', { name: 'Send anyway' }); } @@ async cancel() { await this.btnCancel.click(); await this.waitForDismissal(); } + async confirm() { + await this.okButton.click(); + await this.waitForDismissal(); + } + async confirmSend() { await this.btnSendAnyway.click(); await this.waitForDismissal(); }As per coding guidelines, "Follow Page Object Model pattern consistently in Playwright tests."
Also applies to: 45-57
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/tests/e2e/page-objects/fragments/modals/file-upload-modal.ts` around lines 33 - 35, Replace the public raw locator btnOk with a private locator (e.g., _btnOk or btnOkLocator) and add a public action method confirm() that clicks the private locator and waits for the modal to dismiss using the existing waitForDismissal() helper; mirror the same pattern used by cancel() and confirmSend() so callers use confirm() instead of accessing the locator directly (also update the other similar locators/actions in the same file around the 45-57 area to follow this private-locator + action-method pattern).apps/meteor/client/views/room/composer/messageBox/MessageComposerFileLoader.tsx (1)
5-5: Remove the inline TODO from the component.Please move this note to the issue tracker or a follow-up task; implementation files should stay comment-free.
As per coding guidelines, "Avoid code comments in the implementation".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/client/views/room/composer/messageBox/MessageComposerFileLoader.tsx` at line 5, Remove the inline TODO comment at the top of MessageComposerFileLoader.tsx (the "// TODO: This component should be moved to fuselage" note) and instead create a corresponding ticket or follow-up task in the issue tracker referencing MessageComposerFileLoader so the migration can be tracked; leave the implementation file free of TODO comments per the coding guidelines.apps/meteor/client/views/room/webdav/WebdavFilePickerModal/WebdavFilePickerModal.tsx (1)
131-147: Consider removing unuseddescriptionparameter.The
uploadFilefunction accepts adescriptionparameter (line 131) that is never used since the call at line 147 doesn't pass it. If file descriptions are no longer supported in this flow, consider cleaning up:🧹 Proposed cleanup
- const uploadFile = async (file: File, description?: string): Promise<void> => { + const uploadFile = async (file: File): Promise<void> => { try { - await onUpload?.(file, description); + await onUpload?.(file); } catch (error) {And update the prop type if
descriptionis no longer needed:type WebdavFilePickerModalProps = { - onUpload: (file: File, description?: string) => Promise<void>; + onUpload: (file: File) => Promise<void>;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/client/views/room/webdav/WebdavFilePickerModal/WebdavFilePickerModal.tsx` around lines 131 - 147, The uploadFile function declares an unused description parameter; remove the description argument from uploadFile's signature and any related prop/type references (e.g., the uploadFile function in WebdavFilePickerModal.tsx and the onUpload prop type) so the function only accepts (file: File): Promise<void>, and update any related type/interface definitions and callers (such as the onUpload prop type) to reflect that descriptions are no longer passed in this flow.apps/meteor/client/lib/chats/uploads.ts (1)
145-166: Consider handling non-200/400 status codes more explicitly.The
onloadhandler currently treats any status other than 200 or 400 as a generic error at line 163. Consider logging or including the actual HTTP status code in the error message for easier debugging of unexpected server responses.💡 Proposed enhancement for error clarity
if (xhr.status === 200) { const result = JSON.parse(xhr.responseText); this.updateUpload(id, { id: result.file._id, url: result.file.url }); return; } - this.updateUpload(id, { percentage: 0, error: new Error(i18n.t('FileUpload_Error')) }); + this.updateUpload(id, { percentage: 0, error: new Error(i18n.t('FileUpload_Error') + ` (status: ${xhr.status})`) });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/client/lib/chats/uploads.ts` around lines 145 - 166, The onload handler in the XHR upload flow (xhr.onload) treats all non-200/400 responses as a generic i18n error; update it to include the HTTP status and response body in the error passed to updateUpload to aid debugging. Concretely, inside xhr.onload (the same block that handles xhr.status === 400 and === 200), construct a descriptive error message (e.g., include xhr.status and xhr.responseText or attempt to JSON.parse it) and call this.updateUpload(id, { percentage: 0, error: new Error(<constructed message>) }); optionally also log the full xhr object or the constructed message before calling updateUpload; reuse getErrorMessage for thrown exceptions as currently done. Ensure you only change the non-200/400 branch and keep existing behavior for 200 and 400 cases.apps/meteor/tests/e2e/message-composer.spec.ts (1)
197-197: Avoid hardcoded timeout; use a specific wait condition instead.The coding guidelines specify using
page.waitFor()with specific conditions rather thanpage.waitForTimeout(). Consider waiting for a specific audio recording state or duration indicator.♻️ Suggested improvement
- await page.waitForTimeout(1000); + await poHomeChannel.audioRecorder.getByRole('button', { name: 'Finish Recording', exact: true }).waitFor({ state: 'visible' });Alternatively, if the recorder needs time to capture audio, consider waiting for a recording duration indicator or state change.
As per coding guidelines: "Use
page.waitFor()with specific conditions instead of hardcoded timeouts in Playwright tests".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/tests/e2e/message-composer.spec.ts` at line 197, Replace the hardcoded sleep call page.waitForTimeout(1000) with an explicit wait for a specific UI/state change (e.g., wait for a recording indicator or duration element to appear/update) by using Playwright's page.waitFor or locator.waitFor with a condition; locate the test that calls page.waitForTimeout(1000) and change it to await page.waitForFunction(() => /* check recorder state or duration value */) or await locator('selector-for-recording-indicator').waitFor({ state: 'visible' }) so the test waits for the recorder's actual state/duration instead of a fixed timeout.apps/meteor/client/views/room/contextualBar/Threads/components/ThreadChat.tsx (1)
56-62: Optional chaining is redundant after the runtime guard.Since the component throws at lines 25-27 if
chatis undefined, the optional chaining (chat?.messageEditing) in these callbacks is unnecessary.♻️ Suggested simplification
const handleNavigateToPreviousMessage = useCallback((): void => { - chat?.messageEditing.toPreviousMessage(); + chat.messageEditing.toPreviousMessage(); }, [chat?.messageEditing]); const handleNavigateToNextMessage = useCallback((): void => { - chat?.messageEditing.toNextMessage(); + chat.messageEditing.toNextMessage(); }, [chat?.messageEditing]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/client/views/room/contextualBar/Threads/components/ThreadChat.tsx` around lines 56 - 62, The optional chaining in handleNavigateToPreviousMessage and handleNavigateToNextMessage is redundant because chat is guaranteed by the runtime guard earlier; update both callbacks to call chat.messageEditing.toPreviousMessage() and chat.messageEditing.toNextMessage() respectively (remove the "?"), and use the exact referenced value in the dependency arrays (e.g. [chat.messageEditing]) so React hooks depend on the real object.apps/meteor/client/views/room/composer/messageBox/MessageComposerFileComponent.tsx (1)
14-14: Consider tracking this TODO externally.As per coding guidelines, code comments should be avoided in implementations. Consider creating an issue to track moving this component to
ui-composerrather than leaving a TODO in the code.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/client/views/room/composer/messageBox/MessageComposerFileComponent.tsx` at line 14, Remove the inline TODO comment in MessageComposerFileComponent and instead create and reference an external task in your issue tracker (e.g., an issue/board card to move this component into ui-composer); update the component (MessageComposerFileComponent) to have no implementation comments and, if helpful, add a short single-line TODO reference to the created issue ID (or include the issue URL) in the commit message so the work is tracked externally rather than leaving a code comment.apps/meteor/client/views/room/composer/messageBox/MessageComposerFileError.tsx (1)
18-18: Redundant Boolean coercion.Since
erroris typed asError(not optional),Boolean(error)will always betrue. Consider simplifying toerror={true}or making theerrorprop optional in the type definition if the component should support non-error states.Proposed fix
- error={Boolean(error)} + error🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/client/views/room/composer/messageBox/MessageComposerFileError.tsx` at line 18, The prop expression error={Boolean(error)} is redundant because the local symbol error is typed as non-optional Error; replace the coercion with a literal true (error={true}) when the component always receives an Error, or instead make the component prop optional in the MessageComposerFileError props type (e.g., change Error -> Error | undefined or mark error?: Error) if you intend to support no-error states and keep the Boolean(...) check; update the component's props interface (and any call sites using MessageComposerFileError) accordingly.apps/meteor/client/views/room/body/RoomBody.tsx (1)
203-206: Remove the commented-out wrapper markers.These JSX comments are dead scaffolding now. If
UploadProgressContaineris still needed, keep it as a real component; otherwise collapse this back to a single-line conditional render.As per coding guidelines, `**/*.{ts,tsx,js}`: Avoid code comments in the implementation.🧹 Suggested cleanup
- {isUploading && ( - // <UploadProgressContainer> - <UploadProgressIndicator uploads={uploads} /> - // </UploadProgressContainer> - )} + {isUploading && <UploadProgressIndicator uploads={uploads} />}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/client/views/room/body/RoomBody.tsx` around lines 203 - 206, Remove the commented JSX wrapper markers around the upload indicator: delete the commented lines "// <UploadProgressContainer>" and "// </UploadProgressContainer>" so the conditional render uses a single line with the existing isUploading check and UploadProgressIndicator; if you intended to keep a container, replace the comments with the actual <UploadProgressContainer> wrapper around <UploadProgressIndicator uploads={uploads} /> instead of commented scaffolding.apps/meteor/tests/e2e/page-objects/fragments/home-content.ts (1)
393-393: Keep the new upload selectors behind page-object getters.The overlay selector and the room/thread file-input selectors are now duplicated inline across helpers. Moving them into dedicated locators on
HomeContent/RoomComposer/ThreadComposerkeeps selector churn local and preserves the page-object boundary for the new upload flow.As per coding guidelines,
apps/meteor/tests/e2e/page-objects/**/*.ts: Utilize existing page objects pattern fromapps/meteor/tests/e2e/page-objects/;apps/meteor/tests/e2e/**/*.{ts,spec.ts}: Store commonly used locators in variables/constants for reuse.Also applies to: 412-412, 420-429
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/tests/e2e/page-objects/fragments/home-content.ts` at line 393, The inline overlay selector '[role=dialog][data-qa="DropTargetOverlay"]' and the room/thread file-input selectors are duplicated across helpers; move these into page-object getters on HomeContent (e.g., add a dropTargetOverlay getter) and into RoomComposer/ThreadComposer (e.g., fileInput getter) and update all usages (including the dispatchEvent('drop', { dataTransfer }) call and occurrences around lines 412 and 420–429) to use those getters so selectors are centralized and the page-object boundary is preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/meteor/app/api/server/v1/rooms.ts`:
- Around line 294-296: You update file.name from this.bodyParams.fileName but do
not update the stored/upload path or download name used later by
sendFileMessage(), which causes metadata/path divergence; before calling
sendFileMessage() (and after setting file.name and deleting
this.bodyParams.fileName) recompute and set the attachment’s upload/download
path using the same derivation used for the rooms.media/:rid upload (i.e., the
logic that created the original path from the original file name) and assign it
to the same field the message-sender uses (e.g., the
file.path/downloadName/attachment path) so the stored path matches the new
file.name.
In `@apps/meteor/client/lib/chats/flows/processMessageUploads.ts`:
- Around line 112-119: The code currently attaches composedMessage (currentMsg)
only to the first file (currentMsg = upload === filesToUpload[0]) which causes
one-message-per-file; change the logic to attach the composedMessage only once
for the whole batch by setting currentMsg for the last upload (e.g., upload ===
filesToUpload[filesToUpload.length - 1]) so the confirmation sent via
confirmFilesQueue (and the equivalent branch around lines 141-144) includes the
message only on that single confirm entry; update both occurrences (currentMsg
assignment and the confirmFilesQueue push) so only one /rooms.mediaConfirm entry
carries composedMessage containing tmid/msg/fileName while other file
confirmations omit composedMessage.
- Around line 182-206: The modal's cancel/close and the all-uploads-failed
acknowledgment currently call resolve(true) which signals success and causes the
caller to clear the composer, losing the draft; update the callbacks in
processMessageUploads (the imperativeModal handlers: the allUploadsFailed
branch, the non-failed onCancel, and onClose) to resolve(false) instead of
resolve(true) so that cancel/close/acknowledge do not indicate the message was
sent, and keep the confirm/onConfirm path unchanged (onConfirm should still call
resolve(continueSendingMessage(...))) so only an actual send clears the
composer.
In `@apps/meteor/client/lib/chats/flows/uploadFiles.ts`:
- Around line 69-70: The current code calls
chat.composer?.dismissAllQuotedMessages() immediately after awaiting uploads,
which clears the quote chain when the user merely attaches files; remove or
defer that call so attaching files doesn't wipe quotes. Specifically, in
uploadFiles.ts stop calling chat.composer?.dismissAllQuotedMessages() after
Promise.allSettled(files.map(file => uploadFile(file))) and instead invoke
chat.composer?.dismissAllQuotedMessages() from the message-send flow (the method
that finalizes/sends the message) or on successful send confirmation so quotes
are only cleared when the message is actually sent.
- Around line 28-33: The current branch calls uploadsStore.send(file) when
e2eRoom is not ready or settings.peek('E2E_Enable_Encrypt_Files') is false,
which downgrades encrypted-room uploads to plaintext; instead, do NOT call
uploadsStore.send(file) in this branch — abandon the entire upload queue for
this batch, surface a visible toast error (e.g. toasts/error or similar UI
helper) indicating encryption is required/not ready, and ensure the upload queue
is cleared/cancelled so no files are uploaded partially; change the logic around
e2eRoom?.isReady() and settings.peek('E2E_Enable_Encrypt_Files') to trigger the
abort+toast flow rather than falling back to plaintext.
In
`@apps/meteor/client/views/room/body/UploadProgress/UploadProgressIndicator.tsx`:
- Around line 16-29: The computed percentage uses validUploads (which includes
completed uploads) while count uses activeUploads, causing mismatch; change the
avgPercentage calculation inside the useMemo to derive from activeUploads (the
same set used for count) instead of validUploads: compute totalPercentage by
summing upload.percentage over activeUploads and then average/round it to
produce percentage, while keeping the early-return and count =
activeUploads.length logic in the useMemo (referencing useMemo, validUploads,
activeUploads, avgPercentage, percentage, count).
In `@apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx`:
- Around line 389-390: canSendUploads currently only affects the toolbar button
but the textarea Enter key handler still allows submitting while uploads are in
progress and processMessageUploads drops attachments without id/url; update the
textarea submit handler (the onKeyDown / submit handler that reads
canSendUploads) to check the same guard (use canSendUploads or isUploading) and
prevent Enter-submission when uploads are pending, and modify
processMessageUploads to not clear the upload store or mutate attachments when
any attachment is missing id/url (either defer clearing until uploads complete
or preserve pending attachments and surface an error), ensuring unfinished
attachments are neither dropped nor sent.
In
`@apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/MessageBoxActionsToolbar.tsx`:
- Line 64: useWebdavActions is currently called unconditionally allowing WebDAV
uploads to bypass the message edit-mode guard; change the hook to accept the
edit state and return a no-op/disabled actions object when editing. Concretely,
update useWebdavActions to take a parameter (e.g. isEditing: boolean) and, when
isEditing is true, return an object where fileUploadAction and any other upload
handlers are disabled or no-op; then call it from MessageBoxActionsToolbar as
const webdavActions = useWebdavActions(uploadsStore, isEditing) so WebDAV
pathways respect the same isEditing restriction enforced for fileUploadAction.
- Line 63: The fileUploadAction is being disabled while the user is typing
because the condition passed to useFileUploadAction includes "typing"; update
the call in MessageBoxActionsToolbar to remove "typing" from the disabled
expression so the attachment button remains enabled when the draft has text
(i.e., change useFileUploadAction(!canSend || typing || isRecording ||
isEditing, uploadsStore) to useFileUploadAction(!canSend || isRecording ||
isEditing, uploadsStore)). Ensure you update the call site referencing
fileUploadAction/useFileUploadAction only and run related UI checks.
In `@apps/meteor/client/views/room/composer/messageBox/MessageComposerFile.tsx`:
- Around line 52-57: The cancel button is currently only rendered when isHover
is true, making it inaccessible to keyboard and touch users; update the logic in
the actionIcon rendering (and the similar branch around the other occurrence) so
that when isLoading you still render an accessible IconButton wired to
handleDismiss (and keep MessageComposerFileLoader for visual loading if
desired), i.e., do not gate the IconButton behind isHover — render the
IconButton during isLoading so it is focusable and clickable by keyboard/touch,
ensure it keeps the existing aria-label and onClick handler.
In
`@apps/meteor/client/views/room/composer/messageBox/MessageComposerFileLoader.tsx`:
- Line 22: The SVG's spinner class is being overwritten because {...props} is
spread after className={customCSS} in the Box element; update
MessageComposerFileLoader to merge class names instead of letting caller props
replace them—either spread {...props} before the fixed props or compute a
combined className (e.g., combine customCSS with props.className) and pass that
merged value into the Box's className so the spinner animation from customCSS is
preserved while allowing caller className to be appended.
In `@apps/meteor/tests/e2e/e2e-encryption/e2ee-encryption-decryption.spec.ts`:
- Around line 101-106: The test wraps non-idempotent UI actions in a retrying
expect(...).toPass(), causing duplicate submissions; change the flow in the
e2ee-encryption-decryption.spec.ts to remove the toPass wrapper around the
sequence using encryptedRoomPage.composer.getFileByName(...).click(),
fileUploadModal.setName(...), and fileUploadModal.update(); instead, wait for
the pending upload chip (the pending upload element exposed by
encryptedRoomPage/composer) to appear, perform the rename and update exactly
once, then assert the final state with
expect(encryptedRoomPage.composer.getFileByName(fileName)).toBeVisible(); apply
the same fix to the similar block referenced at lines ~124-129.
In `@apps/meteor/tests/e2e/e2e-encryption/e2ee-file-encryption.spec.ts`:
- Around line 134-141: The test captures composerFiles before performing the
upload, so the subsequent readonly assertions check stale elements; move the
call to compose file lookup (composerFilesLocator and composerFiles) to after
invoking poHomeChannel.content.dragAndDropTxtFile() and then query the new
file(s) (e.g., by composerFilesLocator.all() or locating TEST_FILE_TXT) and
assert they have the readonly attribute and that
poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key') is visible;
in short, perform dragAndDropTxtFile() first, then re-query
composerFiles/composerFilesLocator and run the expectations against those fresh
elements.
In `@apps/meteor/tests/e2e/file-upload.spec.ts`:
- Around line 121-130: The negative assertion is checking the wrong filename;
after sending ten number1.png files and then calling
poHomeChannel.content.dragAndDropTxtFile() the test should assert that the TXT
file is not present. Update the test (the 'should respect the maximum number of
files allowed per message: 10' case) to look for the TXT filename produced by
poHomeChannel.content.dragAndDropTxtFile() (e.g., 'any_file.txt') when calling
poHomeChannel.composer.getFileByName(...) and assert it is not visible while
still asserting poHomeChannel.composer.getFilesInComposer() has count 10.
In `@apps/meteor/tests/e2e/fixtures/responses/mediaResponse.ts`:
- Around line 3-6: The current isMediaResponse matcher is too broad and matches
mediaConfirm requests; update isMediaResponse to only match the upload endpoint
by making the URL regex anchored to the end of the path (and allow optional
query string) e.g. test against a pattern like
/^.*\/api\/v1\/rooms\.media(?:\?.*)?$/ while still checking
response.request().method() === 'POST'; this ensures
createMediaResponsePromise(page) waits for the actual upload response rather
than a mediaConfirm call.
In `@apps/meteor/tests/e2e/image-upload.spec.ts`:
- Line 40: The test currently only asserts the composer entry has a readonly
attribute via poHomeChannel.composer.getFileByName('bad-orientation'), so update
the assertion to first assert the upload error UI is visible using the existing
page-object locator (statusUploadError) — e.g.,
expect(poHomeChannel.statusUploadError).toBeVisible() — and keep the readonly
check as a secondary assertion if desired; locate these in the same test that
references poHomeChannel.composer.getFileByName('bad-orientation') and
replace/augment the single readonly-only expectation with the visible error
assertion.
- Around line 52-55: Replace the loose filename text assertion with an assertion
that the uploaded attachment element is present by using HomeContent's
getLastMessageByFileName helper: after calling
poHomeChannel.content.sendFileMessage(imgName) and
poHomeChannel.composer.btnSend.click(), call
poHomeChannel.content.getLastMessageByFileName(imgName) and assert
visibility/contents with a web-first matcher (e.g., toBeVisible or toHaveText)
instead of checking
poHomeChannel.content.lastUserMessage.toContainText(imgName); this ensures the
test verifies the actual file attachment rendering.
In `@apps/meteor/tests/e2e/page-objects/fragments/home-content.ts`:
- Line 399: The three methods dragAndDropTxtFileToThread, sendFileMessage, and
sendFileMessageToThread destructure an options object without a TypeScript type;
update each signature to annotate the destructured parameter (e.g. change "({
waitForResponse = true } = {})" to include a type like "{ waitForResponse?:
boolean }" so it becomes "({ waitForResponse = true }: { waitForResponse?:
boolean } = {})") to satisfy strict TS mode; apply the same pattern for any
other destructured option parameters (use optional boolean types matching the
default values).
In `@apps/meteor/tests/e2e/prune-messages.spec.ts`:
- Around line 44-46: The click on poHomeChannel.composer.btnSend races with the
attachment preview because sendFileMessage only waits for the HTTP response; add
a UI-level wait like await
expect(content.getFileByName('any_file.txt')).toBeVisible() (or equivalent
page-object method) immediately after calling
content.sendFileMessage('any_file.txt') and before invoking
poHomeChannel.composer.btnSend.click(); alternatively, implement a helper on the
page object (e.g., composer.attachAndSendFile(fileName)) that performs
sendFileMessage, waits for getFileByName(fileName) toBeVisible(), then clicks
btnSend, and replace the three occurrences that currently call sendFileMessage +
btnSend.click() with this helper.
---
Nitpick comments:
In `@apps/meteor/client/lib/chats/uploads.ts`:
- Around line 145-166: The onload handler in the XHR upload flow (xhr.onload)
treats all non-200/400 responses as a generic i18n error; update it to include
the HTTP status and response body in the error passed to updateUpload to aid
debugging. Concretely, inside xhr.onload (the same block that handles xhr.status
=== 400 and === 200), construct a descriptive error message (e.g., include
xhr.status and xhr.responseText or attempt to JSON.parse it) and call
this.updateUpload(id, { percentage: 0, error: new Error(<constructed message>)
}); optionally also log the full xhr object or the constructed message before
calling updateUpload; reuse getErrorMessage for thrown exceptions as currently
done. Ensure you only change the non-200/400 branch and keep existing behavior
for 200 and 400 cases.
In `@apps/meteor/client/views/room/body/RoomBody.tsx`:
- Around line 203-206: Remove the commented JSX wrapper markers around the
upload indicator: delete the commented lines "// <UploadProgressContainer>" and
"// </UploadProgressContainer>" so the conditional render uses a single line
with the existing isUploading check and UploadProgressIndicator; if you intended
to keep a container, replace the comments with the actual
<UploadProgressContainer> wrapper around <UploadProgressIndicator
uploads={uploads} /> instead of commented scaffolding.
In
`@apps/meteor/client/views/room/composer/messageBox/MessageComposerFileComponent.tsx`:
- Line 14: Remove the inline TODO comment in MessageComposerFileComponent and
instead create and reference an external task in your issue tracker (e.g., an
issue/board card to move this component into ui-composer); update the component
(MessageComposerFileComponent) to have no implementation comments and, if
helpful, add a short single-line TODO reference to the created issue ID (or
include the issue URL) in the commit message so the work is tracked externally
rather than leaving a code comment.
In
`@apps/meteor/client/views/room/composer/messageBox/MessageComposerFileError.tsx`:
- Line 18: The prop expression error={Boolean(error)} is redundant because the
local symbol error is typed as non-optional Error; replace the coercion with a
literal true (error={true}) when the component always receives an Error, or
instead make the component prop optional in the MessageComposerFileError props
type (e.g., change Error -> Error | undefined or mark error?: Error) if you
intend to support no-error states and keep the Boolean(...) check; update the
component's props interface (and any call sites using MessageComposerFileError)
accordingly.
In
`@apps/meteor/client/views/room/composer/messageBox/MessageComposerFileLoader.tsx`:
- Line 5: Remove the inline TODO comment at the top of
MessageComposerFileLoader.tsx (the "// TODO: This component should be moved to
fuselage" note) and instead create a corresponding ticket or follow-up task in
the issue tracker referencing MessageComposerFileLoader so the migration can be
tracked; leave the implementation file free of TODO comments per the coding
guidelines.
In
`@apps/meteor/client/views/room/contextualBar/Threads/components/ThreadChat.tsx`:
- Around line 56-62: The optional chaining in handleNavigateToPreviousMessage
and handleNavigateToNextMessage is redundant because chat is guaranteed by the
runtime guard earlier; update both callbacks to call
chat.messageEditing.toPreviousMessage() and chat.messageEditing.toNextMessage()
respectively (remove the "?"), and use the exact referenced value in the
dependency arrays (e.g. [chat.messageEditing]) so React hooks depend on the real
object.
In
`@apps/meteor/client/views/room/webdav/WebdavFilePickerModal/WebdavFilePickerModal.tsx`:
- Around line 131-147: The uploadFile function declares an unused description
parameter; remove the description argument from uploadFile's signature and any
related prop/type references (e.g., the uploadFile function in
WebdavFilePickerModal.tsx and the onUpload prop type) so the function only
accepts (file: File): Promise<void>, and update any related type/interface
definitions and callers (such as the onUpload prop type) to reflect that
descriptions are no longer passed in this flow.
In `@apps/meteor/tests/e2e/e2e-encryption/e2ee-file-encryption.spec.ts`:
- Line 71: Extract the repeated CSS selector into the HomeChannel page object:
add a locator property (e.g., poHomeChannel.content.encryptionIcon) that wraps
the current selector ('.rcx-icon--name-key') and replace all inline uses (lines
referencing poHomeChannel.content.locator('.rcx-icon--name-key')) with
poHomeChannel.content.encryptionIcon; if the DOM exposes a semantic attribute
like aria-label or title, prefer implementing the locator using
getByLabel()/getByTitle() in the new property instead of the raw CSS selector.
In `@apps/meteor/tests/e2e/message-composer.spec.ts`:
- Line 197: Replace the hardcoded sleep call page.waitForTimeout(1000) with an
explicit wait for a specific UI/state change (e.g., wait for a recording
indicator or duration element to appear/update) by using Playwright's
page.waitFor or locator.waitFor with a condition; locate the test that calls
page.waitForTimeout(1000) and change it to await page.waitForFunction(() => /*
check recorder state or duration value */) or await
locator('selector-for-recording-indicator').waitFor({ state: 'visible' }) so the
test waits for the recorder's actual state/duration instead of a fixed timeout.
In `@apps/meteor/tests/e2e/page-objects/fragments/home-content.ts`:
- Line 393: The inline overlay selector
'[role=dialog][data-qa="DropTargetOverlay"]' and the room/thread file-input
selectors are duplicated across helpers; move these into page-object getters on
HomeContent (e.g., add a dropTargetOverlay getter) and into
RoomComposer/ThreadComposer (e.g., fileInput getter) and update all usages
(including the dispatchEvent('drop', { dataTransfer }) call and occurrences
around lines 412 and 420–429) to use those getters so selectors are centralized
and the page-object boundary is preserved.
In `@apps/meteor/tests/e2e/page-objects/fragments/modals/file-upload-modal.ts`:
- Around line 33-35: Replace the public raw locator btnOk with a private locator
(e.g., _btnOk or btnOkLocator) and add a public action method confirm() that
clicks the private locator and waits for the modal to dismiss using the existing
waitForDismissal() helper; mirror the same pattern used by cancel() and
confirmSend() so callers use confirm() instead of accessing the locator directly
(also update the other similar locators/actions in the same file around the
45-57 area to follow this private-locator + action-method pattern).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c6b58cd6-b479-488b-be9d-11cc4d294f2a
⛔ Files ignored due to path filters (1)
apps/meteor/client/views/room/modals/FileUploadModal/__snapshots__/FileUploadModal.spec.tsx.snapis excluded by!**/*.snap
📒 Files selected for processing (53)
apps/meteor/app/api/server/v1/rooms.tsapps/meteor/app/ui/client/lib/ChatMessages.tsapps/meteor/client/lib/chats/ChatAPI.tsapps/meteor/client/lib/chats/Upload.tsapps/meteor/client/lib/chats/flows/processMessageUploads.tsapps/meteor/client/lib/chats/flows/processTooLongMessage.tsapps/meteor/client/lib/chats/flows/sendMessage.tsapps/meteor/client/lib/chats/flows/uploadFiles.tsapps/meteor/client/lib/chats/uploads.tsapps/meteor/client/views/composer/AudioMessageRecorder/AudioMessageRecorder.tsxapps/meteor/client/views/composer/VideoMessageRecorder/VideoMessageRecorder.tsxapps/meteor/client/views/room/body/RoomBody.tsxapps/meteor/client/views/room/body/UploadProgress/UploadProgressContainer.tsxapps/meteor/client/views/room/body/UploadProgress/UploadProgressIndicator.tsxapps/meteor/client/views/room/body/hooks/useFileUpload.tsapps/meteor/client/views/room/body/hooks/useFileUploadDropTarget.tsapps/meteor/client/views/room/composer/ComposerMessage.tsxapps/meteor/client/views/room/composer/messageBox/MessageBox.tsxapps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/MessageBoxActionsToolbar.tsxapps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useFileUploadAction.tsapps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useWebdavActions.tsxapps/meteor/client/views/room/composer/messageBox/MessageComposerFile.tsxapps/meteor/client/views/room/composer/messageBox/MessageComposerFileArea.tsxapps/meteor/client/views/room/composer/messageBox/MessageComposerFileComponent.tsxapps/meteor/client/views/room/composer/messageBox/MessageComposerFileError.tsxapps/meteor/client/views/room/composer/messageBox/MessageComposerFileLoader.tsxapps/meteor/client/views/room/contextualBar/Threads/components/ThreadChat.tsxapps/meteor/client/views/room/modals/FileUploadModal/FileUploadModal.spec.tsxapps/meteor/client/views/room/modals/FileUploadModal/FileUploadModal.stories.tsxapps/meteor/client/views/room/modals/FileUploadModal/FileUploadModal.tsxapps/meteor/client/views/room/webdav/WebdavFilePickerModal/WebdavFilePickerModal.tsxapps/meteor/lib/constants.tsapps/meteor/tests/e2e/e2e-encryption/e2ee-encryption-decryption.spec.tsapps/meteor/tests/e2e/e2e-encryption/e2ee-file-encryption.spec.tsapps/meteor/tests/e2e/file-upload.spec.tsapps/meteor/tests/e2e/files-management.spec.tsapps/meteor/tests/e2e/fixtures/files/another_file.txtapps/meteor/tests/e2e/fixtures/files/empty_file.txtapps/meteor/tests/e2e/fixtures/responses/mediaResponse.tsapps/meteor/tests/e2e/image-gallery.spec.tsapps/meteor/tests/e2e/image-upload.spec.tsapps/meteor/tests/e2e/message-actions.spec.tsapps/meteor/tests/e2e/message-composer.spec.tsapps/meteor/tests/e2e/page-objects/fragments/composer.tsapps/meteor/tests/e2e/page-objects/fragments/home-content.tsapps/meteor/tests/e2e/page-objects/fragments/modals/file-upload-modal.tsapps/meteor/tests/e2e/page-objects/home-channel.tsapps/meteor/tests/e2e/prune-messages.spec.tsapps/meteor/tests/e2e/quote-attachment.spec.tsapps/meteor/tests/e2e/threads.spec.tspackages/core-typings/src/IUpload.tspackages/i18n/src/locales/en.i18n.jsonpackages/rest-typings/src/v1/rooms.ts
💤 Files with no reviewable changes (2)
- apps/meteor/client/views/room/modals/FileUploadModal/FileUploadModal.stories.tsx
- apps/meteor/tests/e2e/page-objects/home-channel.ts
There was a problem hiding this comment.
17 issues found across 122 files
Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/meteor/client/views/room/body/RoomBody.tsx">
<violation number="1" location="apps/meteor/client/views/room/body/RoomBody.tsx:203">
P2: Keep the upload indicator visible until the uploads store is cleared; `isUploading` turns false before attachment confirmation finishes.</violation>
</file>
<file name="apps/meteor/client/lib/chats/flows/processMessageEditing.ts">
<violation number="1" location="apps/meteor/client/lib/chats/flows/processMessageEditing.ts:26">
P2: Don't clear the composer until `updateMessage` succeeds, or failed edits will discard the user's current text.</violation>
</file>
<file name="apps/meteor/client/lib/chats/flows/processSlashCommand.ts">
<violation number="1" location="apps/meteor/client/lib/chats/flows/processSlashCommand.ts:94">
P2: Clear the composer only after the slash command succeeds; clearing it before the request loses the user's command text on errors.</violation>
</file>
<file name="apps/meteor/client/lib/chats/flows/sendMessage.ts">
<violation number="1" location="apps/meteor/client/lib/chats/flows/sendMessage.ts:32">
P2: Clearing the composer before `sdk.call('sendMessage', ...)` causes the user's message text to be lost if the send request fails. Move `clear()` after the await so text is only discarded on success.</violation>
</file>
<file name="apps/meteor/client/views/room/composer/messageBox/MessageComposerFileItem.tsx">
<violation number="1" location="apps/meteor/client/views/room/composer/messageBox/MessageComposerFileItem.tsx:28">
P3: Use the extension helper here; `getMimeType` makes the subtitle show MIME types like `application/pdf` instead of file extensions.</violation>
</file>
<file name="apps/meteor/app/ui/client/lib/ChatMessages.ts">
<violation number="1" location="apps/meteor/app/ui/client/lib/ChatMessages.ts:127">
P1: Clear the active upload store only after the edit is allowed to start. This line currently deletes queued files on failed edit attempts, and in thread context it can clear the wrong store when editing the thread root message.</violation>
</file>
<file name="apps/meteor/client/lib/chats/flows/processSetReaction.ts">
<violation number="1" location="apps/meteor/client/lib/chats/flows/processSetReaction.ts:24">
P2: Clear the composer only after `setReaction` succeeds; otherwise a failed request discards the user's input.</violation>
</file>
<file name="apps/meteor/client/lib/chats/flows/uploadFiles.ts">
<violation number="1" location="apps/meteor/client/lib/chats/flows/uploadFiles.ts:74">
P2: `performContinuously('uploading')` is started unconditionally but is only stopped inside `processMessageUploads`, which runs when the user sends the message. If every file fails during encryption (or `files` is empty), no entries are added to the uploads store, the user never triggers a send, and the 'uploading' status indicator is never cleared.
Wrap the upload batch in a try/finally that stops the action when no files were successfully staged.</violation>
</file>
<file name="apps/meteor/client/lib/chats/flows/processMessageUploads.ts">
<violation number="1" location="apps/meteor/client/lib/chats/flows/processMessageUploads.ts:60">
P2: `URL.createObjectURL()` is called but never revoked, leaking the blob URL. Store the URL, use it, then call `URL.revokeObjectURL()` after the image loads.</violation>
<violation number="2" location="apps/meteor/client/lib/chats/flows/processMessageUploads.ts:189">
P1: Promise never resolves when the user closes the modal, clicks Cancel, or clicks OK on an all-failed dialog. Each of these paths calls `imperativeModal.close()` but never calls `resolve()`, leaving the caller permanently awaiting. Add `resolve(true)` (or `resolve(false)`) in every exit path.</violation>
</file>
<file name="apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx">
<violation number="1" location="apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx:174">
P1: This changed send path no longer clears the composer before starting the async send, so rapid Enter/click repeats can submit the same message twice.</violation>
<violation number="2" location="apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx:503">
P2: `hasUploads` also counts failed uploads, so this enables Send even when the queue has no sendable files.</violation>
</file>
<file name="apps/meteor/client/lib/chats/flows/processTooLongMessage.ts">
<violation number="1" location="apps/meteor/client/lib/chats/flows/processTooLongMessage.ts:35">
P1: Clearing the composer before `uploadFiles()` can drop the user's long message when the attachment cannot be queued.</violation>
</file>
<file name="apps/meteor/client/views/room/modals/FileUploadModal/FileUploadModal.tsx">
<violation number="1" location="apps/meteor/client/views/room/modals/FileUploadModal/FileUploadModal.tsx:29">
P2: This refactor drops attachment descriptions/captions from the composer upload flow. Users can still rename a file, but there is no longer any way to send the per-file `description` that `rooms.mediaConfirm` and attachment rendering still support.</violation>
</file>
<file name="apps/meteor/client/views/room/body/hooks/useFileUpload.ts">
<violation number="1" location="apps/meteor/client/views/room/body/hooks/useFileUpload.ts:22">
P2: Stop the `uploading` action based on active uploads, not total attachments. Canceling the last in-flight upload currently leaves a stale uploading indicator when completed attachments remain in the composer.</violation>
</file>
<file name="apps/meteor/app/api/server/v1/rooms.ts">
<violation number="1" location="apps/meteor/app/api/server/v1/rooms.ts:300">
P2: Validate `fileContent` before persisting it. This endpoint currently accepts any value and writes it into `file.content`, which can create malformed encrypted upload metadata.</violation>
</file>
<file name="apps/meteor/client/lib/chats/uploads.ts">
<violation number="1" location="apps/meteor/client/lib/chats/uploads.ts:147">
P1: On XHR network errors, `reject(event)` passes a `ProgressEvent` to the catch block. `getErrorMessage` cannot extract a message from a `ProgressEvent` (it has no `message`, `reason`, or `error` string property) and no `defaultMessage` is provided, so it throws `TypeError('no default error message specified')` — producing an unhandled promise rejection.
Either reject with an `Error` wrapping the response text, or provide a default message to `getErrorMessage`.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
apps/meteor/client/views/room/modals/FileUploadModal/FileUploadModal.tsx
Show resolved
Hide resolved
apps/meteor/client/views/room/composer/messageBox/MessageComposerFileItem.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 11
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/lib/chats/flows/processTooLongMessage.ts (1)
25-39:⚠️ Potential issue | 🟠 MajorDon't clear the long draft until the attachment conversion succeeds.
If
chat.flows.uploadFiles()fails here, this callback never reachesresolve()and the original text is already gone. Keep the draft until the upload is queued, and always settle the modal promise.Proposed fix
const onConfirm = async (): Promise<void> => { const contentType = 'text/plain'; const messageBlob = new Blob([msg], { type: contentType }); const fileName = `${getUser()?.username ?? 'anonymous'} - ${new Date()}.txt`; // TODO: proper naming and formatting const file = new File([messageBlob], fileName, { type: contentType, lastModified: Date.now(), }); - chat.composer?.clear(); imperativeModal.close(); - await chat.flows.uploadFiles({ files: [file], uploadsStore: tmid ? chat.threadUploads : chat.uploads }); - - resolve(); + try { + await chat.flows.uploadFiles({ files: [file], uploadsStore: tmid ? chat.threadUploads : chat.uploads }); + chat.composer?.clear(); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + chat.composer?.setText(msg); + } finally { + resolve(); + } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/client/lib/chats/flows/processTooLongMessage.ts` around lines 25 - 39, The onConfirm callback currently clears the draft (chat.composer?.clear()) and closes the modal before awaiting chat.flows.uploadFiles(), so if uploadFiles rejects the draft is lost and the Promise never resolves; change the flow so you first attempt to queue/upload the file by awaiting chat.flows.uploadFiles(...) and only clear the composer and close the modal after that call succeeds (or at least after the upload is confirmed queued), and ensure the modal promise is always settled by calling resolve() in both success and error paths (use try/catch/finally around the await of uploadFiles to call imperativeModal.close() and resolve(), and on error leave the composer content intact or restore it so the draft is not lost).
♻️ Duplicate comments (6)
apps/meteor/tests/e2e/image-upload.spec.ts (2)
40-40:⚠️ Potential issue | 🟡 MinorRe-add a visible upload-error assertion.
This currently validates only
readonly, which can pass even when the user-visible error state regresses.Suggested assertion update
+ await expect(poHomeChannel.statusUploadError).toBeVisible(); await expect(poHomeChannel.composer.getFileByName('bad-orientation.jpeg')).toHaveAttribute('readonly');As per coding guidelines "Prefer web-first assertions (
toBeVisible,toHaveText, etc.) in Playwright tests".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/tests/e2e/image-upload.spec.ts` at line 40, The test currently only asserts readonly on poHomeChannel.composer.getFileByName('bad-orientation.jpeg'), which can miss regressions in the user-visible error state; update the assertion to also check the visible upload error (use Playwright web-first assertions) by locating the same file element via poHomeChannel.composer.getFileByName('bad-orientation.jpeg') and asserting its error indicator is visible or contains the expected text (e.g., use toBeVisible() or toHaveText('upload error' or the app's exact message)) so the test validates the user-facing error as well as readonly.
52-55:⚠️ Potential issue | 🟡 MinorAssert attachment rendering, not generic message text.
lastUserMessage.toContainText(imgName)can pass on plain text mentions and does not guarantee an uploaded-file element was rendered.Suggested assertion update
- await expect(poHomeChannel.content.lastUserMessage).toContainText(imgName); + await expect(poHomeChannel.content.getLastMessageByFileName(imgName)).toBeVisible();As per coding guidelines "Prefer web-first assertions (
toBeVisible,toHaveText, etc.) in Playwright tests".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/tests/e2e/image-upload.spec.ts` around lines 52 - 55, Replace the brittle text assertion that checks for imgName with a web-first assertion that verifies an uploaded-file element was rendered: after calling poHomeChannel.content.sendFileMessage and clicking poHomeChannel.composer.btnSend, locate the file/attachment element inside poHomeChannel.content.lastUserMessage (e.g., the image thumbnail, attachment container or filename element) and assert it is visible and/or has the filename using toBeVisible/toHaveText instead of toContainText; update the test to target that attachment element rather than relying on generic message text.apps/meteor/tests/e2e/e2e-encryption/e2ee-encryption-decryption.spec.ts (1)
122-127:⚠️ Potential issue | 🟠 MajorDon’t wrap rename/update side effects inside
toPass().This retry wrapper can rerun
click(),setName(), andupdate()and make the test flaky or duplicate actions.Safer pattern
- await expect(async () => { - await encryptedRoomPage.composer.getFileByName('any_file.txt').click(); - await fileUploadModal.setName(fileName); - await fileUploadModal.update(); - await expect(encryptedRoomPage.composer.getFileByName(fileName)).toBeVisible(); - }).toPass(); + const pendingUpload = encryptedRoomPage.composer.getFileByName('any_file.txt'); + await expect(pendingUpload).toBeVisible(); + await pendingUpload.click(); + await fileUploadModal.setName(fileName); + await fileUploadModal.update(); + await expect(encryptedRoomPage.composer.getFileByName(fileName)).toBeVisible();#!/bin/bash # Verify non-idempotent actions are wrapped inside toPass in this spec rg -n -C2 'toPass|getFileByName\(.*\)\.click\(|fileUploadModal\.setName|fileUploadModal\.update' \ apps/meteor/tests/e2e/e2e-encryption/e2ee-encryption-decryption.spec.tsAs per coding guidelines "Implement proper wait strategies for dynamic content in Playwright tests".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/tests/e2e/e2e-encryption/e2ee-encryption-decryption.spec.ts` around lines 122 - 127, The test currently wraps non-idempotent actions (encryptedRoomPage.composer.getFileByName(...).click, fileUploadModal.setName, fileUploadModal.update) inside the toPass retry wrapper which can re-run those side effects and cause flakiness; change the flow so you perform the click, setName, and update exactly once (call encryptedRoomPage.composer.getFileByName(...).click(); fileUploadModal.setName(...); await fileUploadModal.update();) and then wrap only the assertion that the renamed file is visible (e.g. expect(encryptedRoomPage.composer.getFileByName(fileName)).toBeVisible()) with toPass or use an explicit Playwright wait/assert, ensuring to reference the existing encryptedRoomPage.composer.getFileByName, fileUploadModal.setName, fileUploadModal.update, and toPass symbols when making the change.apps/meteor/client/views/room/body/UploadProgress/UploadProgressIndicator.tsx (1)
16-29:⚠️ Potential issue | 🟠 MajorAverage the same upload set you're reporting.
Lines 24-25 compute
percentagefromvalidUploads, but Line 29 reportsactiveUploads.length. Once some uploads hit 100%, the bubble can say "Uploading 1 file" while showing a percentage dominated by completed uploads.💡 Suggested change
- const totalPercentage = validUploads.reduce((sum, upload) => sum + upload.percentage, 0); - const avgPercentage = Math.round(totalPercentage / validUploads.length); + const totalPercentage = activeUploads.reduce((sum, upload) => sum + upload.percentage, 0); + const avgPercentage = Math.round(totalPercentage / activeUploads.length);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/client/views/room/body/UploadProgress/UploadProgressIndicator.tsx` around lines 16 - 29, The percentage is averaged over validUploads but the count shows activeUploads, causing mismatch when some uploads are complete; update the useMemo in UploadProgressIndicator (the validUploads / activeUploads logic) so that percentage is computed only from the same set you report as count (i.e., average over activeUploads instead of validUploads) and return { percentage, count: activeUploads.length } so the reported percentage and count reflect the same uploads.apps/meteor/client/lib/chats/flows/uploadFiles.ts (1)
37-39:⚠️ Potential issue | 🔴 CriticalDon't fall back to plaintext when the E2EE room instance is missing.
Line 37 still treats
!e2eRoomthe same asE2E_Enable_Encrypt_Files === falseand sends the raw file. In an encrypted room with file encryption enabled, that silently downgrades the upload instead of failing the batch.Based on learnings, plaintext fallback in E2E rooms is only expected when `E2E_Enable_Encrypt_Files` is disabled, and when encryption is required but unavailable the queue should fail fast.🔒 Suggested change
- if (!e2eRoom || !settings.peek('E2E_Enable_Encrypt_Files')) { + if (!room.encrypted || !settings.peek('E2E_Enable_Encrypt_Files')) { await uploadsStore.send(file); return; } + + if (!e2eRoom || !e2eRoom.isReady()) { + dispatchToastMessage({ + type: 'error', + message: t('Error_encrypting_file'), + }); + return; + } const encryptedFile = await e2eRoom.encryptFile(file); - if (!e2eRoom.isReady() || !encryptedFile) { + if (!encryptedFile) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/client/lib/chats/flows/uploadFiles.ts` around lines 37 - 39, The current check lumps missing e2eRoom with the feature-flag off and silently uploads plaintext; change the logic so that only when settings.peek('E2E_Enable_Encrypt_Files') is false you call uploadsStore.send(file), but if E2E file encryption is enabled and e2eRoom is missing (e2eRoom === null/undefined) fail fast (throw or reject the batch) instead of calling uploadsStore.send; update the branch around e2eRoom and settings.peek('E2E_Enable_Encrypt_Files') in uploadFiles.ts so callers of the upload flow (the function handling this loop) receive the error and the batch fails rather than downgrading to plaintext.apps/meteor/client/lib/chats/flows/processMessageUploads.ts (1)
186-208:⚠️ Potential issue | 🔴 CriticalResolve the modal Promise on cancel/close/all-failed acknowledge paths.
Several handlers close the modal but never resolve the Promise (Line 189-191, Line 202-204, Line 206-208). That can leave
processMessageUploadspending indefinitely.Proposed fix
...(allUploadsFailed && { title: t('Warning'), confirmText: t('Ok'), onConfirm: () => { imperativeModal.close(); + resolve(false); }, }), ...(!allUploadsFailed && { title: t('Are_you_sure'), confirmText: t('Send_anyway'), cancelText: t('Cancel'), onConfirm: () => { imperativeModal.close(); failedUploads.forEach((upload) => store.removeUpload(upload.id)); resolve(continueSendingMessage(chat, store, message)); }, onCancel: () => { imperativeModal.close(); + resolve(false); }, }), onClose: () => { imperativeModal.close(); + resolve(false); },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/client/lib/chats/flows/processMessageUploads.ts` around lines 186 - 208, The modal close handlers currently call imperativeModal.close() but never resolve the Promise returned by processMessageUploads, leaving callers hanging; update the three handlers tied to the allUploadsFailed acknowledgement, the cancel path and the onClose path so they call resolve with the appropriate value (e.g., resolve(false) for cancelled/closed paths and resolve(continueSendingMessage(chat, store, message)) where the user confirms sending), and ensure the failedUploads.forEach(store.removeUpload) logic remains before resolving in the non-all-failed confirm path; reference processMessageUploads, imperativeModal.close(), continueSendingMessage, failedUploads, store.removeUpload, allUploadsFailed and resolve to locate and modify the handlers.
🧹 Nitpick comments (6)
packages/i18n/src/locales/en.i18n.json (1)
5913-5913: Optional: consolidate “too many files” copy to one source key.Two near-equivalent messages for the same limit condition can drift over time; consider reusing one key for consistency.
Also applies to: 6331-6331
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/i18n/src/locales/en.i18n.json` at line 5913, There are two near-duplicate translation keys for the same "too many files" limit (e.g., "You_cant_upload_more_than__count__files" and the other similar key found elsewhere); consolidate them into a single canonical key (pick one, e.g., "You_cant_upload_more_than__count__files"), update any code references or other locale keys to use that single key, and remove the duplicate entry; ensure the chosen key uses the consistent placeholder "{{count}}" and update any tests or components that referenced the old key to prevent regressions.apps/meteor/client/lib/chats/flows/processSetReaction.ts (1)
24-25: Consider clearing aftersetReactioncompletes.At Line 24, clearing first can drop the typed command if the reaction call fails. Moving clear after the await improves retry UX.
Suggested adjustment
- chat.composer?.clear(); await callWithErrorHandling('setReaction', reaction, lastMessage._id); + chat.composer?.clear();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/client/lib/chats/flows/processSetReaction.ts` around lines 24 - 25, The composer is cleared before the async call to callWithErrorHandling('setReaction', reaction, lastMessage._id), which can drop the typed command if the call fails; move chat.composer?.clear() to run after the await so clearing happens only on successful completion of setReaction (i.e., callWithErrorHandling resolves) and reference chat.composer?.clear(), callWithErrorHandling('setReaction', ...), and lastMessage._id when making the change.apps/meteor/client/lib/chats/uploads.ts (1)
119-121: Prefer a named sentinel over the inline size-limit comment.If
-1needs explanation here, extracting it to a constant/helper keeps the flow self-documenting without leaving an implementation comment in the middle of the request path.As per coding guidelines,
**/*.{ts,tsx,js}:Avoid code comments in the implementation.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/client/lib/chats/uploads.ts` around lines 119 - 121, Replace the magic -1 sentinel with a named constant to make the intent self-documenting: introduce a constant like UNLIMITED_FILE_SIZE (or NO_SIZE_LIMIT) and use it in the size-check conditional that references maxFileSize (the block that currently reads "if (maxFileSize > -1 && (file.size || 0) > maxFileSize)"): update the condition to compare against the new constant and keep the existing reject(new Error(...)) behavior; define the constant near related upload configuration or at the top of this module and export it if it’s used elsewhere so the code no longer needs the inline comment to explain the sentinel.apps/meteor/client/lib/chats/flows/sendMessage.ts (1)
96-98: Drop the inline implementation note.This branch is clear from the code, and the repo guideline for TS/JS implementation files is to avoid code comments.
Proposed fix
- // When editing an encrypted message with files, preserve the original attachments/files - // This ensures they're included in the re-encryption process if (mid) {As per coding guidelines, "Avoid code comments in the implementation".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/client/lib/chats/flows/sendMessage.ts` around lines 96 - 98, Remove the inline implementation comment inside apps/meteor/client/lib/chats/flows/sendMessage.ts that precedes the "if (mid)" branch; specifically delete the two comment lines starting with "// When editing an encrypted message..." and "// This ensures..." so the code reads directly with the if (mid) block, preserving existing logic in the sendMessage flow.apps/meteor/tests/e2e/page-objects/fragments/home-content.ts (1)
380-416: Extract the drop-target locator to a shared getter for reuse.The same drop overlay locator is repeated in both drag-and-drop helpers (Line 393 and Line 412). Centralizing it reduces maintenance drift.
Proposed refactor
+ get dropTargetOverlay(): Locator { + return this.page.locator('[role=dialog][data-qa="DropTargetOverlay"]'); + } + async dragAndDropLstFile({ waitForResponse = true }: { waitForResponse?: boolean } = {}): Promise<void> { // ... await this.composer.inputMessage.dispatchEvent('dragenter', { dataTransfer }); - await this.page.locator('[role=dialog][data-qa="DropTargetOverlay"]').dispatchEvent('drop', { dataTransfer }); + await this.dropTargetOverlay.dispatchEvent('drop', { dataTransfer }); if (responsePromise) { await responsePromise; } } async dragAndDropTxtFileToThread({ waitForResponse = true }: { waitForResponse?: boolean } = {}): Promise<void> { // ... await this.threadComposer.inputMessage.dispatchEvent('dragenter', { dataTransfer }); - await this.page.locator('[role=dialog][data-qa="DropTargetOverlay"]').dispatchEvent('drop', { dataTransfer }); + await this.dropTargetOverlay.dispatchEvent('drop', { dataTransfer }); if (responsePromise) { await responsePromise; } }As per coding guidelines: "
apps/meteor/tests/e2e/**/*.{ts,spec.ts}: Store commonly used locators in variables/constants for reuse".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/tests/e2e/page-objects/fragments/home-content.ts` around lines 380 - 416, Both dragAndDropLstFile and dragAndDropTxtFileToThread repeat the same drop overlay locator; add a shared getter (e.g., a method or property on this page-object class named dropTargetOverlay or getDropTargetOverlay) that returns this.page.locator('[role=dialog][data-qa="DropTargetOverlay"]'), then replace the inline locator calls in dragAndDropLstFile and dragAndDropTxtFileToThread with that getter (use dropTargetOverlay.dispatchEvent('drop', { dataTransfer }) so both helpers reuse the same locator).apps/meteor/client/lib/chats/flows/processMessageUploads.ts (1)
110-113: Remove inline implementation comment and encode intent in code.Please avoid implementation comments here and make the condition self-descriptive with a boolean variable.
Proposed refactor
- /** - * The first message will keep the composedMessage, - * subsequent messages will have a empty text - * */ - const currentMsg = upload === validFiles[0] ? msg : ''; + const isFirstUpload = upload === validFiles[0]; + const currentMsg = isFirstUpload ? msg : '';As per coding guidelines: "
**/*.{ts,tsx,js}: Avoid code comments in the implementation".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/client/lib/chats/flows/processMessageUploads.ts` around lines 110 - 113, Remove the inline implementation comment and make the intent explicit by introducing a boolean (e.g., shouldKeepComposedMessage or isFirstMessage) inside processMessageUploads; compute it from the message index/iteration context and use that boolean in the conditional that currently decides whether to keep composedMessage or set text to '' (instead of relying on a naked condition + comment). Update the assignment/branch that references composedMessage/text to use this self-descriptive variable and delete the comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/meteor/client/lib/chats/flows/processMessageEditing.ts`:
- Around line 26-27: The composer is being cleared before the save completes
which can lose input if updateMessage fails; change flow in
processMessageEditing so you call chat.data.updateMessage({ ...message, _id: mid
}, previewUrls) first (await it), and only call chat.composer?.clear() after
that promise resolves successfully; wrap the await in a try/catch to avoid
clearing on error and surface/log the failure (or rethrow) so failed edits don’t
erase the composer contents.
In `@apps/meteor/client/lib/chats/flows/processMessageUploads.ts`:
- Around line 149-157: The current processMessageUploads function always returns
true even after a failed media confirm (catch block), which misreports failure
as success; update function flow so that a successful true is only returned when
the confirm/upload completes without throwing (e.g., move or add the final
"return true" inside the try path or return a boolean based on success flag),
ensure existing cleanup in the finally block (store.setProcessingUploads(false)
and chat.action.stop('uploading')) remains, and keep the error handling via
dispatchToastMessage(error) in the catch so failures return false (or
no-success) instead of true.
In `@apps/meteor/client/lib/chats/flows/processSlashCommand.ts`:
- Around line 94-95: The composer is being cleared before invoking
sdk.call('slashCommand', ...) which loses the user's typed command if the call
fails; change the flow in processSlashCommand so that chat.composer?.clear() is
performed only after the slashCommand call succeeds (i.e., after awaiting
sdk.call and verifying success), and if the call rejects or returns an error, do
not clear the composer (or restore the original message text into chat.composer)
so the user's input is preserved; update the handling around
sdk.call('slashCommand', { cmd: commandName, params, msg: message, triggerId })
and move or conditionalize chat.composer?.clear() accordingly.
In `@apps/meteor/client/lib/chats/flows/uploadFiles.ts`:
- Around line 13-27: The validation early-returns (max files and encrypted-room
checks around mergedFilesLength/MAX_MULTIPLE_UPLOADED_FILES and room.encrypted
with settings.peek(...)) do not clear the native file input, so re-selecting the
same files won't fire change; before each validation return, call the same reset
that currently runs at the successful-return path (i.e., clear the native file
input element or invoke the existing resetFileInput/resetInput function) so the
input.value is reset when dispatchToastMessage(...) is returned due to
validation failure.
In `@apps/meteor/client/views/room/body/hooks/useFileUpload.ts`:
- Around line 21-25: The stopUploadingAction currently checks uploads.length (in
useFileUpload's stopUploadingAction) which counts all attachments rather than
active in-flight uploads; change it to use the active upload count or the
upload.isUploading transition (e.g., inspect uploads.filter(u =>
u.isUploading).length or rely on the isUploading flag on each upload) so
stop('uploading') is only called when there are zero active uploads; update the
remove/cancel handlers and the upload completion path (the handlers around where
stopUploadingAction is invoked and where a successful upload resolves,
referenced in the same file) to call the revised stopUploadingAction or directly
check active uploads/isUploading before calling chat.action.stop('uploading').
In
`@apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useWebdavActions.tsx`:
- Around line 12-13: The "Add Server" menu item in useWebdavActions is not
honoring the incoming disabled flag (disabled param) so the toolbar remains
partially interactive; update the webdav-add menu item's props inside
useWebdavActions to include the same disabled gating used for account actions
(e.g., set disabled: true when the function's disabled || !enabled condition
applies), ensuring the 'webdav-add' GenericMenuItemProps is blocked when
disabled is true or Webdav_Integration_Enabled is false.
In `@apps/meteor/tests/e2e/file-upload.spec.ts`:
- Around line 121-125: The test uses Promise.all to call
poHomeChannel.content.sendFileMessage concurrently with the same file input
causing race conditions; change the loop to upload files sequentially by
iterating over the files array and awaiting
poHomeChannel.content.sendFileMessage(file) for each item (then call
poHomeChannel.content.dragAndDropTxtFile() as before) so the max-files
enforcement is tested deterministically.
- Around line 59-67: The test 'should attach multiple files and send one per
message' currently has no effective assertions before send and only checks a
single download link after send; update it to assert both attachments are
present and result in two separate messages: use
poHomeChannel.composer.getFileByName(TEST_FILE_TXT) and
getFileByName(TEST_FILE_LST) with explicit truthy/exists assertions before
clicking poHomeChannel.composer.btnSend, then after send assert that
poHomeChannel.content has two user message download links (e.g.,
expect(poHomeChannel.content.lastUserMessageDownloadLink).toHaveCount(2) or
assert each message contains the respective filename), ensuring each file
produced its own message as intended.
- Around line 47-53: The current assertion
expect(poHomeChannel.composer.getFileByName(updatedFileName)) is a no-op;
replace it with a real Playwright matcher and await it (for example: await
expect(poHomeChannel.composer.getFileByName(updatedFileName)).toBeVisible() or
await
expect(poHomeChannel.composer.getFileByName(updatedFileName)).toHaveCount(1));
update the assertion in the test.step block where getFileByName(updatedFileName)
is used (before calling poHomeChannel.composer.btnSend.click()) and ensure
Playwright's expect is imported so the check actually verifies the renamed file
chip is present/visible.
In
`@packages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFile.tsx`:
- Around line 56-72: The consumer-provided className currently overwrites the
component's internal previewWrapperStyle in the MessageComposerFile component;
modify the JSX where previewWrapperStyle and {...props} are applied (the element
with className={previewWrapperStyle}) to merge the two class names instead of
allowing props.className to override—use the project's class merging utility
(e.g., clsx/classNames) or a template string to combine previewWrapperStyle and
props.className (ensuring the internal previewWrapperStyle remains first) and
remove className from the rest spread if necessary so the merged class is passed
to the element.
In
`@packages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFileGroup.tsx`:
- Around line 15-16: In MessageComposerFileGroup (the React component in
MessageComposerFileGroup.tsx) the inline style { whiteSpace: 'nowrap',
...props.style } is being overwritten by the subsequent {...props} spread; fix
by avoiding spreading props after the style—either destructure props (e.g.,
const { style, ...rest } = props) and render with style={{ whiteSpace: 'nowrap',
...(style || {}) }} and then {...rest}, or move {...props} before the style prop
so the composed style wins; update the JSX in MessageComposerFileGroup
accordingly.
---
Outside diff comments:
In `@apps/meteor/client/lib/chats/flows/processTooLongMessage.ts`:
- Around line 25-39: The onConfirm callback currently clears the draft
(chat.composer?.clear()) and closes the modal before awaiting
chat.flows.uploadFiles(), so if uploadFiles rejects the draft is lost and the
Promise never resolves; change the flow so you first attempt to queue/upload the
file by awaiting chat.flows.uploadFiles(...) and only clear the composer and
close the modal after that call succeeds (or at least after the upload is
confirmed queued), and ensure the modal promise is always settled by calling
resolve() in both success and error paths (use try/catch/finally around the
await of uploadFiles to call imperativeModal.close() and resolve(), and on error
leave the composer content intact or restore it so the draft is not lost).
---
Duplicate comments:
In `@apps/meteor/client/lib/chats/flows/processMessageUploads.ts`:
- Around line 186-208: The modal close handlers currently call
imperativeModal.close() but never resolve the Promise returned by
processMessageUploads, leaving callers hanging; update the three handlers tied
to the allUploadsFailed acknowledgement, the cancel path and the onClose path so
they call resolve with the appropriate value (e.g., resolve(false) for
cancelled/closed paths and resolve(continueSendingMessage(chat, store, message))
where the user confirms sending), and ensure the
failedUploads.forEach(store.removeUpload) logic remains before resolving in the
non-all-failed confirm path; reference processMessageUploads,
imperativeModal.close(), continueSendingMessage, failedUploads,
store.removeUpload, allUploadsFailed and resolve to locate and modify the
handlers.
In `@apps/meteor/client/lib/chats/flows/uploadFiles.ts`:
- Around line 37-39: The current check lumps missing e2eRoom with the
feature-flag off and silently uploads plaintext; change the logic so that only
when settings.peek('E2E_Enable_Encrypt_Files') is false you call
uploadsStore.send(file), but if E2E file encryption is enabled and e2eRoom is
missing (e2eRoom === null/undefined) fail fast (throw or reject the batch)
instead of calling uploadsStore.send; update the branch around e2eRoom and
settings.peek('E2E_Enable_Encrypt_Files') in uploadFiles.ts so callers of the
upload flow (the function handling this loop) receive the error and the batch
fails rather than downgrading to plaintext.
In
`@apps/meteor/client/views/room/body/UploadProgress/UploadProgressIndicator.tsx`:
- Around line 16-29: The percentage is averaged over validUploads but the count
shows activeUploads, causing mismatch when some uploads are complete; update the
useMemo in UploadProgressIndicator (the validUploads / activeUploads logic) so
that percentage is computed only from the same set you report as count (i.e.,
average over activeUploads instead of validUploads) and return { percentage,
count: activeUploads.length } so the reported percentage and count reflect the
same uploads.
In `@apps/meteor/tests/e2e/e2e-encryption/e2ee-encryption-decryption.spec.ts`:
- Around line 122-127: The test currently wraps non-idempotent actions
(encryptedRoomPage.composer.getFileByName(...).click, fileUploadModal.setName,
fileUploadModal.update) inside the toPass retry wrapper which can re-run those
side effects and cause flakiness; change the flow so you perform the click,
setName, and update exactly once (call
encryptedRoomPage.composer.getFileByName(...).click();
fileUploadModal.setName(...); await fileUploadModal.update();) and then wrap
only the assertion that the renamed file is visible (e.g.
expect(encryptedRoomPage.composer.getFileByName(fileName)).toBeVisible()) with
toPass or use an explicit Playwright wait/assert, ensuring to reference the
existing encryptedRoomPage.composer.getFileByName, fileUploadModal.setName,
fileUploadModal.update, and toPass symbols when making the change.
In `@apps/meteor/tests/e2e/image-upload.spec.ts`:
- Line 40: The test currently only asserts readonly on
poHomeChannel.composer.getFileByName('bad-orientation.jpeg'), which can miss
regressions in the user-visible error state; update the assertion to also check
the visible upload error (use Playwright web-first assertions) by locating the
same file element via
poHomeChannel.composer.getFileByName('bad-orientation.jpeg') and asserting its
error indicator is visible or contains the expected text (e.g., use
toBeVisible() or toHaveText('upload error' or the app's exact message)) so the
test validates the user-facing error as well as readonly.
- Around line 52-55: Replace the brittle text assertion that checks for imgName
with a web-first assertion that verifies an uploaded-file element was rendered:
after calling poHomeChannel.content.sendFileMessage and clicking
poHomeChannel.composer.btnSend, locate the file/attachment element inside
poHomeChannel.content.lastUserMessage (e.g., the image thumbnail, attachment
container or filename element) and assert it is visible and/or has the filename
using toBeVisible/toHaveText instead of toContainText; update the test to target
that attachment element rather than relying on generic message text.
---
Nitpick comments:
In `@apps/meteor/client/lib/chats/flows/processMessageUploads.ts`:
- Around line 110-113: Remove the inline implementation comment and make the
intent explicit by introducing a boolean (e.g., shouldKeepComposedMessage or
isFirstMessage) inside processMessageUploads; compute it from the message
index/iteration context and use that boolean in the conditional that currently
decides whether to keep composedMessage or set text to '' (instead of relying on
a naked condition + comment). Update the assignment/branch that references
composedMessage/text to use this self-descriptive variable and delete the
comment.
In `@apps/meteor/client/lib/chats/flows/processSetReaction.ts`:
- Around line 24-25: The composer is cleared before the async call to
callWithErrorHandling('setReaction', reaction, lastMessage._id), which can drop
the typed command if the call fails; move chat.composer?.clear() to run after
the await so clearing happens only on successful completion of setReaction
(i.e., callWithErrorHandling resolves) and reference chat.composer?.clear(),
callWithErrorHandling('setReaction', ...), and lastMessage._id when making the
change.
In `@apps/meteor/client/lib/chats/flows/sendMessage.ts`:
- Around line 96-98: Remove the inline implementation comment inside
apps/meteor/client/lib/chats/flows/sendMessage.ts that precedes the "if (mid)"
branch; specifically delete the two comment lines starting with "// When editing
an encrypted message..." and "// This ensures..." so the code reads directly
with the if (mid) block, preserving existing logic in the sendMessage flow.
In `@apps/meteor/client/lib/chats/uploads.ts`:
- Around line 119-121: Replace the magic -1 sentinel with a named constant to
make the intent self-documenting: introduce a constant like UNLIMITED_FILE_SIZE
(or NO_SIZE_LIMIT) and use it in the size-check conditional that references
maxFileSize (the block that currently reads "if (maxFileSize > -1 && (file.size
|| 0) > maxFileSize)"): update the condition to compare against the new constant
and keep the existing reject(new Error(...)) behavior; define the constant near
related upload configuration or at the top of this module and export it if it’s
used elsewhere so the code no longer needs the inline comment to explain the
sentinel.
In `@apps/meteor/tests/e2e/page-objects/fragments/home-content.ts`:
- Around line 380-416: Both dragAndDropLstFile and dragAndDropTxtFileToThread
repeat the same drop overlay locator; add a shared getter (e.g., a method or
property on this page-object class named dropTargetOverlay or
getDropTargetOverlay) that returns
this.page.locator('[role=dialog][data-qa="DropTargetOverlay"]'), then replace
the inline locator calls in dragAndDropLstFile and dragAndDropTxtFileToThread
with that getter (use dropTargetOverlay.dispatchEvent('drop', { dataTransfer })
so both helpers reuse the same locator).
In `@packages/i18n/src/locales/en.i18n.json`:
- Line 5913: There are two near-duplicate translation keys for the same "too
many files" limit (e.g., "You_cant_upload_more_than__count__files" and the other
similar key found elsewhere); consolidate them into a single canonical key (pick
one, e.g., "You_cant_upload_more_than__count__files"), update any code
references or other locale keys to use that single key, and remove the duplicate
entry; ensure the chosen key uses the consistent placeholder "{{count}}" and
update any tests or components that referenced the old key to prevent
regressions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4eff67de-8963-4d1f-a5e9-8a8d47976efc
⛔ Files ignored due to path filters (1)
packages/ui-composer/src/MessageComposer/__snapshots__/MessageComposer.spec.tsx.snapis excluded by!**/*.snap
📒 Files selected for processing (97)
.changeset/spotty-news-burn.mdapps/meteor/client/lib/chats/ChatAPI.tsapps/meteor/client/lib/chats/flows/processMessageEditing.tsapps/meteor/client/lib/chats/flows/processMessageUploads.tsapps/meteor/client/lib/chats/flows/processSetReaction.tsapps/meteor/client/lib/chats/flows/processSlashCommand.tsapps/meteor/client/lib/chats/flows/processTooLongMessage.tsapps/meteor/client/lib/chats/flows/sendMessage.tsapps/meteor/client/lib/chats/flows/uploadFiles.tsapps/meteor/client/lib/chats/uploads.tsapps/meteor/client/lib/e2ee/rocketchat.e2e.room.tsapps/meteor/client/views/room/body/RoomBody.tsxapps/meteor/client/views/room/body/UploadProgress/UploadProgressContainer.tsxapps/meteor/client/views/room/body/UploadProgress/UploadProgressIndicator.tsxapps/meteor/client/views/room/body/UploadProgress/index.tsapps/meteor/client/views/room/body/hooks/useFileUpload.tsapps/meteor/client/views/room/composer/ComposerMessage.tsxapps/meteor/client/views/room/composer/messageBox/MessageBox.tsxapps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/MessageBoxActionsToolbar.tsxapps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useWebdavActions.tsxapps/meteor/client/views/room/composer/messageBox/MessageComposerFileItem.tsxapps/meteor/client/views/room/composer/messageBox/MessageComposerFiles.tsxapps/meteor/client/views/room/modals/FileUploadModal/FileUploadModal.spec.tsxapps/meteor/client/views/room/webdav/WebdavFilePickerModal/WebdavFilePickerModal.tsxapps/meteor/tests/e2e/e2e-encryption/e2ee-encryption-decryption.spec.tsapps/meteor/tests/e2e/e2e-encryption/e2ee-file-encryption.spec.tsapps/meteor/tests/e2e/file-upload.spec.tsapps/meteor/tests/e2e/fixtures/responses/mediaResponse.tsapps/meteor/tests/e2e/image-upload.spec.tsapps/meteor/tests/e2e/page-objects/fragments/composer.tsapps/meteor/tests/e2e/page-objects/fragments/home-content.tsapps/meteor/tests/e2e/prune-messages.spec.tspackages/i18n/src/locales/af.i18n.jsonpackages/i18n/src/locales/ar.i18n.jsonpackages/i18n/src/locales/az.i18n.jsonpackages/i18n/src/locales/be-BY.i18n.jsonpackages/i18n/src/locales/bg.i18n.jsonpackages/i18n/src/locales/bs.i18n.jsonpackages/i18n/src/locales/ca.i18n.jsonpackages/i18n/src/locales/cs.i18n.jsonpackages/i18n/src/locales/cy.i18n.jsonpackages/i18n/src/locales/da.i18n.jsonpackages/i18n/src/locales/de-AT.i18n.jsonpackages/i18n/src/locales/de-IN.i18n.jsonpackages/i18n/src/locales/de.i18n.jsonpackages/i18n/src/locales/el.i18n.jsonpackages/i18n/src/locales/en.i18n.jsonpackages/i18n/src/locales/eo.i18n.jsonpackages/i18n/src/locales/es.i18n.jsonpackages/i18n/src/locales/fa.i18n.jsonpackages/i18n/src/locales/fi.i18n.jsonpackages/i18n/src/locales/fr.i18n.jsonpackages/i18n/src/locales/he.i18n.jsonpackages/i18n/src/locales/hi-IN.i18n.jsonpackages/i18n/src/locales/hr.i18n.jsonpackages/i18n/src/locales/hu.i18n.jsonpackages/i18n/src/locales/id.i18n.jsonpackages/i18n/src/locales/it.i18n.jsonpackages/i18n/src/locales/ja.i18n.jsonpackages/i18n/src/locales/ka-GE.i18n.jsonpackages/i18n/src/locales/km.i18n.jsonpackages/i18n/src/locales/ko.i18n.jsonpackages/i18n/src/locales/ku.i18n.jsonpackages/i18n/src/locales/lo.i18n.jsonpackages/i18n/src/locales/lt.i18n.jsonpackages/i18n/src/locales/lv.i18n.jsonpackages/i18n/src/locales/mn.i18n.jsonpackages/i18n/src/locales/ms-MY.i18n.jsonpackages/i18n/src/locales/nb.i18n.jsonpackages/i18n/src/locales/nl.i18n.jsonpackages/i18n/src/locales/nn.i18n.jsonpackages/i18n/src/locales/pl.i18n.jsonpackages/i18n/src/locales/pt-BR.i18n.jsonpackages/i18n/src/locales/pt.i18n.jsonpackages/i18n/src/locales/ro.i18n.jsonpackages/i18n/src/locales/ru.i18n.jsonpackages/i18n/src/locales/sk-SK.i18n.jsonpackages/i18n/src/locales/sl-SI.i18n.jsonpackages/i18n/src/locales/sq.i18n.jsonpackages/i18n/src/locales/sr.i18n.jsonpackages/i18n/src/locales/sv.i18n.jsonpackages/i18n/src/locales/ta-IN.i18n.jsonpackages/i18n/src/locales/th-TH.i18n.jsonpackages/i18n/src/locales/tr.i18n.jsonpackages/i18n/src/locales/uk.i18n.jsonpackages/i18n/src/locales/vi-VN.i18n.jsonpackages/i18n/src/locales/zh-HK.i18n.jsonpackages/i18n/src/locales/zh-TW.i18n.jsonpackages/i18n/src/locales/zh.i18n.jsonpackages/ui-composer/src/MessageComposer/MessageComposer.stories.tsxpackages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFile.tsxpackages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFileError.tsxpackages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFileGroup.tsxpackages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFileLoader.tsxpackages/ui-composer/src/MessageComposer/MessageComposerFile/index.tspackages/ui-composer/src/MessageComposer/MessageComposerHint.tsxpackages/ui-composer/src/MessageComposer/index.ts
💤 Files with no reviewable changes (57)
- packages/i18n/src/locales/sr.i18n.json
- packages/i18n/src/locales/pt.i18n.json
- packages/i18n/src/locales/ka-GE.i18n.json
- packages/i18n/src/locales/sk-SK.i18n.json
- packages/i18n/src/locales/af.i18n.json
- packages/i18n/src/locales/lt.i18n.json
- packages/i18n/src/locales/az.i18n.json
- packages/i18n/src/locales/ro.i18n.json
- packages/i18n/src/locales/uk.i18n.json
- packages/i18n/src/locales/sq.i18n.json
- packages/i18n/src/locales/cs.i18n.json
- packages/i18n/src/locales/hu.i18n.json
- packages/i18n/src/locales/ar.i18n.json
- packages/i18n/src/locales/fa.i18n.json
- packages/i18n/src/locales/de.i18n.json
- packages/i18n/src/locales/bs.i18n.json
- packages/i18n/src/locales/mn.i18n.json
- packages/i18n/src/locales/hr.i18n.json
- packages/i18n/src/locales/cy.i18n.json
- packages/i18n/src/locales/ca.i18n.json
- packages/i18n/src/locales/nl.i18n.json
- packages/i18n/src/locales/da.i18n.json
- packages/i18n/src/locales/vi-VN.i18n.json
- packages/i18n/src/locales/tr.i18n.json
- packages/i18n/src/locales/es.i18n.json
- packages/i18n/src/locales/bg.i18n.json
- apps/meteor/client/views/room/body/UploadProgress/UploadProgressContainer.tsx
- packages/i18n/src/locales/sl-SI.i18n.json
- packages/i18n/src/locales/km.i18n.json
- packages/i18n/src/locales/ku.i18n.json
- packages/i18n/src/locales/ta-IN.i18n.json
- packages/i18n/src/locales/be-BY.i18n.json
- packages/i18n/src/locales/ko.i18n.json
- packages/i18n/src/locales/th-TH.i18n.json
- packages/i18n/src/locales/el.i18n.json
- packages/i18n/src/locales/pl.i18n.json
- packages/i18n/src/locales/it.i18n.json
- packages/i18n/src/locales/ja.i18n.json
- packages/i18n/src/locales/hi-IN.i18n.json
- packages/i18n/src/locales/de-AT.i18n.json
- packages/i18n/src/locales/lo.i18n.json
- packages/i18n/src/locales/sv.i18n.json
- packages/i18n/src/locales/he.i18n.json
- packages/i18n/src/locales/zh-HK.i18n.json
- packages/i18n/src/locales/nn.i18n.json
- packages/i18n/src/locales/nb.i18n.json
- packages/i18n/src/locales/id.i18n.json
- packages/i18n/src/locales/zh.i18n.json
- packages/i18n/src/locales/de-IN.i18n.json
- packages/i18n/src/locales/ru.i18n.json
- packages/i18n/src/locales/ms-MY.i18n.json
- packages/i18n/src/locales/lv.i18n.json
- packages/i18n/src/locales/eo.i18n.json
- packages/i18n/src/locales/zh-TW.i18n.json
- packages/i18n/src/locales/fi.i18n.json
- packages/i18n/src/locales/fr.i18n.json
- packages/i18n/src/locales/pt-BR.i18n.json
🚧 Files skipped from review as they are similar to previous changes (4)
- apps/meteor/client/views/room/webdav/WebdavFilePickerModal/WebdavFilePickerModal.tsx
- apps/meteor/tests/e2e/fixtures/responses/mediaResponse.ts
- apps/meteor/tests/e2e/page-objects/fragments/composer.ts
- apps/meteor/tests/e2e/prune-messages.spec.ts
packages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFile.tsx
Outdated
Show resolved
Hide resolved
packages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFileGroup.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
♻️ Duplicate comments (1)
apps/meteor/tests/e2e/file-upload.spec.ts (1)
59-67:⚠️ Potential issue | 🟡 MinorStrengthen post-send verification for the multi-file scenario.
Line 66 only proves the last message has one attachment link; it does not prove both selected files were sent as separate messages. Please assert message presence for both filenames after clicking send.
Proposed assertion update
await poHomeChannel.composer.btnSend.click(); - await expect(poHomeChannel.content.lastUserMessageDownloadLink).toHaveCount(1); + await expect(poHomeChannel.content.getLastMessageByFileName(TEST_FILE_TXT)).toBeVisible(); + await expect(poHomeChannel.content.getLastMessageByFileName(TEST_FILE_LST)).toBeVisible();Based on learnings, "each file is confirmed via its own
/rooms.mediaConfirm/${rid}/${fileId}call and produces a separate message."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/tests/e2e/file-upload.spec.ts` around lines 59 - 67, After sending the two files, add assertions that each file produced its own message by checking for messages/download links containing each filename (use the existing TEST_FILE_TXT and TEST_FILE_LST identifiers) instead of only asserting lastUserMessageDownloadLink count; update the test to, after poHomeChannel.composer.btnSend.click(), assert presence of a message or download link for TEST_FILE_TXT and separately for TEST_FILE_LST via the poHomeChannel.content helpers (e.g., look up messages/download links by filename) so both files are verified as distinct sent messages.
🧹 Nitpick comments (2)
packages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFileGroup.tsx (1)
6-7: Consider adding an accessible name to the group.For screen reader users, a
role='group'without an accessible name doesn't convey what the group contains. Since this groups file attachments, anaria-labelwould improve accessibility per the linked issue's requirements (keyboard navigation, screen reader support).♿ Proposed accessibility improvement
<Box role='group' + aria-label='Attached files' display='flex'Note: If the label should be translatable, consider accepting it as a prop or using an i18n hook.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFileGroup.tsx` around lines 6 - 7, The Box with role='group' in MessageComposerFileGroup lacks an accessible name; update MessageComposerFileGroup to provide an aria-label (or aria-labelledby) for that Box so screen readers can identify the attachments group—either hardcode a meaningful label like "Attachments" or accept a translatable prop (e.g., attachmentsLabel) on the MessageComposerFileGroup component and pass it into the Box as aria-label, or use your i18n hook to supply the label.apps/meteor/tests/e2e/file-upload.spec.ts (1)
127-128: Use the existing test-file constant instead of a string literal.Line 128 hardcodes
'any_file.txt'whileTEST_FILE_TXTalready exists; reusing the constant avoids drift.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/meteor/tests/e2e/file-upload.spec.ts` around lines 127 - 128, Replace the string literal 'any_file.txt' with the existing test constant TEST_FILE_TXT in the assertion that calls poHomeChannel.composer.getFileByName so the test uses the canonical filename; update the call in the file-upload.spec.ts expectation (the second line shown) to pass TEST_FILE_TXT to getFileByName instead of the hardcoded string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@apps/meteor/tests/e2e/file-upload.spec.ts`:
- Around line 59-67: After sending the two files, add assertions that each file
produced its own message by checking for messages/download links containing each
filename (use the existing TEST_FILE_TXT and TEST_FILE_LST identifiers) instead
of only asserting lastUserMessageDownloadLink count; update the test to, after
poHomeChannel.composer.btnSend.click(), assert presence of a message or download
link for TEST_FILE_TXT and separately for TEST_FILE_LST via the
poHomeChannel.content helpers (e.g., look up messages/download links by
filename) so both files are verified as distinct sent messages.
---
Nitpick comments:
In `@apps/meteor/tests/e2e/file-upload.spec.ts`:
- Around line 127-128: Replace the string literal 'any_file.txt' with the
existing test constant TEST_FILE_TXT in the assertion that calls
poHomeChannel.composer.getFileByName so the test uses the canonical filename;
update the call in the file-upload.spec.ts expectation (the second line shown)
to pass TEST_FILE_TXT to getFileByName instead of the hardcoded string.
In
`@packages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFileGroup.tsx`:
- Around line 6-7: The Box with role='group' in MessageComposerFileGroup lacks
an accessible name; update MessageComposerFileGroup to provide an aria-label (or
aria-labelledby) for that Box so screen readers can identify the attachments
group—either hardcode a meaningful label like "Attachments" or accept a
translatable prop (e.g., attachmentsLabel) on the MessageComposerFileGroup
component and pass it into the Box as aria-label, or use your i18n hook to
supply the label.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0a6c8ced-76b6-4b19-b054-18b219aa3134
📒 Files selected for processing (4)
apps/meteor/tests/e2e/file-upload.spec.tspackages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFile.tsxpackages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFileGroup.tsxpackages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFileLoader.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFileLoader.tsx
- packages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFile.tsx
📜 Review details
⏰ 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: 📦 Build Packages
- GitHub Check: cubic · AI code reviewer
- GitHub Check: CodeQL-Build
- GitHub Check: CodeQL-Build
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{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:
packages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFileGroup.tsxapps/meteor/tests/e2e/file-upload.spec.ts
**/*.spec.ts
📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)
**/*.spec.ts: Use descriptive test names that clearly communicate expected behavior in Playwright tests
Use.spec.tsextension for test files (e.g.,login.spec.ts)
Files:
apps/meteor/tests/e2e/file-upload.spec.ts
apps/meteor/tests/e2e/**/*.spec.ts
📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)
apps/meteor/tests/e2e/**/*.spec.ts: All test files must be created inapps/meteor/tests/e2e/directory
Avoid usingpage.locator()in Playwright tests - always prefer semantic locators such aspage.getByRole(),page.getByLabel(),page.getByText(), orpage.getByTitle()
Usetest.beforeAll()andtest.afterAll()for setup/teardown in Playwright tests
Usetest.step()for complex test scenarios to improve organization in Playwright tests
Group related tests in the same file
Utilize Playwright fixtures (test,page,expect) for consistency in test files
Prefer web-first assertions (toBeVisible,toHaveText, etc.) in Playwright tests
Useexpectmatchers for assertions (toEqual,toContain,toBeTruthy,toHaveLength, etc.) instead ofassertstatements in Playwright tests
Usepage.waitFor()with specific conditions instead of hardcoded timeouts in Playwright tests
Implement proper wait strategies for dynamic content in Playwright tests
Maintain test isolation between test cases in Playwright tests
Ensure clean state for each test execution in Playwright tests
Ensure tests run reliably in parallel without shared state conflicts
Files:
apps/meteor/tests/e2e/file-upload.spec.ts
apps/meteor/tests/e2e/**/*.{ts,spec.ts}
📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)
apps/meteor/tests/e2e/**/*.{ts,spec.ts}: Store commonly used locators in variables/constants for reuse
Follow Page Object Model pattern consistently in Playwright tests
Files:
apps/meteor/tests/e2e/file-upload.spec.ts
🧠 Learnings (30)
📓 Common learnings
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 39425
File: apps/meteor/client/lib/chats/flows/processMessageUploads.ts:112-119
Timestamp: 2026-03-11T18:17:53.972Z
Learning: In `apps/meteor/client/lib/chats/flows/processMessageUploads.ts`, when sending multiple file uploads, each file is confirmed via its own `/rooms.mediaConfirm/${rid}/${fileId}` call and produces a separate message. Only the first file's confirm payload carries the composed message text (`msg`); all subsequent files receive `msg: ''`. This one-message-per-file behavior is intentional by design — do not flag it as a bug or suggest batching into a single message.
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 39425
File: apps/meteor/client/lib/chats/flows/uploadFiles.ts:28-33
Timestamp: 2026-03-12T17:12:42.624Z
Learning: Rocket.Chat — apps/meteor/client/lib/chats/flows/uploadFiles.ts: When E2E_Enable_Encrypt_Files is disabled, plaintext file uploads are allowed in E2E rooms; this fallback is expected and should not be flagged as a security regression.
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 32703
File: apps/meteor/client/lib/chats/flows/uploadFiles.ts:52-58
Timestamp: 2026-02-12T15:39:28.416Z
Learning: In `apps/meteor/client/lib/chats/flows/uploadFiles.ts`, when E2E encryption is required but not allowed (e.g., `E2E_Enable_Encrypt_Files` setting is disabled), the function intentionally abandons the entire upload queue and displays a toast error. This fail-fast behavior prevents partial uploads when encryption requirements cannot be met and is the expected behavior, not a bug.
📚 Learning: 2026-03-12T17:12:42.624Z
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 39425
File: apps/meteor/client/lib/chats/flows/uploadFiles.ts:28-33
Timestamp: 2026-03-12T17:12:42.624Z
Learning: Rocket.Chat — apps/meteor/client/lib/chats/flows/uploadFiles.ts: When E2E_Enable_Encrypt_Files is disabled, plaintext file uploads are allowed in E2E rooms; this fallback is expected and should not be flagged as a security regression.
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Group related tests in the same file
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2026-03-11T18:17:53.972Z
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 39425
File: apps/meteor/client/lib/chats/flows/processMessageUploads.ts:112-119
Timestamp: 2026-03-11T18:17:53.972Z
Learning: In `apps/meteor/client/lib/chats/flows/processMessageUploads.ts`, when sending multiple file uploads, each file is confirmed via its own `/rooms.mediaConfirm/${rid}/${fileId}` call and produces a separate message. Only the first file's confirm payload carries the composed message text (`msg`); all subsequent files receive `msg: ''`. This one-message-per-file behavior is intentional by design — do not flag it as a bug or suggest batching into a single message.
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Utilize Playwright fixtures (`test`, `page`, `expect`) for consistency in test files
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : All test files must be created in `apps/meteor/tests/e2e/` directory
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2026-02-12T15:39:28.416Z
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 32703
File: apps/meteor/client/lib/chats/flows/uploadFiles.ts:52-58
Timestamp: 2026-02-12T15:39:28.416Z
Learning: In `apps/meteor/client/lib/chats/flows/uploadFiles.ts`, when E2E encryption is required but not allowed (e.g., `E2E_Enable_Encrypt_Files` setting is disabled), the function intentionally abandons the entire upload queue and displays a toast error. This fail-fast behavior prevents partial uploads when encryption requirements cannot be met and is the expected behavior, not a bug.
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.{ts,spec.ts} : Follow Page Object Model pattern consistently in Playwright tests
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Ensure tests run reliably in parallel without shared state conflicts
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/page-objects/**/*.ts : Utilize existing page objects pattern from `apps/meteor/tests/e2e/page-objects/`
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use `test.step()` for complex test scenarios to improve organization in Playwright tests
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2026-03-11T18:15:53.272Z
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 39425
File: apps/meteor/app/api/server/v1/rooms.ts:294-296
Timestamp: 2026-03-11T18:15:53.272Z
Learning: In Rocket.Chat's `rooms.mediaConfirm/:rid/:fileId` endpoint (apps/meteor/app/api/server/v1/rooms.ts), updating `file.name` from `bodyParams.fileName` without updating `file.path` is intentionally safe. The file path is keyed by `_id` (not by filename), so the stored path remains valid regardless of a rename. `file.name` only affects the display name in the message attachment; do not flag this as a path-divergence issue.
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-12-10T21:00:54.909Z
Learnt from: KevLehman
Repo: RocketChat/Rocket.Chat PR: 37091
File: ee/packages/abac/jest.config.ts:4-7
Timestamp: 2025-12-10T21:00:54.909Z
Learning: Rocket.Chat monorepo: Jest testMatch pattern '<rootDir>/src/**/*.spec.(ts|js|mjs)' is valid in this repo and used across multiple packages (e.g., packages/tools, ee/packages/omnichannel-services). Do not flag it as invalid in future reviews.
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2026-02-24T19:36:55.089Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 38493
File: apps/meteor/tests/e2e/page-objects/fragments/home-content.ts:60-82
Timestamp: 2026-02-24T19:36:55.089Z
Learning: In RocketChat/Rocket.Chat e2e tests (apps/meteor/tests/e2e/page-objects/fragments/home-content.ts), thread message preview listitems do not have aria-roledescription="message", so lastThreadMessagePreview locator cannot be scoped to messageListItems (which filters for aria-roledescription="message"). It should remain scoped to page.getByRole('listitem') or mainMessageList.getByRole('listitem').
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Prefer web-first assertions (`toBeVisible`, `toHaveText`, etc.) in Playwright tests
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use `expect` matchers for assertions (`toEqual`, `toContain`, `toBeTruthy`, `toHaveLength`, etc.) instead of `assert` statements in Playwright tests
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-12-16T17:29:45.163Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 37834
File: apps/meteor/tests/e2e/page-objects/fragments/admin-flextab-emoji.ts:12-22
Timestamp: 2025-12-16T17:29:45.163Z
Learning: In page object files under `apps/meteor/tests/e2e/page-objects/`, always import `expect` from `../../utils/test` (Playwright's async expect), not from Jest. Jest's `expect` has a synchronous signature and will cause TypeScript errors when used with web-first assertions like `toBeVisible()`.
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Avoid using `page.locator()` in Playwright tests - always prefer semantic locators such as `page.getByRole()`, `page.getByLabel()`, `page.getByText()`, or `page.getByTitle()`
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to **/*.spec.ts : Use descriptive test names that clearly communicate expected behavior in Playwright tests
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Implement proper wait strategies for dynamic content in Playwright tests
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Use `page.waitFor()` with specific conditions instead of hardcoded timeouts in Playwright tests
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2026-03-11T22:04:20.529Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 39545
File: apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts:59-61
Timestamp: 2026-03-11T22:04:20.529Z
Learning: In `apps/meteor/client/views/room/body/hooks/useHasNewMessages.ts`, the `msg.u._id === uid` early-return in the `streamNewMessage` handler is intentional: the "New messages" indicator is designed to notify about messages from other users only. Self-sent messages — including those sent from a different session/device — are always skipped, by design. Do not flag this as a multi-session regression.
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Maintain test isolation between test cases in Playwright tests
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.spec.ts : Ensure clean state for each test execution in Playwright tests
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2025-11-24T17:08:17.065Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat PR: 0
File: .cursor/rules/playwright.mdc:0-0
Timestamp: 2025-11-24T17:08:17.065Z
Learning: Applies to apps/meteor/tests/e2e/**/*.{ts,spec.ts} : Store commonly used locators in variables/constants for reuse
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2026-02-24T19:22:48.358Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 38493
File: apps/meteor/tests/e2e/omnichannel/omnichannel-send-pdf-transcript.spec.ts:66-67
Timestamp: 2026-02-24T19:22:48.358Z
Learning: In Playwright end-to-end tests (e.g., under apps/meteor/tests/e2e/...), prefer locating elements by translated text (getByText) and ARIA roles (getByRole) over data-qa attributes. If translation values change, update the corresponding test locators accordingly. Never use data-qa locators. This guideline applies to all Playwright e2e test specs in the repository and helps keep tests robust to UI text changes and accessible semantics.
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2026-02-24T19:39:42.247Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 38493
File: apps/meteor/tests/e2e/page-objects/fragments/message.ts:7-7
Timestamp: 2026-02-24T19:39:42.247Z
Learning: In RocketChat e2e tests, avoid using data-qa attributes to locate elements. Prefer semantic locators such as getByRole, getByLabel, getByText, getByTitle and ARIA-based selectors. Apply this rule to all TypeScript files under apps/meteor/tests/e2e to improve test reliability, accessibility, and maintainability.
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
📚 Learning: 2026-03-06T18:10:15.268Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 39397
File: packages/gazzodown/src/code/CodeBlock.spec.tsx:47-68
Timestamp: 2026-03-06T18:10:15.268Z
Learning: In tests (especially those using testing-library/dom/jsdom) for Rocket.Chat components, the HTML <code> element has an implicit ARIA role of 'code'. Therefore, screen.getByRole('code') or screen.findByRole('code') will locate <code> elements even without a role attribute. Do not flag findByRole('code') as invalid in reviews; prefer using the implicit role instead of adding role="code" unless necessary for accessibility.
Applied to files:
apps/meteor/tests/e2e/file-upload.spec.ts
🔇 Additional comments (4)
packages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFileGroup.tsx (1)
4-16: Style prop handling is now correct.The previous concern about
{...props}overwriting the composedstylehas been properly addressed. By destructuringstyleseparately frompropson line 4, the spread on line 16 no longer containsstyle, ensuringwhiteSpace: 'nowrap'is preserved and merged with any caller-provided styles.apps/meteor/tests/e2e/file-upload.spec.ts (3)
43-57: Rename-and-send flow is well-structured.Nice use of
test.step()plus a real visibility assertion before send; this is clear and robust.
121-125: Avoid concurrent upload setup against a single composer input.Using
Promise.all()here can make this cap test flaky/racy. Upload sequentially to keep the max-files assertion deterministic.Proposed deterministic setup
const files = new Array(10).fill('number1.png'); - await Promise.all(files.map((file) => poHomeChannel.content.sendFileMessage(file))); + for (const file of files) { + await poHomeChannel.content.sendFileMessage(file); + } await poHomeChannel.content.dragAndDropTxtFile();As per coding guidelines, "Ensure tests run reliably in parallel without shared state conflicts".
156-206: Failure-handling coverage is comprehensive.Good validation of all-fail, partial-fail, cancel, and send-anyway paths with web-first assertions.
There was a problem hiding this comment.
1 issue found across 4 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFileLoader.tsx">
<violation number="1" location="packages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFileLoader.tsx:5">
P2: Use SVG props here instead of `AllHTMLAttributes`. Because this loader renders `is='svg'` and is publicly exported, the new type turns valid SVG attributes into TypeScript errors for consumers.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| import { Box, Palette } from '@rocket.chat/fuselage'; | ||
| import type { AllHTMLAttributes } from 'react'; | ||
|
|
||
| const MessageComposerFileLoader = ({ className, ...props }: Omit<AllHTMLAttributes<HTMLOrSVGElement>, 'is'>) => { |
There was a problem hiding this comment.
P2: Use SVG props here instead of AllHTMLAttributes. Because this loader renders is='svg' and is publicly exported, the new type turns valid SVG attributes into TypeScript errors for consumers.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/ui-composer/src/MessageComposer/MessageComposerFile/MessageComposerFileLoader.tsx, line 5:
<comment>Use SVG props here instead of `AllHTMLAttributes`. Because this loader renders `is='svg'` and is publicly exported, the new type turns valid SVG attributes into TypeScript errors for consumers.</comment>
<file context>
@@ -1,8 +1,8 @@
+import type { AllHTMLAttributes } from 'react';
-const MessageComposerFileLoader = ({ className, ...props }: ComponentProps<typeof Box>) => {
+const MessageComposerFileLoader = ({ className, ...props }: Omit<AllHTMLAttributes<HTMLOrSVGElement>, 'is'>) => {
const customCSS = css`
animation: spin-animation 0.8s linear infinite;
</file context>
Proposed changes (including videos or screenshots)
It changes how we handle uploads. If the user upload multiple files they will be attached in the composer for better visibility
Issue(s)
Steps to test or reproduce
Further comments
CORE-1959
Summary by CodeRabbit
New Features
Improvements