fix: attachment collapsed state resets after switching channels#40359
fix: attachment collapsed state resets after switching channels#40359rafaelcunha02 wants to merge 2 commits intoRocketChat:developfrom
Conversation
|
Looks like this PR is not ready to merge, because of the following issues:
Please fix the issues and try again If you have any trouble, please check the PR guidelines |
🦋 Changeset detectedLatest commit: fc32cd2 The changes in this PR will be included in the next version bump. This PR includes changesets to release 41 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 |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📜 Recent 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). (1)
WalkthroughAdds session-level persistence for attachment collapse state using Changes
Sequence DiagramsequenceDiagram
actor User
participant ImageAttachment as ImageAttachment (Component)
participant MessageCollapsible as MessageCollapsible (Component)
participant useCollapse as useCollapse (Hook)
participant sessionStorage as sessionStorage (Browser)
User->>ImageAttachment: Mount with id
ImageAttachment->>MessageCollapsible: Pass storageKey=id
MessageCollapsible->>useCollapse: Call useCollapse(isCollapsed, storageKey)
useCollapse->>sessionStorage: Read "rc_attachment_collapsed_<id>"
sessionStorage-->>useCollapse: Return stored value or undefined
useCollapse-->>MessageCollapsible: Return collapsed state + toggleFn
MessageCollapsible-->>ImageAttachment: Render with collapsed state
User->>ImageAttachment: Click collapse toggle
ImageAttachment->>MessageCollapsible: Invoke toggleFn
MessageCollapsible->>useCollapse: toggleCollapsed()
useCollapse->>useCollapse: Update state
useCollapse->>sessionStorage: Write "rc_attachment_collapsed_<id>" = true/false
useCollapse-->>MessageCollapsible: Updated collapsed state
MessageCollapsible-->>ImageAttachment: Re-render
User->>User: Navigate away and back
ImageAttachment->>useCollapse: Re-mount with same storageKey
useCollapse->>sessionStorage: Read "rc_attachment_collapsed_<id>"
sessionStorage-->>useCollapse: Return persisted value
useCollapse-->>ImageAttachment: Render with persisted collapsed state
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/components/message/content/attachments/DefaultAttachment.tsx`:
- Around line 30-31: The current storageKey uses attachment.title_link which can
collide across messages; change how storageKey is derived in DefaultAttachment
by only using a stable unique id (attachment.id) or, if you need persistence for
attachments without an id, build a composite key that includes the message
identity (e.g., combine message.id and attachment.id) so the key is unique per
attachment-instance; update the storageKey assignment used by useCollapse
(storageKey, useCollapse, collapsed, collapse) to stop falling back to
attachment.title_link and ensure keys are undefined when no stable identifier
exists.
In `@apps/meteor/client/components/message/hooks/useCollapse.tsx`:
- Around line 13-16: Guard all sessionStorage reads/writes in the useCollapse
hook by wrapping calls to sessionStorage.getItem and sessionStorage.setItem
(where SESSION_STORAGE_PREFIX and storageKey are used) in try-catch blocks; on
read (e.g., the block that returns stored === 'true') catch and fall back to the
existing collapseByDefault value, and on write (where attachmentCollapsed/state
updates persist) catch and no-op so the in-memory state is preserved; update the
useCollapse hook and any helper functions that reference
SESSION_STORAGE_PREFIX/storageKey to use these safe reads/writes.
🪄 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: 6cce4a73-3c4b-4b0a-9afd-8d1c41c12d24
📒 Files selected for processing (8)
.changeset/fiery-cows-rhyme.mdapps/meteor/client/components/message/MessageCollapsible.tsxapps/meteor/client/components/message/content/attachments/DefaultAttachment.tsxapps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsxapps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsxapps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsxapps/meteor/client/components/message/content/attachments/file/VideoAttachment.tsxapps/meteor/client/components/message/hooks/useCollapse.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). (1)
- GitHub Check: cubic · AI code reviewer
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)
**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation
Files:
apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsxapps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsxapps/meteor/client/components/message/content/attachments/DefaultAttachment.tsxapps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsxapps/meteor/client/components/message/MessageCollapsible.tsxapps/meteor/client/components/message/content/attachments/file/VideoAttachment.tsxapps/meteor/client/components/message/hooks/useCollapse.tsx
🧠 Learnings (9)
📓 Common learnings
Learnt from: rodrigok
Repo: RocketChat/Rocket.Chat PR: 38623
File: apps/meteor/app/lib/server/functions/cleanRoomHistory.ts:146-149
Timestamp: 2026-04-18T12:32:53.425Z
Learning: In `apps/meteor/app/lib/server/functions/cleanRoomHistory.ts` (PR `#38623`), the read-receipt cleanup (both `ReadReceipts.removeByMessageIds` and `ReadReceiptsArchive.removeByMessageIds`) is intentionally only performed in the limited prune path (`limit && selectedMessageIds`). The unlimited/delete-all path (`limit === 0`) deliberately skips cleaning up orphaned read receipts in both hot and cold storage — this is by design. Do not flag this as a bug or missing cleanup in future reviews.
Learnt from: smirk-dev
Repo: RocketChat/Rocket.Chat PR: 39625
File: apps/meteor/app/api/server/v1/push.ts:85-97
Timestamp: 2026-03-14T14:58:58.834Z
Learning: In RocketChat/Rocket.Chat, the `push.token` POST/DELETE endpoints in `apps/meteor/app/api/server/v1/push.ts` were already migrated to the chained router API pattern on `develop` prior to PR `#39625`. `cleanTokenResult` (which strips `authToken` and returns `PushTokenResult`) and `isPushTokenPOSTProps`/`isPushTokenDELETEProps` validators already exist on `develop`. PR `#39625` only migrates `push.get` and `push.info` to the chained pattern. Do not flag `cleanTokenResult` or `PushTokenResult` as newly introduced behavior-breaking changes when reviewing this PR.
📚 Learning: 2026-04-17T23:32:07.223Z
Learnt from: d-gubert
Repo: RocketChat/Rocket.Chat PR: 38357
File: apps/meteor/app/apps/server/converters/messages.ts:348-352
Timestamp: 2026-04-17T23:32:07.223Z
Learning: In `apps/meteor/app/apps/server/converters/messages.ts`, the `timestamp` handler inside `_convertAttachmentsToApp` uses a non-null assertion (`attachment.ts!`) intentionally. The `ts` property on `MessageAttachment` is optional only to accommodate MessageAttachment creation-time scenarios; by the time `_convertAttachmentsToApp` is called, the message has already been stored and the attachment is guaranteed to have a `ts` value. Do not flag this non-null assertion as unsafe during code review.
Applied to files:
apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsxapps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsxapps/meteor/client/components/message/content/attachments/DefaultAttachment.tsxapps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsxapps/meteor/client/components/message/content/attachments/file/VideoAttachment.tsx
📚 Learning: 2026-04-10T22:42:05.539Z
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 40075
File: apps/meteor/client/views/room/modals/FileUploadModal/FileUploadModal.tsx:69-71
Timestamp: 2026-04-10T22:42:05.539Z
Learning: In `apps/meteor/client/views/room/modals/FileUploadModal/FileUploadModal.tsx`, the submit handler converts an empty/whitespace-only description to `undefined` (`description?.trim() || undefined`) intentionally. All downstream image-rendering components (`AttachmentImage`, `ImagePreview`, `ImageItem`, `ImageGallery`) default `undefined` alt to `''`, so the `<img alt="">` attribute is always present. Do not flag this `undefined` conversion as a bug preventing alt text from being cleared.
Applied to files:
apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsxapps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsxapps/meteor/client/components/message/content/attachments/DefaultAttachment.tsxapps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsxapps/meteor/client/components/message/content/attachments/file/VideoAttachment.tsx
📚 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/client/components/message/content/attachments/file/ImageAttachment.tsxapps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsx.changeset/fiery-cows-rhyme.mdapps/meteor/client/components/message/content/attachments/file/VideoAttachment.tsx
📚 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/client/components/message/content/attachments/file/ImageAttachment.tsx
📚 Learning: 2026-03-27T14:52:56.865Z
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 39892
File: apps/meteor/client/views/room/contextualBar/Threads/Thread.tsx:150-155
Timestamp: 2026-03-27T14:52:56.865Z
Learning: In Rocket.Chat, there are two different `ModalBackdrop` components with different prop APIs. During review, confirm the import source: (1) `rocket.chat/fuselage` `ModalBackdrop` uses `ModalBackdropProps` based on `BoxProps` (so it supports `onClick` and other Box/DOM props) and does not have an `onDismiss` prop; (2) `rocket.chat/ui-client` `ModalBackdrop` uses a narrower props interface like `{ children?: ReactNode; onDismiss?: () => void }` and handles Escape keypress and outside mouse-up, and it does not forward arbitrary DOM props such as `onClick`. Flag mismatched props (e.g., `onDismiss` passed to the fuselage component or `onClick` passed to the ui-client component) and ensure the usage matches the correct component being imported.
Applied to files:
apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsxapps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsxapps/meteor/client/components/message/content/attachments/DefaultAttachment.tsxapps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsxapps/meteor/client/components/message/MessageCollapsible.tsxapps/meteor/client/components/message/content/attachments/file/VideoAttachment.tsxapps/meteor/client/components/message/hooks/useCollapse.tsx
📚 Learning: 2026-02-24T19:05:56.710Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 0
File: :0-0
Timestamp: 2026-02-24T19:05:56.710Z
Learning: Rocket.Chat repo context: When a workspace manifest on develop already pins a dependency version (e.g., packages/web-ui-registration → "rocket.chat/ui-contexts": "27.0.1"), a lockfile change in a feature PR that upgrades only that dependency’s resolution is considered a manifest-driven sync and can be kept, preferably as a small "chore: sync yarn.lock with manifests" commit.
Applied to files:
.changeset/fiery-cows-rhyme.md
📚 Learning: 2026-02-24T19:09:09.561Z
Learnt from: ahmed-n-abdeltwab
Repo: RocketChat/Rocket.Chat PR: 38974
File: apps/meteor/app/api/server/v1/im.ts:220-221
Timestamp: 2026-02-24T19:09:09.561Z
Learning: In RocketChat/Rocket.Chat OpenAPI migration PRs for apps/meteor/app/api/server/v1 endpoints, maintainers prefer to avoid any logic changes; style-only cleanups (like removing inline comments) may be deferred to follow-ups to keep scope tight.
Applied to files:
.changeset/fiery-cows-rhyme.md
📚 Learning: 2026-03-16T21:50:37.589Z
Learnt from: amitb0ra
Repo: RocketChat/Rocket.Chat PR: 39676
File: .changeset/migrate-users-register-openapi.md:3-3
Timestamp: 2026-03-16T21:50:37.589Z
Learning: For changes related to OpenAPI migrations in Rocket.Chat/OpenAPI, when removing endpoint types and validators from rocket.chat/rest-typings (e.g., UserRegisterParamsPOST, /v1/users.register) document this as a minor changeset (not breaking) per RocketChat/Rocket.Chat-Open-API#150 Rule 7. Note that the endpoint type is re-exposed via a module augmentation .d.ts in the consuming package (e.g., packages/web-ui-registration/src/users-register.d.ts). In reviews, ensure the changeset clearly states: this is a non-breaking change, the major version should not be bumped, and the changeset reflects a minor version bump. Do not treat this as a breaking change during OpenAPI migrations.
Applied to files:
.changeset/fiery-cows-rhyme.md
🔇 Additional comments (6)
.changeset/fiery-cows-rhyme.md (1)
1-5: Changeset entry is aligned with the implementation scope.Patch bump and release note text are consistent with the PR behavior.
apps/meteor/client/components/message/content/attachments/file/ImageAttachment.tsx (1)
28-28:storageKeywiring looks correct here.Passing
idthroughMessageCollapsibleis consistent with the intended persisted-collapse behavior.apps/meteor/client/components/message/content/attachments/file/GenericFileAttachment.tsx (1)
24-24: Looks good.
idextraction andstorageKeyforwarding are consistent and localized.Also applies to: 72-72
apps/meteor/client/components/message/content/attachments/file/VideoAttachment.tsx (1)
11-11:storageKeyintegration is correct inVideoAttachment.No concerns with this wiring change.
Also applies to: 26-26
apps/meteor/client/components/message/content/attachments/file/AudioAttachment.tsx (1)
10-10: LGTM for this segment.The added
id/storageKeyflow is consistent with the shared collapsible-state strategy.Also applies to: 25-25
apps/meteor/client/components/message/MessageCollapsible.tsx (1)
15-15:MessageCollapsibleprop plumbing is clean and backward-compatible.Optional
storageKeyforwarding touseCollapselooks correct.Also applies to: 18-19
There was a problem hiding this comment.
2 issues found across 8 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/components/message/hooks/useCollapse.tsx">
<violation number="1" location="apps/meteor/client/components/message/hooks/useCollapse.tsx:14">
P2: Direct `sessionStorage` access is unguarded, so attachment collapsing can crash in browsers that block Web Storage or throw on access.</violation>
</file>
<file name="apps/meteor/client/components/message/content/attachments/DefaultAttachment.tsx">
<violation number="1" location="apps/meteor/client/components/message/content/attachments/DefaultAttachment.tsx:30">
P2: Using `attachment.title_link` as a storage key fallback can cause collapse state to bleed between unrelated attachments that happen to share the same URL. Only use `attachment.id` (which is unique per attachment) as the key; fall back to `undefined` (no persistence) when `id` is absent.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Proposed changes
Message attachments (images, GIFs, videos, audio, files) store their collapsed/expanded state only in React component state. When a user switches channels, the message list unmounts and all in-memory state is lost — so every manually collapsed attachment re-expands on return.
This fix replaces the in-memory toggle in
useCollapsewithuseStatebacked bysessionStorage, keyed on the attachment ID. On mount the hook reads the stored value; every toggle writes it back. The user's collapse choice now survives channel switches for the duration of the browser session.Files changed:
useCollapse,MessageCollapsible,ImageAttachment,VideoAttachment,AudioAttachment,GenericFileAttachment,DefaultAttachment.Issue(s)
Closes #40337
Steps to test or reproduce
Further comments
sessionStoragewas chosen overlocalStorageso the state does not persist across browser sessions or bleed between different users on the same machine.Summary by CodeRabbit