Skip to content

feat(dashboard): Conversations page#10739

Merged
scopsy merged 4 commits intonextfrom
conversations-page
Apr 15, 2026
Merged

feat(dashboard): Conversations page#10739
scopsy merged 4 commits intonextfrom
conversations-page

Conversation

@scopsy
Copy link
Copy Markdown
Contributor

@scopsy scopsy commented Apr 15, 2026

What changed? Why was the change needed?

What changed

Added a new Conversations page to the dashboard that enables users to browse, filter, and view conversation details. This includes backend support for agent name resolution in conversation activities and a complete frontend implementation with filtering, pagination, activity timeline, and detailed conversation viewing capabilities.

Affected areas

@novu/api-service: Updated the HandleAgentReply usecase to fetch agent details by identifier before delivering messages. The agent name is now resolved and passed to deliverMessage() to ensure conversation activities include the agent's display name via the senderName field. Added an agent identity validation guard to prevent mismatched agent identifiers.

@novu/dashboard: Introduced a comprehensive conversations feature with new API client methods (getConversationsList, getConversation, getConversationActivities), React hooks for data fetching (useFetchConversations, useFetchConversation, useFetchConversationActivities), and UI components for the conversations page (filters, table with rows, detail panel, timeline, status badges, empty states). Added URL-synchronized filter state management via useConversationUrlState hook, supporting filters for date range, subscriber, provider, and conversation ID. Integrated the new conversations tab into the activity feed page with feature-flag support. Updated routes to include the new /activity/conversations path.

Submodule reference (.source): Updated to a new commit hash reflecting changes in the referenced subproject.

Key technical decisions

  • Filter state is synchronized with URL query parameters, enabling shareable filtered views and browser back/forward navigation.
  • Pagination state (page size) is persisted to localStorage via usePersistedPageSize for consistent UX across sessions.
  • Conversation selection and detail viewing uses a resizable panel group (right panel) that can be toggled and is only rendered when a conversation is selected.
  • Agent name resolution happens at delivery time on the backend, preventing stale or missing names in activity records.

Testing

No test files were added in this PR. Manual verification of the conversations page UI, filtering behavior, pagination, and timeline rendering would be necessary.

Screenshots

Expand for optional sections

Related enterprise PR

Special notes for your reviewer

scopsy added 2 commits April 15, 2026 19:19
Introduce a conversations feature: adds API client (conversations endpoints and DTOs) and types, plus React UI components (filters, table, table rows, detail/overview, timeline, status badge, empty state, content) to browse and inspect conversations. Adds hooks for data fetching and URL state (use-fetch-conversations, use-fetch-conversation-activities, use-conversation-url-state, use-conversation-url-state) with React Query integration, pagination and persisted page-size support. Updates query-keys and routes utilities and makes small adjustments in main.tsx and activity-feed.tsx to integrate the new feature. Supports filtering, date ranges, providers, pagination and a resizable detail panel with timeline.
Backend: fetch agent by identifier and pass agent name (senderName) into deliverMessage so outgoing ConversationActivity records include the agent's display name. Inject AgentRepository and adjust types/imports accordingly.

Dashboard: add ParticipantSubscriberData and ParticipantAgentData types and include subscriber/agent fields on ConversationParticipantDto. Use subscriber first/last name and avatar for display, resolve agent name from participant.agent.name when present, replace vertical Separator with a simple divider element, and pass currentPageItemsCount to the pagination footer.

Also update submodule reference (.source) to the new commit.
@netlify
Copy link
Copy Markdown

netlify bot commented Apr 15, 2026

Deploy preview added

Name Link
🔨 Latest commit 63b00ca
🔍 Latest deploy log https://app.netlify.com/projects/dashboard-v2-novu-staging/deploys/69dfcd374b7b4a000820cf66
😎 Deploy Preview https://deploy-preview-10739.dashboard-v2.novu-staging.co
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 15, 2026

Hey there and thank you for opening this pull request! 👋

We require pull request titles to follow specific formatting rules and it looks like your proposed title needs to be adjusted.

Your PR title is: feat(dashboard): Conversations page

Requirements:

  1. Follow the Conventional Commits specification
  2. As a team member, include Linear ticket ID at the end: fixes TICKET-ID or include it in your branch name

Expected format: feat(scope): Add fancy new feature fixes NOV-123

Details:

PR title must end with 'fixes TICKET-ID' (e.g., 'fixes NOV-123') or include ticket ID in branch name

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 15, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Introduces a conversations feature to the dashboard with API endpoints, React components, and hooks for fetching, filtering, and displaying conversations; updates the agent reply usecase to validate agent identifiers before delivery; and integrates a conversations tab in the activity feed.

Changes

Cohort / File(s) Summary
Submodule Reference
.source
Updated pinned commit hash from 6982551... to 791db25... for referenced subproject.
Agent Reply Usecase
apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts
Added AgentRepository injection and pre-delivery validation: fetches agent by environment, organization, and identifier; throws NotFoundException if missing; validates agent _id matches conversation's agent ID; throws ForbiddenException on mismatch. Extended deliverMessage signature with optional agentName parameter for activity records. Minor formatting adjustments.
Conversations API & Types
apps/dashboard/src/api/conversations.ts, apps/dashboard/src/types/conversation.ts, apps/dashboard/src/components/conversations/conversation-query-keys.ts
New API module with getConversationsList, getConversation, getConversationActivities functions and related DTOs; new type definitions for conversation filters, URL state, and filter data; query key constants for React Query caching.
Conversations Components
apps/dashboard/src/components/conversations/conversation-*.tsx (8 files: detail, overview, status-badge, table-row, timeline, constants, conversations-content, conversations-empty-state, conversations-filters, conversations-table)
New UI components for displaying conversations: detail view with navigation, overview card with metadata, status badge, table rows with selection, timeline of activities with message/signal rendering, filters panel with date range/provider/subscriber/ID inputs, empty states, and main content layout. Accompanying constants for filter defaults and provider options.
Conversation Hooks
apps/dashboard/src/hooks/use-fetch-conversation*.ts (2 files: use-fetch-conversation-activities, use-fetch-conversations), apps/dashboard/src/hooks/use-conversation-url-state.ts
New custom hooks: useFetchConversations for paginated list fetching, useFetchConversation and useFetchConversationActivities for detail/activity data; useConversationUrlState for syncing selection and filters to URL query parameters.
Routes & Navigation
apps/dashboard/src/utils/routes.ts, apps/dashboard/src/main.tsx, apps/dashboard/src/pages/activity-feed.tsx
Added ACTIVITY_CONVERSATIONS route constant; wired new route in main router; added feature-flagged conversations tab to activity feed with conditional redirect when feature is disabled.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • djabarovgeorge
  • LetItRock
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title follows Conventional Commits format with valid type (feat) and scope (dashboard), uses imperative/descriptive language, but lacks a Linear ticket reference required by guidelines. Verify if a Linear ticket exists for this PR and append it as 'fixes XXX-XXXX' to the title if applicable; otherwise, confirm the ticket reference is not required for this PR.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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.

❤️ 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: 8

🧹 Nitpick comments (3)
apps/dashboard/src/utils/query-keys.ts (1)

42-43: Consider co-locating conversation query keys with the conversations feature.

Keeping these keys next to apps/dashboard/src/api/conversations.ts (or feature hooks) improves discoverability and reduces cross-module coupling for future feature changes.

As per coding guidelines Co-locate query keys and fetcher functions in src/api/ or alongside the feature they belong to.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/utils/query-keys.ts` around lines 42 - 43, The query key
constants fetchConversations and fetchConversation are defined in a central
query-keys module but should be co-located with the conversations feature; move
these constants out of query-keys.ts and into the conversations feature (e.g.,
alongside the fetcher functions or hooks in the conversations API module where
functions like the conversations fetcher or useConversations hook live), then
update any imports that reference fetchConversations/fetchConversation to import
from the new module to improve discoverability and reduce cross-module coupling.
apps/dashboard/src/components/conversations/conversations-filters.tsx (1)

24-27: Reuse shared PROVIDER_OPTIONS instead of duplicating provider mapping.

This mapping duplicates apps/dashboard/src/components/conversations/constants.ts and can drift over time. Prefer importing the shared constant.

♻️ Suggested cleanup
-import { CONVERSATIONAL_PROVIDERS } from '@novu/shared';
+import { PROVIDER_OPTIONS } from './constants';
@@
-const PROVIDER_OPTIONS = CONVERSATIONAL_PROVIDERS.filter((p) => !p.comingSoon).map((p) => ({
-  label: p.displayName,
-  value: p.providerId,
-}));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/conversations/conversations-filters.tsx` around
lines 24 - 27, The local PROVIDER_OPTIONS duplicates the provider mapping;
remove this duplicate and import the shared PROVIDER_OPTIONS from the
conversations/constants module instead (use the existing
CONVERSATIONAL_PROVIDERS mapping there) so the component reuses the canonical
source; if the shared constant doesn't already filter out comingSoon, either
import the shared CONVERSATIONAL_PROVIDERS and apply .filter(p =>
!p.comingSoon).map(...) once in the shared module or import the shared
PROVIDER_OPTIONS directly and replace all local usages in this file (e.g., where
PROVIDER_OPTIONS and CONVERSATIONAL_PROVIDERS are referenced).
apps/dashboard/src/components/conversations/conversation-timeline.tsx (1)

117-125: Add button semantics for expand/collapse state.

Consider adding type="button" and aria-expanded to make the toggle behavior clearer to assistive tech.

Suggested fix
       {isLong && (
         <button
+          type="button"
+          aria-expanded={expanded}
           onClick={() => setExpanded(!expanded)}
           className="text-text-soft flex shrink-0 items-center gap-0.5"
         >

As per coding guidelines apps/dashboard/**: “Review with focus on UX, accessibility, and performance.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/conversations/conversation-timeline.tsx` around
lines 117 - 125, The toggle button for expanding messages (the element using
onClick={() => setExpanded(!expanded)} with RiExpandUpDownLine and the expanded
state) lacks proper semantics; update the button element to include
type="button" to prevent accidental form submission and add
aria-expanded={expanded} so assistive tech can detect the current state
(optionally add aria-controls referencing the ID of the collapsible content).
Ensure you keep the existing onClick and text content while adding these
attributes.
🤖 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/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts`:
- Around line 58-67: You are resolving agent info eagerly from
command.agentIdentifier via agentRepository.findOne and setting agentName, but
you must validate that this identity matches the conversation._agentId used for
delivery and avoid the lookup when the message is only a resolve/signals type;
update handle-agent-reply.usecase.ts to (1) lazily fetch agent data only when
required for writing activity/senderName (i.e., not for resolve/signals), (2)
when fetching via agentRepository.findOne, cross-check the returned agent._id
(or identifier) against conversation._agentId and throw or handle an
authorization/error if they mismatch, and (3) ensure agentName is populated from
the validated agent after the check so activity writes cannot record mismatched
agentId/senderName.

In `@apps/dashboard/src/components/conversations/conversation-detail.tsx`:
- Around line 29-47: The icon-only navigation and close buttons (the elements
invoking onNavigate('prev'), onNavigate('next') and onClose that render
RiArrowUpSLine, RiArrowDownSLine and RiCloseFill) must include accessible labels
and explicit button type to satisfy a11y: add type="button" and aria-label
attributes (e.g., aria-label="Previous conversation", "Next conversation",
"Close conversation" or use passed-in label props) to each respective button and
ensure the onClick handlers remain unchanged; update the JSX for those buttons
to include these attributes so screen readers receive meaningful names.

In `@apps/dashboard/src/components/conversations/conversation-status-badge.tsx`:
- Line 27: The current logic sets const config = STATUS_CONFIG[status] ||
STATUS_CONFIG.active which silently maps any unrecognized status to the
"active/OPEN" styling; change this so unknown statuses render a neutral/unknown
state instead: look up STATUS_CONFIG[status] into config and if it's undefined,
set config to a dedicated STATUS_CONFIG.unknown (or a constructed neutral
config) and ensure the component (ConversationStatusBadge) uses that config so
unknown statuses display an "UNKNOWN"/neutral label and styling rather than
defaulting to OPEN.

In `@apps/dashboard/src/components/conversations/conversation-table-row.tsx`:
- Around line 52-55: The TableRow currently handles only mouse clicks via
onClick={handleClick} and lacks keyboard and ARIA semantics; make it
keyboard-accessible by adding tabIndex={0} so it can receive focus, add
onKeyDown that listens for Enter and Space (triggering handleClick;
preventDefault for Space to avoid page scroll), and expose selection state with
aria-selected={isSelected} (and role="row" or appropriate role if not already
present) while keeping the existing className/cn usage; ensure you update the
JSX for the TableRow element (reference: TableRow, handleClick, isSelected, cn)
and keep behavior identical for mouse clicks.

In `@apps/dashboard/src/components/conversations/conversation-timeline.tsx`:
- Around line 112-115: The paragraph always applies the "truncate" class so even
when expanded is true the text stays clipped; update the JSX for the <p> that
renders displayContent (the element using className "text-label-xs min-w-0
flex-1 truncate font-medium text-[`#1a1a1a`]") to conditionally include "truncate"
only when the message is not expanded (e.g., className={`text-label-xs min-w-0
flex-1 font-medium text-[`#1a1a1a`] ${!expanded ? 'truncate' : ''}`}) or use a
helper like clsx; ensure this uses the existing expanded and isLong state so
expanded messages show full content.

In `@apps/dashboard/src/components/conversations/conversations-table.tsx`:
- Line 135: The TableCell in the ConversationsTable component currently sets
colSpan={7} which doesn't match the actual table header column count (2) and
breaks accessibility; update the footer TableCell's colSpan to colSpan={2} (in
the conversations-table component where TableCell is rendered) so the footer
spans the correct number of columns and restores proper table semantics for
assistive tech.

In `@apps/dashboard/src/hooks/use-conversation-url-state.ts`:
- Around line 86-88: The current logic in useConversationUrlState copies the
existing 'page' param (searchParams.get('page')) into newParams, which preserves
pagination across filter changes and can produce empty results; change the code
in the useConversationUrlState hook so filter updates do not carry over the old
page—either remove the block that sets newParams.set('page', ...) entirely or
explicitly reset newParams.set('page', '0') when filters change (referencing the
searchParams and newParams variables and the 'page' key) so pagination is
cleared on filter updates.

In `@apps/dashboard/src/pages/activity-feed.tsx`:
- Around line 25-27: The current check unconditionally returns 'conversations'
when location.pathname.includes('/activity/conversations'), which can set an
invalid active tab when isConversationalAgentsEnabled is false; update the logic
where the active tab is determined (the location.pathname check that returns
'conversations') to first guard with the feature flag
(isConversationalAgentsEnabled) and if the flag is false, return a safe fallback
tab (e.g., 'all' or the default tab) and trigger a redirect/navigation off
'/activity/conversations' to the fallback route so the UI renders a valid tab
instead of a broken view.

---

Nitpick comments:
In `@apps/dashboard/src/components/conversations/conversation-timeline.tsx`:
- Around line 117-125: The toggle button for expanding messages (the element
using onClick={() => setExpanded(!expanded)} with RiExpandUpDownLine and the
expanded state) lacks proper semantics; update the button element to include
type="button" to prevent accidental form submission and add
aria-expanded={expanded} so assistive tech can detect the current state
(optionally add aria-controls referencing the ID of the collapsible content).
Ensure you keep the existing onClick and text content while adding these
attributes.

In `@apps/dashboard/src/components/conversations/conversations-filters.tsx`:
- Around line 24-27: The local PROVIDER_OPTIONS duplicates the provider mapping;
remove this duplicate and import the shared PROVIDER_OPTIONS from the
conversations/constants module instead (use the existing
CONVERSATIONAL_PROVIDERS mapping there) so the component reuses the canonical
source; if the shared constant doesn't already filter out comingSoon, either
import the shared CONVERSATIONAL_PROVIDERS and apply .filter(p =>
!p.comingSoon).map(...) once in the shared module or import the shared
PROVIDER_OPTIONS directly and replace all local usages in this file (e.g., where
PROVIDER_OPTIONS and CONVERSATIONAL_PROVIDERS are referenced).

In `@apps/dashboard/src/utils/query-keys.ts`:
- Around line 42-43: The query key constants fetchConversations and
fetchConversation are defined in a central query-keys module but should be
co-located with the conversations feature; move these constants out of
query-keys.ts and into the conversations feature (e.g., alongside the fetcher
functions or hooks in the conversations API module where functions like the
conversations fetcher or useConversations hook live), then update any imports
that reference fetchConversations/fetchConversation to import from the new
module to improve discoverability and reduce cross-module coupling.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6c5f6e6a-acb5-476b-8afa-f8e4d9541890

📥 Commits

Reviewing files that changed from the base of the PR and between 319360f and a5ccccd.

📒 Files selected for processing (21)
  • .source
  • apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts
  • apps/dashboard/src/api/conversations.ts
  • apps/dashboard/src/components/conversations/constants.ts
  • apps/dashboard/src/components/conversations/conversation-detail.tsx
  • apps/dashboard/src/components/conversations/conversation-overview.tsx
  • apps/dashboard/src/components/conversations/conversation-status-badge.tsx
  • apps/dashboard/src/components/conversations/conversation-table-row.tsx
  • apps/dashboard/src/components/conversations/conversation-timeline.tsx
  • apps/dashboard/src/components/conversations/conversations-content.tsx
  • apps/dashboard/src/components/conversations/conversations-empty-state.tsx
  • apps/dashboard/src/components/conversations/conversations-filters.tsx
  • apps/dashboard/src/components/conversations/conversations-table.tsx
  • apps/dashboard/src/hooks/use-conversation-url-state.ts
  • apps/dashboard/src/hooks/use-fetch-conversation-activities.ts
  • apps/dashboard/src/hooks/use-fetch-conversations.ts
  • apps/dashboard/src/main.tsx
  • apps/dashboard/src/pages/activity-feed.tsx
  • apps/dashboard/src/types/conversation.ts
  • apps/dashboard/src/utils/query-keys.ts
  • apps/dashboard/src/utils/routes.ts

Comment thread apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts Outdated
Comment thread apps/dashboard/src/components/conversations/conversation-detail.tsx
Comment thread apps/dashboard/src/components/conversations/conversation-status-badge.tsx Outdated
Comment thread apps/dashboard/src/components/conversations/conversation-timeline.tsx Outdated
Comment thread apps/dashboard/src/components/conversations/conversations-table.tsx Outdated
Comment thread apps/dashboard/src/hooks/use-conversation-url-state.ts Outdated
Comment thread apps/dashboard/src/pages/activity-feed.tsx
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.

♻️ Duplicate comments (1)
apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts (1)

58-67: ⚠️ Potential issue | 🟠 Major

Validate the agent against conversation._agentId before using its name.

This lookup trusts command.agentIdentifier, but the actual delivery still happens as conversation._agentId. If those diverge, the message is sent as one agent while the activity row records another agentId/senderName. It also does an extra query for resolve/signals flows where senderName is never used.

Suggested fix
-    const agent = await this.agentRepository.findOne(
-      {
-        _environmentId: command.environmentId,
-        _organizationId: command.organizationId,
-        identifier: command.agentIdentifier,
-      },
-      { name: 1, identifier: 1 }
-    );
-    const agentName = agent?.name;
+    let agentName: string | undefined;
+    if (command.reply || command.update) {
+      const agent = await this.agentRepository.findOne(
+        {
+          _environmentId: command.environmentId,
+          _organizationId: command.organizationId,
+          _id: conversation._agentId,
+          identifier: command.agentIdentifier,
+        },
+        { name: 1 }
+      );
+
+      if (!agent) {
+        throw new BadRequestException('Agent identifier does not match conversation agent');
+      }
+
+      agentName = agent.name;
+    }

As per coding guidelines: apps/api/**: Review with focus on security, authentication, and authorization. Check for proper error handling and input validation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts`
around lines 58 - 67, The code fetches an agent by command.agentIdentifier and
uses agent?.name without verifying it matches the conversation actor, which can
cause senderName/agentId divergence and performs unnecessary queries when
senderName isn't used; update handle-agent-reply.usecase.ts so that you validate
the fetched agent against conversation._agentId (or directly fetch by
conversation._agentId) before using its name, only perform the
agentRepository.findOne (or findById) when senderName will be used, and add a
guard to handle mismatches (throw or fallback) so the activity row always
records the correct agentId/senderName (refer to agentRepository.findOne,
conversation._agentId, and the surrounding logic in handle-agent-reply.usecase).
🤖 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/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts`:
- Around line 58-67: The code fetches an agent by command.agentIdentifier and
uses agent?.name without verifying it matches the conversation actor, which can
cause senderName/agentId divergence and performs unnecessary queries when
senderName isn't used; update handle-agent-reply.usecase.ts so that you validate
the fetched agent against conversation._agentId (or directly fetch by
conversation._agentId) before using its name, only perform the
agentRepository.findOne (or findById) when senderName will be used, and add a
guard to handle mismatches (throw or fallback) so the activity row always
records the correct agentId/senderName (refer to agentRepository.findOne,
conversation._agentId, and the surrounding logic in handle-agent-reply.usecase).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a43d61f4-05d8-4996-bfa0-9127e54fcfea

📥 Commits

Reviewing files that changed from the base of the PR and between a5ccccd and 39b38d8.

📒 Files selected for processing (1)
  • apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts

@scopsy scopsy merged commit 5b526ac into next Apr 15, 2026
32 of 34 checks passed
@scopsy scopsy deleted the conversations-page branch April 15, 2026 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant