perf(ui): lazy load activity feed editor surfaces#29178
Conversation
❌ UI Checkstyle Failed❌ ESLint + Prettier + Organise Imports (src)One or more source files have linting or formatting issues. Affected files
Fix locally (fast — only checks files changed in this branch): make ui-checkstyle-changed |
Code Review ✅ Approved 4 resolved / 4 findingsImplements lazy loading for activity feed editors and adds LRU caching to reduce bundle size and API overhead. Resolves compilation errors, cache invalidation bugs, and logout cleanup issues identified during the review. ✅ 4 resolved✅ Bug: ActivityFeedCache imports non-existent modules (build break)
✅ Security: Stale/cross-user feed applied to state after cache invalidation
✅ Security: clearActivityFeedCache never invoked on logout/user switch
✅ Bug: replaceAll with string replacement mis-handles '$' in FQN/href
OptionsDisplay: compact → Showing less information. Comment with these commands to change:
Was this helpful? React with 👍 / 👎 | Gitar |
Summary
Testing
Ref: https://github.com/open-metadata/openmetadata-collate/issues/4230
Summary by Gitar
RichTextEditorPreviewerV1,Reactions, andActivityFeedEditorinFeedCardBody.myActivityFeedCacheusing an LRU cache and request coalescing inActivityFeedProviderto prevent redundant API calls.BlockEditorPureUtilsto reduce bundle dependencies.formatContentwithformatClientContentacross multiple previewer components.TaskNavigationUtilsand action utilities toTaskActionUtilsfor better modularity.clearActivityFeedCacheto handle session-based cleanup of cached feed data.This will update automatically on new commits.
Greptile Summary
This PR moves activity-feed and editor-heavy surfaces behind
React.lazy+withSuspenseFallbackboundaries to reduce the initial bundle size and improve LCP on the dashboard. It also extracts the client-side markdown-to-HTML formatting logic fromBlockEditorUtilsinto a newBlockEditorPureUtilsfile to eliminate heavy editor dependencies from previewer components.RichTextEditorPreviewerV1,ActivityFeedEditor,Reactions, and a dozen feed sub-components to lazy-loaded variants across five files.formatClientContentinBlockEditorPureUtils.tsas a pure extraction of theformatContent(_, 'client')path, and updates three previewer components and their tests to use it.TaskDescriptionPreviewertests to usefindBy/waitForto accommodate the newly async lazyBlockEditor.Confidence Score: 5/5
Safe to merge — all changes are mechanical lazy-load conversions and a clean utility extraction with no behavioural regressions.
The lazy-loading conversions are straightforward pattern applications of an already-established
withSuspenseFallbackHOC. TheformatClientContentextraction is a direct copy of the'client'branch offormatContent, with a minor improvement (longer FQN matches are now prioritised to prevent substring collisions). No data flow, state management, or API logic is altered.ActivityPanelBody.tsx is worth a second look: it lazy-loads
ProfilePictureandUserPopOverCard, which are small inline components that briefly show a full-layout spinner on first load.Important Files Changed
formatClientContent(extracted fromformatContent(_, 'client')in BlockEditorUtils) andisHTMLString— functionally equivalent to the originals but now duplicatesisHTMLStringin a second file with a subtly different error-handling path.RichTextEditorPreviewerV1,Reactions, andActivityFeedEditorto lazy imports viawithSuspenseFallback; no logic changes.ProfilePictureandUserPopOverCard— small, lightweight inline components — alongside the heavier editor components, using a full-layout-area Loader as fallback, which may cause layout shifts on first render.ErrorPlaceHolder,ConfirmationModal,FeedPanelHeader,TaskFeedCardFromTask,ActivityThread,ActivityThreadList,ActivityFeedEditor) to lazy-loaded variants; no logic changes.BlockEditorand replacesformatContent(markdown, 'client')withformatClientContent(markdown); tests updated to usewaitFor/findByfor async rendering.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD subgraph Before["Before (static imports)"] A[FeedCardBody] -->|static| B[RichTextEditorPreviewerV1] A -->|static| C[Reactions] A -->|static| D[ActivityFeedEditor] E[ActivityPanelBody] -->|static| F[ProfilePicture] E -->|static| G[UserPopOverCard] E -->|static| H[ActivityFeedEditorNew] I[ActivityThreadPanelBody] -->|static| J[ActivityThread] I -->|static| K[ActivityThreadList] I -->|static| L[ConfirmationModal] end subgraph After["After (lazy imports)"] A2[FeedCardBody] -->|lazy + Suspense| B2[RichTextEditorPreviewerV1] A2 -->|lazy + Suspense| C2[Reactions] A2 -->|lazy + Suspense| D2[ActivityFeedEditor] E2[ActivityPanelBody] -->|lazy + Suspense| F2[ProfilePicture] E2 -->|lazy + Suspense| G2[UserPopOverCard] E2 -->|lazy + Suspense| H2[ActivityFeedEditorNew] I2[ActivityThreadPanelBody] -->|lazy + Suspense| J2[ActivityThread] I2 -->|lazy + Suspense| K2[ActivityThreadList] I2 -->|lazy + Suspense| L2[ConfirmationModal] end subgraph Utils["Utility Refactor"] M[BlockEditorUtils.ts] -->|extracted client path| N[BlockEditorPureUtils.ts] N --> O[formatClientContent] N --> P[isHTMLString duplicate] O --> Q[RichTextEditorPreviewerV1] O --> R[RichTextEditorPreviewerNew] O --> S[TaskDescriptionPreviewer] end%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% flowchart TD subgraph Before["Before (static imports)"] A[FeedCardBody] -->|static| B[RichTextEditorPreviewerV1] A -->|static| C[Reactions] A -->|static| D[ActivityFeedEditor] E[ActivityPanelBody] -->|static| F[ProfilePicture] E -->|static| G[UserPopOverCard] E -->|static| H[ActivityFeedEditorNew] I[ActivityThreadPanelBody] -->|static| J[ActivityThread] I -->|static| K[ActivityThreadList] I -->|static| L[ConfirmationModal] end subgraph After["After (lazy imports)"] A2[FeedCardBody] -->|lazy + Suspense| B2[RichTextEditorPreviewerV1] A2 -->|lazy + Suspense| C2[Reactions] A2 -->|lazy + Suspense| D2[ActivityFeedEditor] E2[ActivityPanelBody] -->|lazy + Suspense| F2[ProfilePicture] E2 -->|lazy + Suspense| G2[UserPopOverCard] E2 -->|lazy + Suspense| H2[ActivityFeedEditorNew] I2[ActivityThreadPanelBody] -->|lazy + Suspense| J2[ActivityThread] I2 -->|lazy + Suspense| K2[ActivityThreadList] I2 -->|lazy + Suspense| L2[ConfirmationModal] end subgraph Utils["Utility Refactor"] M[BlockEditorUtils.ts] -->|extracted client path| N[BlockEditorPureUtils.ts] N --> O[formatClientContent] N --> P[isHTMLString duplicate] O --> Q[RichTextEditorPreviewerV1] O --> R[RichTextEditorPreviewerNew] O --> S[TaskDescriptionPreviewer] endReviews (2): Last reviewed commit: "fix(ui): remove activity feed cache spli..." | Re-trigger Greptile