Skip to content

feat(files): multipart upload module + attach files to comments - #413

Merged
nossila merged 34 commits into
masterfrom
feature/baseapp_files
Oct 7, 2026
Merged

nossila merged 34 commits into
masterfrom
feature/baseapp_files

Conversation

@nossila

@nossila nossila commented Jul 23, 2026

Copy link
Copy Markdown
Member

Summary

Adds a files module (@baseapp-frontend/components/files) — multipart chunked upload plus attaching files to any FilesInterface-enabled object — and wires it into comments. Consumes the backend baseapp_files package (PR on silverlogic/baseapp-backend).

What's included

Files module (packages/components/modules/files)

  • Chunked multipart upload (useChunkedUpload): 5 MB chunks, concurrent parts, XHR progress, exponential-backoff retry, and in-session pause/resume/retry via AbortController. Backed by a zustand upload store.
  • Composable web components: FileUploadTrigger (dropzone or compact icon button), FileUploadList / UploadingFilesList, AttachedFileItem, FileChip, FileThumbnail — split so consumers can mount just the pieces they need and reproduce the BaseApp Figma design.
  • Attach flows: useFileUploadLogic (upload → auto-attach to an existing target) and useDeferredFileAttachments (upload now, attach after the target is created).
  • Uploads are scoped to their owning composer so one composer's progress doesn't leak onto unrelated file lists (they share one global store).
  • Relay artifacts: FileAttachToTarget / FileDelete mutations, FilesList_target on FilesInterface, FileItem_file.
  • Native is a stub (files/native) for a later pass.

Comments integration

  • CommentUpdate: attach icon in the SocialInput action bar; files auto-attach to the existing comment.
  • CommentCreate: attach icon uploads immediately, then files attach to the new comment on successful create (the comment's FilesList_files connection already exists via the CommentItem_comment response, so the @prependEdge lands). Composer shows its own upload progress; completed items hide the progress bar and expose a remove control.

Schema / other

  • schema.graphql synced with backend (ContentPost implements CommentsInterface, files types) and image fields adapted to the String contract across profiles/messages/notifications.

Tests

  • Jest: chunkFile, uploadChunk/uploadChunks (retry/backoff/ETag), the upload store, useFileSelect, and pause/resume paths.
  • pnpm relay compiles per package against the committed schema; tsc --noEmit clean; eslint clean.

Status

Draft — depends on the backend baseapp_files PR. Opening for early review of the module API and comments wiring.

🤖 Generated with Claude Code

nossila and others added 20 commits June 27, 2026 01:19
…ntract

- regenerate packages/components/schema.graphql against the deployed ported backend
  (image/avatar/bannerImage fields are now String scalars, not { url } objects)
- update fragments to select image fields as scalars; drop { url } subselections
- update components/utils to consume image fields as strings (coalesce null -> undefined
  where passed to string|undefined props)
- regenerate Relay __generated__ artifacts
- rebased onto origin/master

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XyuGcRJryi9hQiWRVXU7Js
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146oeiGsELS6QVpVUQHDQXd

# Conflicts:
#	packages/components/modules/profiles/web/ProfileMembers/MemberItem/index.tsx
#	packages/components/schema.graphql
Master's InviteMemberDialog queried image { url }; on this branch Profile.image
is a String scalar, so select it directly and use it as avatarUrl.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146oeiGsELS6QVpVUQHDQXd
README claimed a FileUploadProvider JSX wrapper; the store is a module-level
zustand singleton, so no provider exists. Documented the shared-state
implication for multiple targets and fixed the edges.map any cast.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146oeiGsELS6QVpVUQHDQXd
- Record ETags per chunk index as chunks complete (new recordChunkEtag store
  action); completedChunks was chunkIndex+1, which under 3-way parallel
  uploads mis-counted on out-of-order completion, and etags were only stored
  at the very end — a paused upload resumed with wrong slices and empty parts.
- Resume now uploads exactly the chunks with missing ETags (non-contiguous
  safe) against their original presigned URLs.
- Track initiatedAt/expiresIn from the initiate response; resuming past
  expiry aborts the stale upload (best effort) and transparently re-initiates.
- Extract shared startUpload used by upload/retry/expired-resume.
- Drop parentContentType/parentObjectId from initiate payloads — the API
  accepts parent_id only, so the fields were silently ignored; attaching is
  done via fileAttachToTarget.
- Select id on FilesList file nodes (was masked by the removed any cast);
  remove leftover merge-conflict marker in ProfileMembers MemberItem.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146oeiGsELS6QVpVUQHDQXd
…paths

chunkFile slicing, uploadChunks (out-of-order ETag indexing, retry/backoff,
abort short-circuit), the upload store (recordChunkEtag sparse counting,
pause/resume/retry transitions), and useChunkedUpload (initiate/complete
payloads, sparse resume, expired-URL re-initiation, failed-state handling).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146oeiGsELS6QVpVUQHDQXd
…ume works

Two bugs made in-session pause/resume unreachable from the UI (the jest tests
exercised resumeUpload directly on a hand-crafted PAUSED state, masking both):

- The catch in upload/resume/retry set status=FAILED on any thrown error,
  including the AbortController rejection a pause triggers — clobbering the
  PAUSED the store just set and firing onUploadError for a deliberate pause.
  A shared handleUploadError now leaves PAUSED/ABORTED untouched.
- UploadingFileItem.handleResume called the store's resumeFile (PAUSED→PENDING)
  before resumeUpload, whose guard requires PAUSED — so resume no-op'd and the
  file stranded in PENDING. Removed the redundant call; the hook flips it.
- resumeUpload now computes pending chunks before treating URL expiry as fatal:
  if every chunk already uploaded, it only re-issues the (URL-free) complete
  call instead of discarding the whole upload and re-uploading from byte zero.

Added regression tests for both fixed behaviors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146oeiGsELS6QVpVUQHDQXd
…App design)

Decouple the three concerns so the pieces can be mounted to match the Figma
comment design while keeping today's behaviour as defaults:

- useFileSelect: headless hidden-input select (accept/size/count validation),
  so the trigger is pluggable.
- FileUploadTrigger: owns the upload logic and renders as a dropzone (default)
  or a compact paperclip button (as="button"). FileUpload stays as a
  back-compat dropzone wrapper.
- FileThumbnail + FileChip: presentational 40x40 thumbnail (image or
  type-icon tile) and the compact bordered chip from the design.
- UploadingFileItem / AttachedFileItem gain variant='chip'; AttachedFileItem
  gains editable (× remove on files the user can change).
- FileUploadList gains variant (cards|chips), layout (stack|horizontal), and
  editable — cards/stack remain the defaults so nothing changes for existing
  callers.

Wire comments to the design: CommentItem renders the chip list
(editable while editing); CommentUpdate renders the paperclip trigger button.

Tests: useFileSelect unit coverage; full components suite green (37).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146oeiGsELS6QVpVUQHDQXd
…ion bar

- AttachedFileItem and FileUploadTrigger now use the design-system IconButton
  (which renders a spinner via isLoading) instead of raw MUI: the remove/delete
  action shows isLoading={isDeletingFile}, the attach button isLoading while
  attaching.
- Wire the comment editor's attach icon: SocialInput forwards a new optional
  SocialUpsertActionsProps to its SocialUpsertActions slot, and CommentUpdate
  passes a module-level CommentFilesUpsertActions (target-bound) whose
  AttachmentIcon is the real FileUploadTrigger. Removes the standalone attach
  button that used to sit below the input.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146oeiGsELS6QVpVUQHDQXd
- The attached-file preview used file.thumbnail, which the backend leaves null
  when easy_thumbnails can't generate one, and downloads used the raw `file`
  field (a host-less /media path the frontend origin can't serve). Query the
  absolute `url` field and use it: preview falls back to thumbnail || url so
  images always render, and download opens `url`.
- FileChip max-width 264 -> 232px to match the design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146oeiGsELS6QVpVUQHDQXd
Previously the uploading item decoded the source File (via createObjectURL)
into an image preview. Even for a 40px tile the browser decodes the full-res
bitmap, which is wasteful for large images. Show a generic type icon during
upload; the real thumbnail arrives when FileAttachToTarget prependEdges the
committed File node.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146oeiGsELS6QVpVUQHDQXd
Adds file attachment to CommentCreate, mirroring CommentUpdate. Because the
comment does not exist at selection time, files upload immediately and their
relay ids are held by a new useDeferredFileAttachments hook; on successful
create they are attached to the new comment in one FileAttachToTarget mutation
(the comment's FilesList_files connection already exists via the CommentItem
response, so the prependEdge lands). The attach icon lives in a create-specific
SocialUpsertActions bar and shows a loading state while uploads are in flight.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146oeiGsELS6QVpVUQHDQXd
The upload store is a global singleton, so every FileUploadList (one per
comment) was rendering all in-flight uploads — a file attached during comment
creation appeared on existing comments. Tag each upload with a scope: the
auto-attach path scopes to its target id; the deferred (create) path scopes to
a per-composer id. FileUploadList now shows only uploads matching its target,
and CommentCreate renders a new target-less UploadingFilesList for its own scope.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146oeiGsELS6QVpVUQHDQXd
In a composer (e.g. new comment) an uploaded-but-not-yet-attached file kept
showing its progress bar and a pause control, with no way to undo it. Once
COMPLETED the chip now shows the file size instead of the bar (card hides
FileProgress) and offers a remove control. Removing works correctly because the
deferred hook now reads the completed uploads for its scope straight from the
store at attach time (fileRelayId is persisted on completion), so a removed
file is excluded from the attach; cleanup is scoped to the composer instead of
clearing all completed uploads globally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146oeiGsELS6QVpVUQHDQXd
@changeset-bot

changeset-bot Bot commented Jul 23, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 2bb82ca

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

This PR includes no changesets

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

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

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

@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 151 files, which is 51 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 511ee8a4-04a2-494a-9749-571edb378f93
📥 Commits

Reviewing files that changed from the base of the PR and between 3b07298 and 2bb82ca.

📒 Files selected for processing (151)
  • .changeset/payments-navigation-hrefs.md
  • packages/components/CHANGELOG.md
  • packages/components/modules/__shared__/web/ReactionButton/__storybook__/mockResolvers.ts
  • packages/components/modules/__shared__/web/SocialInput/index.tsx
  • packages/components/modules/__shared__/web/SocialInput/types.ts
  • packages/components/modules/activity-log/common/graphql/queries/ActivityLogsFragment.ts
  • packages/components/modules/activity-log/web/ActivityLogComponent/LogGroups/__storybook__/stories.tsx
  • packages/components/modules/activity-log/web/ActivityLogComponent/LogGroups/index.tsx
  • packages/components/modules/activity-log/web/ActivityLogComponent/LogItem/__storybook__/stories.tsx
  • packages/components/modules/activity-log/web/ActivityLogComponent/__storybook__/mockResolvers.ts
  • packages/components/modules/comments/common/graphql/queries/CommentItem.ts
  • packages/components/modules/comments/common/hooks/useCommentCreateForm/index.ts
  • packages/components/modules/comments/common/hooks/useCommentCreateForm/types.ts
  • packages/components/modules/comments/native/CommentItem/index.tsx
  • packages/components/modules/comments/web/CommentCreate/CommentFilesUpsertActions/index.tsx
  • packages/components/modules/comments/web/CommentCreate/CommentFilesUpsertActions/styled.tsx
  • packages/components/modules/comments/web/CommentCreate/CommentFilesUpsertActions/types.ts
  • packages/components/modules/comments/web/CommentCreate/index.tsx
  • packages/components/modules/comments/web/CommentItem/__storybook__/mockResolvers.ts
  • packages/components/modules/comments/web/CommentItem/index.tsx
  • packages/components/modules/comments/web/CommentUpdate/CommentFilesUpsertActions/index.tsx
  • packages/components/modules/comments/web/CommentUpdate/CommentFilesUpsertActions/styled.tsx
  • packages/components/modules/comments/web/CommentUpdate/CommentFilesUpsertActions/types.ts
  • packages/components/modules/comments/web/CommentUpdate/__storybook__/mockResolvers.ts
  • packages/components/modules/comments/web/CommentUpdate/index.tsx
  • packages/components/modules/comments/web/Comments/__storybook__/mockResolvers.ts
  • packages/components/modules/comments/web/Comments/__tests__/__mocks__/constants.ts
  • packages/components/modules/comments/web/Comments/__tests__/__mocks__/requests.ts
  • packages/components/modules/comments/web/Comments/__tests__/__mocks__/resolvers.ts
  • packages/components/modules/comments/web/CommentsList/__storybook__/mockResolvers.ts
  • packages/components/modules/content-feed/common/graphql/fragments/ContentPostImage.ts
  • packages/components/modules/content-feed/web/PostHeader/index.tsx
  • packages/components/modules/content-feed/web/PostImageSlide/index.tsx
  • packages/components/modules/files/README.md
  • packages/components/modules/files/common/constants.ts
  • packages/components/modules/files/common/context/FileUploadProvider/index.tsx
  • packages/components/modules/files/common/context/FileUploadProvider/types.ts
  • packages/components/modules/files/common/context/__tests__/fileUploadStore.test.ts
  • packages/components/modules/files/common/context/useFileUpload/index.ts
  • packages/components/modules/files/common/context/useScopedUploads/index.ts
  • packages/components/modules/files/common/context/useScopedUploads/types.ts
  • packages/components/modules/files/common/graphql/fragments/FileItem.ts
  • packages/components/modules/files/common/graphql/mutations/FileAttachToTarget.ts
  • packages/components/modules/files/common/graphql/mutations/FileDelete.ts
  • packages/components/modules/files/common/graphql/queries/FilesList.ts
  • packages/components/modules/files/common/hooks/useChunkedUpload/__tests__/useChunkedUpload.test.ts
  • packages/components/modules/files/common/hooks/useChunkedUpload/index.ts
  • packages/components/modules/files/common/hooks/useChunkedUpload/types.ts
  • packages/components/modules/files/common/hooks/useDeferredFileAttachments/index.ts
  • packages/components/modules/files/common/hooks/useDeferredFileAttachments/types.ts
  • packages/components/modules/files/common/hooks/useFileDeleteLogic/index.ts
  • packages/components/modules/files/common/hooks/useFileDeleteLogic/types.ts
  • packages/components/modules/files/common/hooks/useFileDownloadLogic/index.ts
  • packages/components/modules/files/common/hooks/useFileDownloadLogic/types.ts
  • packages/components/modules/files/common/hooks/useFileSelect/__tests__/useFileSelect.test.ts
  • packages/components/modules/files/common/hooks/useFileSelect/index.ts
  • packages/components/modules/files/common/hooks/useFileSelect/types.ts
  • packages/components/modules/files/common/hooks/useFileUploadLogic/index.ts
  • packages/components/modules/files/common/hooks/useFileUploadLogic/types.ts
  • packages/components/modules/files/common/index.ts
  • packages/components/modules/files/common/types.ts
  • packages/components/modules/files/common/utils/__tests__/chunkFile.test.ts
  • packages/components/modules/files/common/utils/__tests__/uploadChunks.test.ts
  • packages/components/modules/files/common/utils/chunkFile.ts
  • packages/components/modules/files/common/utils/formatters.ts
  • packages/components/modules/files/common/utils/index.ts
  • packages/components/modules/files/common/utils/localId.ts
  • packages/components/modules/files/common/utils/uploadChunk.ts
  • packages/components/modules/files/common/utils/uploadChunks.ts
  • packages/components/modules/files/native/index.ts
  • packages/components/modules/files/web/AttachedFileItem/index.tsx
  • packages/components/modules/files/web/AttachedFileItem/types.ts
  • packages/components/modules/files/web/FileChip/index.tsx
  • packages/components/modules/files/web/FileChip/types.ts
  • packages/components/modules/files/web/FileThumbnail/index.tsx
  • packages/components/modules/files/web/FileThumbnail/types.ts
  • packages/components/modules/files/web/FileUpload/index.tsx
  • packages/components/modules/files/web/FileUpload/types.ts
  • packages/components/modules/files/web/FileUploadDropzone/index.tsx
  • packages/components/modules/files/web/FileUploadDropzone/types.ts
  • packages/components/modules/files/web/FileUploadList/UploadingFileItem/FileProgress.tsx
  • packages/components/modules/files/web/FileUploadList/UploadingFileItem/index.tsx
  • packages/components/modules/files/web/FileUploadList/UploadingFileItem/types.ts
  • packages/components/modules/files/web/FileUploadList/index.tsx
  • packages/components/modules/files/web/FileUploadList/types.ts
  • packages/components/modules/files/web/FileUploadTrigger/index.tsx
  • packages/components/modules/files/web/FileUploadTrigger/types.ts
  • packages/components/modules/files/web/UploadingFilesList/index.tsx
  • packages/components/modules/files/web/UploadingFilesList/types.ts
  • packages/components/modules/files/web/index.ts
  • packages/components/modules/messages/common/graphql/fragments/AddContactToGroupItem.ts
  • packages/components/modules/messages/common/graphql/fragments/MessagesList.ts
  • packages/components/modules/messages/common/graphql/fragments/RoomTitle.ts
  • packages/components/modules/messages/common/graphql/fragments/SingleChatDetailsFragment.ts
  • packages/components/modules/messages/common/utils.ts
  • packages/components/modules/messages/native/AddContactToGroupPage/GroupsList/GroupListItem/index.tsx
  • packages/components/modules/messages/native/ChatCreate/CreateRoomList/CreateGroupListItem/index.tsx
  • packages/components/modules/messages/native/ChatCreate/CreateRoomList/CreateRoomListItem/index.tsx
  • packages/components/modules/messages/native/ChatCreate/CreateRoomList/SelectedGroupMembers/index.tsx
  • packages/components/modules/messages/native/ChatRoomPage/ChatRoomHeader/index.tsx
  • packages/components/modules/messages/native/ChatRoomPage/MessagesList/MessagesGroup/UserMessage/index.tsx
  • packages/components/modules/messages/native/GroupChatDetails/index.tsx
  • packages/components/modules/messages/native/GroupDetailsPage/GroupProfile/index.tsx
  • packages/components/modules/messages/native/GroupDetailsPage/Members/MemberItem/index.tsx
  • packages/components/modules/messages/native/RoomsList/ChatCard/ChatCardComponent/index.tsx
  • packages/components/modules/messages/native/SingleChatDetailsPage/ProfileSummary/index.tsx
  • packages/components/modules/messages/native/SingleChatDetailsPage/index.tsx
  • packages/components/modules/messages/web/AddContactToGroupDialog/GroupsList/GroupItem/index.tsx
  • packages/components/modules/messages/web/AllChatRoomsList/ChatRoomItem/index.tsx
  • packages/components/modules/messages/web/ChatCreate/ChatRoomListItem/index.tsx
  • packages/components/modules/messages/web/GroupChatDetails/Body/index.tsx
  • packages/components/modules/messages/web/GroupChatDetails/ProfileCard/index.tsx
  • packages/components/modules/messages/web/GroupChatDetails/index.tsx
  • packages/components/modules/messages/web/GroupChatEdit/AddMemberCard/index.tsx
  • packages/components/modules/messages/web/GroupChatEdit/AddedMemberCard/index.tsx
  • packages/components/modules/messages/web/GroupChatEdit/__tests__/__mocks__/requests.ts
  • packages/components/modules/messages/web/MessagesList/MessagesGroup/UserMessage/index.tsx
  • packages/components/modules/messages/web/MessagesList/MessagesGroup/__storybook__/MessagesGroupWithQuery/index.tsx
  • packages/components/modules/messages/web/MessagesList/MessagesGroup/__storybook__/mockResolvers.ts
  • packages/components/modules/messages/web/MessagesList/MessagesGroup/__storybook__/stories.tsx
  • packages/components/modules/messages/web/MessagesList/__storybook__/mockResolvers.ts
  • packages/components/modules/messages/web/ProfileSummary/Body/index.tsx
  • packages/components/modules/messages/web/__shared__/EditGroupTitleAndImage/index.tsx
  • packages/components/modules/messages/web/__shared__/GroupChatMembersList/ProfileCard/index.tsx
  • packages/components/modules/navigations/web/Header/AccountMenu/AccountPopover/__storybook__/mockResolvers.ts
  • packages/components/modules/navigations/web/Header/AccountMenu/AccountPopover/__tests__/__mocks__/profiles.ts
  • packages/components/modules/notifications/common/graphql/queries/NotificationItem.ts
  • packages/components/modules/notifications/native/NotificationsList/NotificationItem/CommentCreated/index.tsx
  • packages/components/modules/notifications/native/NotificationsList/NotificationItem/CommentReply/index.tsx
  • packages/components/modules/notifications/native/NotificationsList/NotificationItem/ReactionCreated/index.tsx
  • packages/components/modules/notifications/web/NotificationsList/NotificationItem/CommentCreated/index.tsx
  • packages/components/modules/notifications/web/NotificationsList/NotificationItem/CommentReply/index.tsx
  • packages/components/modules/notifications/web/NotificationsList/NotificationItem/ReactionCreated/index.tsx
  • packages/components/modules/pages/common/graphql/fragments/Metadata.ts
  • packages/components/modules/profiles/common/graphql/fragments/InviteMembersSearch.ts
  • packages/components/modules/profiles/common/graphql/fragments/ProfileComponent.ts
  • packages/components/modules/profiles/common/graphql/fragments/ProfileItem.ts
  • packages/components/modules/profiles/common/utils/getProfileDefaultValues/index.ts
  • packages/components/modules/profiles/native/ProfileComponent/index.tsx
  • packages/components/modules/profiles/native/ProfileSettingsComponent/index.tsx
  • packages/components/modules/profiles/web/ProfileComponent/index.tsx
  • packages/components/modules/profiles/web/ProfileMembers/InviteMemberDialog/MemberSearch/index.tsx
  • packages/components/modules/profiles/web/ProfileMembers/MemberItem/index.tsx
  • packages/components/modules/profiles/web/ProfileMembers/__tests__/__mocks__/requests.ts
  • packages/components/modules/profiles/web/ProfileMembers/__tests__/__utils__/Members.story.tsx
  • packages/components/modules/profiles/web/ProfileSettingsComponent/__tests__/__mocks__/requests.ts
  • packages/components/modules/profiles/web/ProfileSettingsComponent/index.tsx
  • packages/components/modules/profiles/web/profile-popover/ProfilesList/ProfileMenuItem/index.tsx
  • packages/components/modules/profiles/web/profile-popover/ProfilesList/index.tsx
  • packages/components/package.json
  • packages/components/schema.graphql

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/baseapp_files
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

❤️ Share

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

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

nossila and others added 2 commits September 11, 2026 17:45
Master refactored the comments module into a shared hook layer (#392) and
converted tailwind to MUI styled components (#408), touching the same
components this branch extends with file attachments. Resolved by taking
master's structure and re-applying the attachment feature on top:

- CommentCreate/CommentUpdate/CommentItem rebuilt on master's
  useCommentCreateForm / useCommentUpdateForm / useCommentItem hooks and
  styled components, keeping the file upsert actions, UploadingFilesList
  and FileUploadList.
- useCommentCreateForm now surfaces the created comment id to onSuccess,
  which the deferred-attachment flow needs to attach uploads once the
  comment exists.

This branch makes thumbnails resolve to a plain String, so master's
`image/avatar { url }` selections and readers had to be adapted:
AddContactToGroupItem's fragment plus three consumers new in master.
Relay compiles and tsc is clean; the remaining lint output is
pre-existing warnings only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013LkamMd6XMPeYNHbAV1DZ9
Three unrelated CI failures on this branch, all fallout from the master merge:

- The components package moved to Vitest on master. The shared setup aliases
  globalThis.jest = vi, so jest.fn() kept working and hid the problem, but
  vi.mock is hoisted by the transform only when written literally — reached
  through the alias it never hoists, so the real module was imported and the
  "mock" had no mock methods. Ported the three files-module specs to vi,
  including jest.requireActual -> await vi.importActual.

- The image/avatar -> String schema change was only half applied to fixtures:
  25 sites across 5 files still used the old { url } shape, plus a
  createNodeProfile helper whose parameter was typed { url: string }. The
  earlier sweep caught readers, not mock payload shapes.

- Math.random() generated local-only ids (upload store keys, composer scopes)
  and tripped Sonar's S2245. Replaced with a counter-based nextLocalId, which
  also avoids crypto.randomUUID's secure-context requirement under jsdom.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013LkamMd6XMPeYNHbAV1DZ9
…ragment selects

CommentItem's fragment now spreads ...FilesList_target, so every mocked Comment
must carry isFilesEnabled, filesCount, files and the __isFilesInterface/__isNode
discriminators. Shapes taken from a live backend response, not guessed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013LkamMd6XMPeYNHbAV1DZ9
nossila and others added 3 commits October 6, 2026 13:44
Master migrated component tests from Cypress to Playwright (#427), deleting
every .cy.tsx spec. Conflict was Members.cy.tsx — deleted there, modified here
(thumbnail String conversion); took the deletion and applied the equivalent
conversion to its replacement, Members.story.tsx.

Also converted two single-line `image: { url }` mocks in GroupChatEdit that the
earlier multi-line sweep missed. `image`/`avatar` are String on this branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013LkamMd6XMPeYNHbAV1DZ9
groupDetailsResponse still wrapped the image as `{ url }` behind a conditional,
which the literal-pattern sweep could not match. `image` is a String on this
branch, so the avatar rendered nothing and the pre-fill assertion failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013LkamMd6XMPeYNHbAV1DZ9
attachTo is async, so calling it bare from onSuccess left a floating promise
(Sonar typescript:S9383, the sole MAJOR holding new-code reliability at C).
Mutation failures are already reported through attachTo's own onError, so this
stays fire-and-forget — the catch just keeps a rejection from escaping.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013LkamMd6XMPeYNHbAV1DZ9
@nossila
nossila marked this pull request as ready for review October 6, 2026 14:52

@matheusysd matheusysd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Critical] Breaking change without a changeset. image { url } → String across ~10 modules breaks Relay compilation for every consumer/fork and requires a lockstep backend deploy, but there's no .changeset/version bump.

Fix: add a major changeset with a migration note (image { url } → image, x.image?.url → x.image ?? undefined). Ideally split the image-contract change into its own PR so the files module can ship independently.

Comment thread packages/components/modules/files/common/hooks/useFileUploadLogic/index.ts Outdated
Comment thread packages/components/modules/files/common/hooks/useFileUploadLogic/index.ts Outdated
nossila and others added 4 commits October 6, 2026 20:18
…re-renders

Addresses matheusysd's review on #413.

Cross-composer file loss: clearCompleted() deleted every COMPLETED upload in the
global store, so attaching on comment A wiped files waiting in the create
composer. Attach-side cleanup now removes exactly the files it just attached, and
the store gains clearScope() for the "discard this composer" case.

Partial batches: Promise.all meant one paused or failed upload discarded the ids
of the ones that finished. Now allSettled + attach the fulfilled ids. Resume and
retry land outside the original batch, so useChunkedUpload's onUploadComplete
attaches late arrivals too, deduped against the batch so nothing attaches twice.

Wrong-comment attachment: attachTo now snapshots its scope before awaiting, so
files added while it settles belong to the next comment, and it releases the
whole snapshot afterwards so a paused upload cannot ride along. The composer also
blocks submit via hasPendingFiles while anything is unfinished.

Re-render storm: every progress tick replaces the store's Map, so whole-store
subscribers re-rendered on each XHR event. Lists now read through a scoped
useScopedUploads selector and action consumers pick individual actions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013LkamMd6XMPeYNHbAV1DZ9
The image/avatar/bannerImage fields went from an object to a String across the
components package, which breaks Relay compilation for every consumer and needs
the matching backend. That is a major for @baseapp-frontend/components and had no
changeset.

Records the migration (drop the selection set; `x.image?.url` -> `x.image ?? undefined`),
names the fragments changed so a fork can diff against them, and notes that mock
payloads need the same fix since a stale `{ url }` fixture renders nothing silently
instead of failing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013LkamMd6XMPeYNHbAV1DZ9
nossila and others added 3 commits October 7, 2026 00:59
Per anicioalexandre's review and the styling conventions: styled() is the
primary approach, Tailwind the last resort. The grid moves into a co-located
styled.tsx using theme.spacing(1), which is the same 8px `gap-2` resolved to —
no visual change.

Applied to the CommentUpdate bar too, which carried the identical class.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013LkamMd6XMPeYNHbAV1DZ9
Picks up the fileAttachToTarget example fix (FilesInterface, not
CommentsInterface) plus drift the committed snapshot had missed: field
descriptions, and several File fields tightening from nullable to non-null
(fileContentType, fileName, name, description, uploadId). Relay artifacts
recompiled; tsc clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013LkamMd6XMPeYNHbAV1DZ9
@sonarqubecloud

sonarqubecloud Bot commented Oct 7, 2026

Copy link
Copy Markdown

@nossila
nossila merged commit 90f89a0 into master Oct 7, 2026
9 checks passed
@nossila
nossila deleted the feature/baseapp_files branch October 7, 2026 17:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants