Skip to content

Marking notification, joining community fixes#663

Merged
feruzm merged 4 commits intodevelopfrom
mark
Feb 19, 2026
Merged

Marking notification, joining community fixes#663
feruzm merged 4 commits intodevelopfrom
mark

Conversation

@feruzm
Copy link
Copy Markdown
Member

@feruzm feruzm commented Feb 19, 2026

Fixes ECENCY-NEXT-13YF
Fixes #662

Summary by CodeRabbit

  • New Features

    • Subscription button shows a loading spinner and is disabled while subscribe/unsubscribe is pending.
    • Confirmation popovers for delete actions are always shown but disable confirmation while operations are pending.
  • Bug Fixes

    • Mark-as-read now reliably updates unread counts and handles paginated/large notification lists with improved optimistic updates and rollbacks.
    • Author links inside archived tweet blocks are no longer enhanced.
  • Chores

    • Patch version and changelog updates for SDK and wallets packages.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 19, 2026

📝 Walkthrough

Walkthrough

Standardizes pending-state handling for subscription and entry-delete UI buttons, adds a guard to skip author-link enhancement inside archived tweets, and rewrites the notifications "mark as read" mutation to support infinite-query shapes, unified optimistic updates, and explicit unread-count cache management. Also bumps SDK and wallets package versions and changelogs.

Changes

Cohort / File(s) Summary
Community subscription button
apps/web/src/app/communities/_components/subscription-btn/index.tsx
Subscribe/unsubscribe handlers now call mutate(...) (was mutateAsync(...)); subscribe button always renders when not subscribed, shows Spinner and is disabled while pending.
Entry delete UI
apps/web/src/features/shared/entry-delete-btn/index.tsx
Always renders PopoverConfirm; when isPending the confirm handler is passed as undefined (action disabled) instead of returning early.
Notifications mutation (mark-read)
packages/sdk/src/modules/notifications/mutations/use-mark-notifications-read.ts
Adds InfiniteData typing and runtime guard, markNotificationRead helper, unified optimistic-update flow for infinite queries (pages/items), centralized previousData rollback, explicit unread-count cache keyed by username, uses QueryKeys.notifications._prefix for cancel/invalidate.
Post renderer link enhancer
apps/web/src/features/post-renderer/components/utils/hiveAuthorLinkEnhancer.tsx
Adds guard to skip enhancing author links inside archived tweet blocks by checking nearest .markdown-view text.
SDK & Wallets package metadata
packages/sdk/package.json, packages/sdk/CHANGELOG.md, packages/wallets/package.json, packages/wallets/CHANGELOG.md
Bumps packages/sdk to 2.0.6 and packages/wallets to 1.5.34; adds corresponding changelog entries.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client Component
    participant Mutation as MarkNotificationsRead Mutation
    participant Cache as Query Cache
    participant Server as API Server

    Client->>Mutation: trigger mark-as-read (id?|all)
    Mutation->>Cache: getQueriesData(QueryKeys.notifications._prefix)
    Mutation->>Mutation: detect isInfiniteData or finite
    Mutation->>Cache: store previousData (for rollback)
    alt Infinite data
        Mutation->>Cache: map pages/items -> markNotificationRead
    else Finite data
        Mutation->>Cache: update notification list
    end
    Mutation->>Cache: adjust unreadCount cache (decrement)
    Mutation->>Server: send mutation request
    alt Success
        Server-->>Mutation: server unread count
        Mutation->>Cache: align unreadCount & data with server
    else Error
        Mutation->>Cache: restore previousData and unreadCount
    end
    Cache-->>Client: UI reflects updated cache
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

patch:sdk

Poem

🐰 I hopped in code with tiny paws,
Buttons that spin without applause,
Pages mended, counts made right,
Cache and server sleep tonight,
A carrot cheer — small fixes, big cause! 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes to subscription/unsubscribe buttons and entry-delete-btn using mutate instead of mutateAsync, plus author link enhancement for archived tweets, are not mentioned in linked issue #662. Clarify whether subscription/deletion/author-link changes relate to issue #662's notification cache logic, or if these are separate unrelated fixes that should be in a different PR.
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main changes: notification marking and community joining fixes, matching the primary objectives in the linked issues.
Linked Issues check ✅ Passed The changes implement the core objective from #662: adding array type guards in useMarkNotificationsRead to prevent data.map errors on non-array cached values like unread counts.

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

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

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

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/sdk/src/modules/notifications/mutations/use-mark-notifications-read.ts (1)

109-150: Redundant invalidation: onSuccess + onSettled both invalidate the same queries when marking all.

onSettled (line 146) always invalidates QueryKeys.notifications._prefix, which already covers the !variables.id invalidation in onSuccess (line 127). The onSuccess invalidation is redundant since onSettled fires immediately after.

If the intent is to always refetch after settle, the onSuccess block at lines 126-130 can be removed. Alternatively, if you want to avoid refetching on single-notification success (since the optimistic update already handled it), make onSettled conditional instead:

Option A: Remove redundant onSuccess invalidation (simpler)
       onSuccess?.(unreadCount);
-
-      // If marking all notifications, invalidate to ensure fresh data
-      if (!variables.id) {
-        queryClient.invalidateQueries({
-          queryKey: QueryKeys.notifications._prefix,
-        });
-      }
     },
Option B: Only invalidate onSettled when marking all (preserves optimistic-only for single)
     onSettled: () => {
-      queryClient.invalidateQueries({
-        queryKey: QueryKeys.notifications._prefix,
-      });
+      // Only invalidate for "mark all" to ensure fresh data;
+      // single-notification marking relies on the optimistic update
     },

Remove the onSuccess invalidation and move it here:

+    onSettled: (_data, _error, variables) => {
+      if (!variables.id) {
+        queryClient.invalidateQueries({
+          queryKey: QueryKeys.notifications._prefix,
+        });
+      }
+    },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/sdk/src/modules/notifications/mutations/use-mark-notifications-read.ts`
around lines 109 - 150, The onSuccess handler duplicates invalidation already
done in onSettled: remove the conditional invalidation in onSuccess (the block
checking if (!variables.id) and calling queryClient.invalidateQueries with
QueryKeys.notifications._prefix) so only onSettled performs the global refetch,
or alternatively change onSettled to only invalidate when variables.id is falsy
(check variables.id) and keep the current onSuccess behavior; update the
handlers around the mutation (onSuccess, onSettled) accordingly and keep use of
QueryKeys.notifications._prefix and variables.id as the determining symbols.
apps/web/src/app/communities/_components/subscription-btn/index.tsx (1)

54-65: Good UX improvement—subscribe button stays visible during pending.

One minor note: mutateAsync (line 59) returns a promise, and if it rejects, this becomes an unhandled rejection since there's no .catch(). The same pattern exists on the unsubscribe button (line 46), so this is pre-existing. Consider using mutate instead of mutateAsync if you don't need the returned promise—mutate swallows rejections and routes them through the mutation's onError callback.

♻️ Suggested: use `mutate` instead of `mutateAsync` to avoid unhandled rejections
-            onClick={() => subscribeMutation.mutateAsync({ community: community.name })}
+            onClick={() => subscribeMutation.mutate({ community: community.name })}

Same applies to the unsubscribe button at line 46:

-          onClick={() => unsubscribeMutation.mutateAsync({ community: community.name })}
+          onClick={() => unsubscribeMutation.mutate({ community: community.name })}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/communities/_components/subscription-btn/index.tsx` around
lines 54 - 65, The subscribe/unsubscribe handlers currently call
subscribeMutation.mutateAsync and unsubscribeMutation.mutateAsync which can
create unhandled promise rejections; replace those calls with
subscribeMutation.mutate(...) and unsubscribeMutation.mutate(...) (passing the
same payload { community: community.name }) so rejections are handled by the
mutation's onError callback, or alternatively wrap the mutateAsync calls in
try/catch if you intentionally need the returned promise; update the Button
onClick handlers where subscribeMutation.mutateAsync and
unsubscribeMutation.mutateAsync are used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/sdk/src/modules/notifications/mutations/use-mark-notifications-read.ts`:
- Around line 97-103: When optimistically decrementing unread count using
QueryKeys.notifications.unreadCount, first verify the target notification's
cached read status instead of unconditionally subtracting 1: use
queryClient.getQueryData to fetch the cached notifications list (the same cache
key your list uses), find the notification by id, and only push/set the
unreadKey to currentUnread - 1 if that notification exists and its read field
!== 1; otherwise leave the unread count unchanged (still record previousData and
restore on error as before). Ensure you still handle the id falsy branch
(mark-all) by setting to 0 as currently done.

---

Nitpick comments:
In `@apps/web/src/app/communities/_components/subscription-btn/index.tsx`:
- Around line 54-65: The subscribe/unsubscribe handlers currently call
subscribeMutation.mutateAsync and unsubscribeMutation.mutateAsync which can
create unhandled promise rejections; replace those calls with
subscribeMutation.mutate(...) and unsubscribeMutation.mutate(...) (passing the
same payload { community: community.name }) so rejections are handled by the
mutation's onError callback, or alternatively wrap the mutateAsync calls in
try/catch if you intentionally need the returned promise; update the Button
onClick handlers where subscribeMutation.mutateAsync and
unsubscribeMutation.mutateAsync are used.

In
`@packages/sdk/src/modules/notifications/mutations/use-mark-notifications-read.ts`:
- Around line 109-150: The onSuccess handler duplicates invalidation already
done in onSettled: remove the conditional invalidation in onSuccess (the block
checking if (!variables.id) and calling queryClient.invalidateQueries with
QueryKeys.notifications._prefix) so only onSettled performs the global refetch,
or alternatively change onSettled to only invalidate when variables.id is falsy
(check variables.id) and keep the current onSuccess behavior; update the
handlers around the mutation (onSuccess, onSettled) accordingly and keep use of
QueryKeys.notifications._prefix and variables.id as the determining symbols.

@feruzm feruzm added the patch Bug fixes and patches (1.0.0 → 1.0.1), add this only if any packages/ have patch changes in PR label Feb 19, 2026
@feruzm feruzm merged commit 1b26299 into develop Feb 19, 2026
1 check was pending
@feruzm feruzm deleted the mark branch February 19, 2026 07:03
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
apps/web/src/features/post-renderer/components/utils/hiveAuthorLinkEnhancer.tsx (1)

21-23: Set the processed marker before the archived-tweet early return to avoid repeated traversal.

The el.dataset.enhanced = "true" assignment at line 24 is never reached for skipped elements. On every subsequent applyAuthorLinks call these elements pass the line-13 guard, go through the DOM-connection check, and then re-execute el.closest(".markdown-view")?.textContent?.includes(…) — which reads the entire container's text content — for nothing.

♻️ Proposed fix
+           el.dataset.enhanced = "true";
+
            // Skip mentions inside archived tweet blocks
            if (el.closest(".markdown-view")?.textContent?.includes("Archived Tweet from")) return;
-
-           el.dataset.enhanced = "true";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/features/post-renderer/components/utils/hiveAuthorLinkEnhancer.tsx`
around lines 21 - 23, In applyAuthorLinks, mark elements as processed before any
early returns for archived tweets so skipped nodes don't get re-evaluated on
subsequent runs: set el.dataset.enhanced = "true" immediately after passing the
initial guard (the same place where el.dataset.enhanced is currently assigned)
or move that assignment to just before the archived-tweet check (the if that
calls el.closest(".markdown-view")?.textContent?.includes("Archived Tweet
from")), ensuring the function still returns early for archived tweets but that
those elements are flagged to avoid repeated DOM traversal.
packages/sdk/src/modules/notifications/mutations/use-mark-notifications-read.ts (1)

74-95: Inner isInfiniteData check is redundant — the predicate already guarantees it.

Because the returned data in each tuple from getQueriesData can be of varying structures, the TData generic is advisory — if you provide a specific type it is assumed you are certain each tuple's data entry is of that type. TypeScript will type data as InfiniteNotificationData | undefined after the predicate filter, so at runtime every non-undefined entry is guaranteed to satisfy isInfiniteData. The isInfiniteData(data) call inside forEach can be dropped in favour of just the null-guard.

♻️ Proposed simplification
      infiniteQueries.forEach(([queryKey, data]) => {
-       if (data && isInfiniteData(data)) {
+       if (data) {
          previousData.push([queryKey, data]);
          const updatedData: InfiniteNotificationData = {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/sdk/src/modules/notifications/mutations/use-mark-notifications-read.ts`
around lines 74 - 95, The inner isInfiniteData check is redundant because
getQueriesData was called with a predicate that already ensures each
non-undefined data is InfiniteNotificationData; remove the isInfiniteData(data)
condition inside the forEach and only guard against undefined (i.e., if (data) {
... }), keeping the rest of the logic that pushes to previousData and creates
updatedData using markNotificationRead, then call
queryClient.setQueryData(queryKey, updatedData); this simplifies the loop while
retaining getQueriesData, predicate, InfiniteNotificationData, previousData,
markNotificationRead, and queryClient.setQueryData usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@packages/sdk/src/modules/notifications/mutations/use-mark-notifications-read.ts`:
- Around line 97-117: The review confirms the optimistic unread-count decrement
in useMarkNotificationsRead.ts is correct: ensure no changes are made to the
logic in the block around QueryKeys.notifications.unreadCount, currentUnread,
and the isUnread check (which inspects the captured infiniteQueries pages) —
keep the existing pre-update snapshot usage so that queryClient.setQueryData
only decrements when the notification (id) was originally unread.

---

Nitpick comments:
In
`@apps/web/src/features/post-renderer/components/utils/hiveAuthorLinkEnhancer.tsx`:
- Around line 21-23: In applyAuthorLinks, mark elements as processed before any
early returns for archived tweets so skipped nodes don't get re-evaluated on
subsequent runs: set el.dataset.enhanced = "true" immediately after passing the
initial guard (the same place where el.dataset.enhanced is currently assigned)
or move that assignment to just before the archived-tweet check (the if that
calls el.closest(".markdown-view")?.textContent?.includes("Archived Tweet
from")), ensuring the function still returns early for archived tweets but that
those elements are flagged to avoid repeated DOM traversal.

In
`@packages/sdk/src/modules/notifications/mutations/use-mark-notifications-read.ts`:
- Around line 74-95: The inner isInfiniteData check is redundant because
getQueriesData was called with a predicate that already ensures each
non-undefined data is InfiniteNotificationData; remove the isInfiniteData(data)
condition inside the forEach and only guard against undefined (i.e., if (data) {
... }), keeping the rest of the logic that pushes to previousData and creates
updatedData using markNotificationRead, then call
queryClient.setQueryData(queryKey, updatedData); this simplifies the loop while
retaining getQueriesData, predicate, InfiniteNotificationData, previousData,
markNotificationRead, and queryClient.setQueryData usage.

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

Labels

patch Bug fixes and patches (1.0.0 → 1.0.1), add this only if any packages/ have patch changes in PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant