fix(web-e2e): Human Chat Panel E2E navigation without Playwright baseURL#2209
Conversation
WalkthroughCentralizes Playwright navigation with a new gotoApp helper; implements composer mention token format and resolved-mention plumbing across picker, insertion, and send-time conversion; adds group-call stall detection, WebRTC diagnostics, env-driven client settings and telemetry; updates matrix-provider room power-levels; plus various UI/layout, i18n, E2E, auth/device-id, and tooling changes. ChangesE2E navigation
Composer mention handling & picker
Mentions aggregation & inbox
Message rendering & deep-linking
Call UI, stall detection & telemetry
Provider wiring & logging
Layout, i18n, auth/device IDs, tooling & misc
Sequence Diagram(s)sequenceDiagram
participant Composer as Composer (editor)
participant TokenDetect as Token Detector
participant Resolve as useResolvedMentionCandidateLabel
participant Picker as Mention Picker Row
participant ChatBar as HumanChatPanelChatBar
participant RightPanel as HumanRightPanel
participant Matrix as Matrix (sendMessage)
Composer->>TokenDetect: Type "@<ZWSP>Name"
TokenDetect->>Resolve: construct MentionPickCandidate
Resolve->>Resolve: lookup Privy/person → resolvedLabel
Resolve-->>Picker: return {resolvedLabel,busy,avatarUrl,pickDisabled}
Picker->>ChatBar: onPickResolved(resolvedLabel)
ChatBar->>ChatBar: formatComposerMentionToken(resolvedLabel)
ChatBar->>RightPanel: onMergeMentionDisplayLabel(userId, displayLabel)
RightPanel->>RightPanel: wireComposerPlainForMatrixSend(plain, map)
RightPanel->>Matrix: sendMessage(wirePlain, mentionUserIds)
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsTimed out fetching pipeline failures after 30000ms Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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: 0/1 reviews remaining, refill in 60 minutes.Comment |
|
🔗 Custom preview URL: https://pr-2209.preview-app.hypha.earth |
…baseURL Relative page.goto paths failed when tests ran without apps/web-e2e playwright.config.ts (wrong CWD), yielding "Cannot navigate to invalid URL". Centralize BASE_URL-aligned absolute URLs via resolveAppUrl/gotoApp. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
Drop the second-line Matrix ID / technical handle from mention rows. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
Insert `@` + zero-width space + Hypha/profile label when picking a member, strip `@` from labels for stable tokens, and rewrite to MXIDs plus explicit m.mentions user_ids before sendMessage/editRoomMessage so Matrix notifications and interoperability stay correct. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
When AI + Human panels wrap the layout, the main scroll root allowed overflow-x; panning revealed empty space beside the fixed chat rail. Add overflow-x-hidden on the scrollport so content cannot shift sideways. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
Replace Container max-width centering with full-width plus horizontal padding so hero and secondary chrome span the scroll column when panels narrow the viewport. Clip horizontal overflow on the dual-panel main scroll root to avoid dead gaps next to fixed side rails. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
Share Matrix→Privy→Person resolution between picker rows and composer: insert the same resolved label as display tokens; merge overrides into mention maps so wire send + timeline pills match the dropdown (not shortened MXID fallbacks). Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
…ll guard - Pass resolveMentionMemberLabel into HumanChatPanelCallStage for roster/Person labels on video tiles (not Matrix shortened MXIDs). - Auto-leave after 5 minutes when connected solo (othersInRoom === 0). - Abort hung GroupCall.enter() after 90s with recoverable error + telemetry. - i18n for idle-leave banner (en, de, es, fr, pt). Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
Clear mention display overrides after roomId is declared. Widen resolveMemberLabel typing to accept undefined for call tiles. Gate keyboard mention pick until Hypha name resolution completes. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
0f88b0e to
ec51b68
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
packages/core/src/matrix/client/hooks/use-space-group-call.ts (1)
470-505:⚠️ Potential issue | 🔴 CriticalGuard
gc.enter()continuation against stale attempts.If the stall watchdog fires (or a room switch/retry happens) while
await gc.enter()is pending, this continuation can still run later and incorrectly set state toconnectedor interfere with a newer session. Add a stale-attempt check before both catch-path mutations and success-path state updates.🐛 Proposed fix
@@ - const joinT0 = + const joinT0 = typeof performance !== 'undefined' ? performance.now() : Date.now(); + const joinAttemptStartedAt = joinT0; joinStartedAtRef.current = joinT0; @@ try { await gc.enter(); } catch (e) { clearConnectingStallTimer(); + if ( + joinStartedAtRef.current !== joinAttemptStartedAt || + groupCallRef.current !== gc + ) { + return; + } isJoiningRef.current = false; @@ } clearConnectingStallTimer(); + if ( + joinStartedAtRef.current !== joinAttemptStartedAt || + groupCallRef.current !== gc + ) { + return; + } setCallState('connected');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/core/src/matrix/client/hooks/use-space-group-call.ts` around lines 470 - 505, The continuation after awaiting gc.enter() must bail out if the join attempt became stale; capture the current joinStartedAtRef.current (e.g., const attemptStartedAt = joinStartedAtRef.current) immediately before calling await gc.enter(), then at the top of the catch block and again on the success path check if joinStartedAtRef.current !== attemptStartedAt and if so return early without mutating shared state. Apply this guard before any use of isJoiningRef.current, setCallState, setCallKind, setThreadContext, runCleanup, setIsMicrophoneMuted, setIsLocalVideoMuted, setActiveKeyFromGroupCall, setErrorCode, or clearing timers so only the active join attempt updates state.apps/web-e2e/src/menu-top-consistent-height.spec.ts (1)
39-63:⚠️ Potential issue | 🟠 MajorAccessibility coverage is missing for the navigated pages in this spec.
After navigating to
/en/dho/hypha/agreementsand/en/network, add an axe-core assertion to keep accessibility regressions from slipping through this suite.As per coding guidelines: "Accessibility assertions with axe-core where pages are visited".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web-e2e/src/menu-top-consistent-height.spec.ts` around lines 39 - 63, After each navigation in the spec (the test that compares header heights and the test '--menu-top-height CSS variable should be set and integer'), run the axe-core accessibility check once the page is fully loaded and visible; specifically, after the gotoApp(...) + page.waitForLoadState('domcontentloaded') and after waiting for the header locator (menuSelector) to be visible for both the '/en/dho/hypha/agreements' and '/en/network' visits, invoke your project’s axe helper (e.g., expectA11y(page) or the equivalent runAxe helper) to assert no a11y violations before continuing with boundingBox assertions so accessibility regressions are caught.apps/web-e2e/src/human-chat-panel-feature-flag.spec.ts (1)
66-77:⚠️ Potential issue | 🟠 MajorAdd an accessibility assertion after these page visits.
These tests now navigate via
gotoApp(...), but they still lack an axe-core check on the visited page, so accessibility regressions can pass unnoticed in this path.As per coding guidelines: "Accessibility assertions with axe-core where pages are visited".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web-e2e/src/human-chat-panel-feature-flag.spec.ts` around lines 66 - 77, After each gotoApp(...) + page.waitForLoadState('domcontentloaded') call add an axe-core accessibility assertion: import or call your project's axe helper (e.g., runAxe or axeCheck) and invoke it against the Playwright page immediately after page.waitForLoadState; update both the test containing the chatButton visibility check and the 'should render page content normally' test so they each run the accessibility check on the loaded page.apps/web-e2e/src/coherence-chat-panel.spec.ts (1)
50-90:⚠️ Potential issue | 🟠 MajorBaseline navigation tests need an accessibility assertion.
These active tests visit
/en/dho/${SPACE_SLUG}/coherencebut do not run an axe-core check, leaving a required accessibility gate uncovered in the updated path.As per coding guidelines: "Accessibility assertions with axe-core where pages are visited".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web-e2e/src/coherence-chat-panel.spec.ts` around lines 50 - 90, These tests that call gotoApp(page, `/en/dho/${SPACE_SLUG}/coherence`) are missing the required axe-core accessibility assertion; after each navigation + page.waitForLoadState('domcontentloaded') in the tests named "panel opens...", "panel opens in space chat mode by default from coherence page", "panel in space mode has no back button (hash icon is shown)", and "space chat panel has functional chat input and send button", invoke the repository's axe helper (for example runAxe(page) or the project's injectAxe + axe.run helper) and assert there are no violations before interacting with chatPanel (i.e., add await runAxe(page) / equivalent right after gotoApp(...) and page.waitForLoadState('domcontentloaded')). Ensure you call the same helper used elsewhere in tests to keep consistency.
🤖 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/web-e2e/src/ai-chat-feature-flag.spec.ts`:
- Line 61: The test uses a non-locale route in the call to gotoApp(page,
'/my-spaces') which can cause redirect-dependent flakiness; update that call in
apps/web-e2e/src/ai-chat-feature-flag.spec.ts to use the explicit
locale-prefixed path '/en/my-spaces' (and ensure any other gotoApp calls in the
same suite follow the same '/en/...' pattern) so navigation no longer depends on
redirects.
In `@apps/web-e2e/src/utils/nav-url.ts`:
- Around line 10-12: The code trims BASE_URL in resolveBaseUrl() but
playwright.config.ts reads process.env['BASE_URL'] raw, causing inconsistent URL
resolution; update playwright.config.ts so it parses BASE_URL the same way (use
process.env['BASE_URL']?.trim() || DEFAULT_BASE_URL) to match resolveBaseUrl(),
ensuring both resolveBaseUrl() and the Playwright config use identical
trimming/fallback logic.
In
`@packages/epics/src/common/human-chat-panel/use-resolved-mention-candidate-label.ts`:
- Around line 48-61: busy currently trusts usePersonBySub's isLoading, but that
hook returns isLoading = false when JWT is missing (SWR key null), so
roster-backed candidates become pickable during auth bootstrap; update the
busy/pickDisabled logic to also consider JWT readiness for the privySub path:
when privySub/resolvedSub is present treat the candidate as busy if
loadingPerson OR the auth JWT is not yet available (e.g., check the auth/jwt
readiness value from your auth hook or context), so busy = Boolean(candidate) &&
( ((!privySub && loadingLink) || (Boolean(resolvedSub) && (loadingPerson ||
!jwtReady))) ). Ensure useMemo/deps include the jwtReady value and update
pickDisabled to use busy.
In `@packages/epics/src/common/human-right-panel.tsx`:
- Around line 887-896: The current mentionSanitizedLabelToUserId collapses
identical sanitized labels to the first MXID, causing ambiguous mentions; change
its creation so collisions are detected and handled: while iterating
mentionLabelByUserId and using sanitizeMentionDisplayLabel, track duplicates
(e.g., store a sentinel or multiple IDs for a key) and do NOT map a plain
sanitized label to a single MXID when a duplicate exists—instead mark that key
as collided so downstream code (wireComposerPlainForMatrixSend) will use a
fallback token containing an unambiguous identifier (raw MXID or a disambiguated
label) for those users; update wireComposerPlainForMatrixSend to check the
collision marker from mentionSanitizedLabelToUserId and emit the fallback token
for any collided/same-key entries.
---
Outside diff comments:
In `@apps/web-e2e/src/coherence-chat-panel.spec.ts`:
- Around line 50-90: These tests that call gotoApp(page,
`/en/dho/${SPACE_SLUG}/coherence`) are missing the required axe-core
accessibility assertion; after each navigation +
page.waitForLoadState('domcontentloaded') in the tests named "panel opens...",
"panel opens in space chat mode by default from coherence page", "panel in space
mode has no back button (hash icon is shown)", and "space chat panel has
functional chat input and send button", invoke the repository's axe helper (for
example runAxe(page) or the project's injectAxe + axe.run helper) and assert
there are no violations before interacting with chatPanel (i.e., add await
runAxe(page) / equivalent right after gotoApp(...) and
page.waitForLoadState('domcontentloaded')). Ensure you call the same helper used
elsewhere in tests to keep consistency.
In `@apps/web-e2e/src/human-chat-panel-feature-flag.spec.ts`:
- Around line 66-77: After each gotoApp(...) +
page.waitForLoadState('domcontentloaded') call add an axe-core accessibility
assertion: import or call your project's axe helper (e.g., runAxe or axeCheck)
and invoke it against the Playwright page immediately after
page.waitForLoadState; update both the test containing the chatButton visibility
check and the 'should render page content normally' test so they each run the
accessibility check on the loaded page.
In `@apps/web-e2e/src/menu-top-consistent-height.spec.ts`:
- Around line 39-63: After each navigation in the spec (the test that compares
header heights and the test '--menu-top-height CSS variable should be set and
integer'), run the axe-core accessibility check once the page is fully loaded
and visible; specifically, after the gotoApp(...) +
page.waitForLoadState('domcontentloaded') and after waiting for the header
locator (menuSelector) to be visible for both the '/en/dho/hypha/agreements' and
'/en/network' visits, invoke your project’s axe helper (e.g., expectA11y(page)
or the equivalent runAxe helper) to assert no a11y violations before continuing
with boundingBox assertions so accessibility regressions are caught.
In `@packages/core/src/matrix/client/hooks/use-space-group-call.ts`:
- Around line 470-505: The continuation after awaiting gc.enter() must bail out
if the join attempt became stale; capture the current joinStartedAtRef.current
(e.g., const attemptStartedAt = joinStartedAtRef.current) immediately before
calling await gc.enter(), then at the top of the catch block and again on the
success path check if joinStartedAtRef.current !== attemptStartedAt and if so
return early without mutating shared state. Apply this guard before any use of
isJoiningRef.current, setCallState, setCallKind, setThreadContext, runCleanup,
setIsMicrophoneMuted, setIsLocalVideoMuted, setActiveKeyFromGroupCall,
setErrorCode, or clearing timers so only the active join attempt updates state.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: 62dbfa71-6d90-4577-a418-74a27faf702f
📒 Files selected for processing (27)
apps/web-e2e/src/ai-chat-feature-flag.spec.tsapps/web-e2e/src/coherence-chat-panel.spec.tsapps/web-e2e/src/human-chat-panel-feature-flag.spec.tsapps/web-e2e/src/menu-top-consistent-height.spec.tsapps/web-e2e/src/pages/ai-chat-panel.page.tsapps/web-e2e/src/pages/base.page.tsapps/web-e2e/src/pages/coherence-chat-panel.page.tsapps/web-e2e/src/pages/coherence.page.tsapps/web-e2e/src/pages/human-chat-panel.page.tsapps/web-e2e/src/pages/layout.page.tsapps/web-e2e/src/pages/my-spaces.page.tsapps/web-e2e/src/panels-space-context.spec.tsapps/web-e2e/src/utils/nav-url.tsapps/web/src/app/[lang]/dho/[id]/layout.tsxpackages/core/src/matrix/client/hooks/use-space-group-call.tspackages/epics/src/common/human-chat-panel/human-chat-display-mention.tspackages/epics/src/common/human-chat-panel/human-chat-mention-candidate-row.tsxpackages/epics/src/common/human-chat-panel/human-chat-mention-token.tspackages/epics/src/common/human-chat-panel/human-chat-panel-chat-bar.tsxpackages/epics/src/common/human-chat-panel/use-resolved-mention-candidate-label.tspackages/epics/src/common/human-right-panel.tsxpackages/epics/src/common/panel-main-column-scroll-bridge.tsxpackages/i18n/src/messages/de.jsonpackages/i18n/src/messages/en.jsonpackages/i18n/src/messages/es.jsonpackages/i18n/src/messages/fr.jsonpackages/i18n/src/messages/pt.json
Avoid redirect-dependent navigation per CodeRabbit review on PR #2209. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
Align baseURL parsing with resolveBaseUrl() for consistent navigation. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
Person lookup uses usePersonBySub which does not fetch until JWT exists; keep busy until auth finishes loading the token when a privySub path is active. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
When sanitized labels collide, map keys and composer tokens include a shortened MXID suffix so wire send resolves to the intended user. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
Cold loads map history before `getUserId()` is available, so own rows were stored as member with technical display labels. Reconcile role, senderName, and avatar when currentUserId or profile avatar resolves. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
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 `@packages/epics/src/common/human-right-panel.tsx`:
- Around line 1800-1813: The optimistic pending row is using wirePlain (which
contains MXIDs) as captionPreview; change the code that constructs the pending
object (where pendingId is set and setSendingPending is called) to use the
original text variable for captionPreview instead of wirePlain, leaving
wirePlain (from wireComposerPlainForMatrixSend) only for the actual send logic;
update the setSendingPending call that creates the object with id: pendingId,
attachmentCount, uploadedCount, etc., to set captionPreview: text so the UI
shows the user-typed display names while wirePlain remains used by the send
function.
- Around line 894-904: The callback mergeMentionDisplayLabel shadows the outer
translation function t by declaring const t = displayLabel.trim();; rename the
local variable (e.g., to trimmed or trimmedLabel) inside
mergeMentionDisplayLabel so it no longer conflicts with the
useTranslations('HumanChatPanel') t and update all uses within that callback
(setMentionDisplayOverride logic) to use the new name.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: 97396cf9-0b3b-46d3-ba25-e579778ef1ab
📒 Files selected for processing (5)
apps/web-e2e/playwright.config.tsapps/web-e2e/src/ai-chat-feature-flag.spec.tspackages/epics/src/common/human-chat-panel/human-chat-panel-chat-bar.tsxpackages/epics/src/common/human-chat-panel/use-resolved-mention-candidate-label.tspackages/epics/src/common/human-right-panel.tsx
|
@code rabbit please review |
Message body used synchronous resolveMatrixMemberLabel only; Matrixoften shows bridged Privy MXIDs until roster/Hypha hydrate. Mirror sender pills: use matrix_user_links + Person when label still looks technical. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
…s lag Emit connected/media_snapshot/remote_media_stall telemetry in dev; detect when participants exist without remote userMedia feeds for 45s and show an inline banner with dismiss. Periodic snapshots every 12s while connected. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
Prefer roster merge over raw Matrix member displaynames; resolve Person via matrix_user_links when labels still look bridged. Apply same logic to connecting placeholders. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
Remove ring outline; use minimal horizontal padding and smaller corner radius so the background sits close to the @ name. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
Parse http(s) URLs in plaintext bodies, trim trailing punctuation from hrefs, and wrap with anchor target _blank rel noopener noreferrer. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
Copy link now omits redundant chat param (?msg only). Resolve msg-only deep links when viewing the same space. Render Hypha chat URLs as compact # slug chips instead of raw long URLs. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
CodeQL flagged repeated regex tests on user-controlled chat text. Strip trailing URL punctuation with a constant-time character set and scan http(s) URLs without a nested-quantifier regex. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
The first-unread marker used accent-tinted backgrounds that read as a green strip next to messages (including reply context). Use neutral border and muted row tint instead, aligned with mention-highlight styling. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
Address known advisories: bump hono to a patched 4.12.x, ensure @hono/node-server stays on the 1.19.x line >=1.19.13 for static middleware fixes, enforce dompurify >=3.4.0 for transitive mermaid chains, and pin @xmldom/xmldom >=0.9.9 under mathjax tooling. Co-authored-by: webguru-hypha <webguru-hypha@users.noreply.github.com>
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
- Added retry mechanism for outgoing call placements to handle rejoin and refresh scenarios. - Implemented cleanup for stale timers and member state before entering a group call. - Updated error handling for permission-related issues in group calls.
…ion handling - Updated URL validation to correctly handle subdomains of 'hypha.earth'. - Added a utility function to determine if an event originates from an interactive child element. - Modified mention selection logic to prevent actions when triggered from interactive child elements.
…in mention inbox - Simplified the logic for checking if an event originates from an interactive child element by consolidating the closest selector into a single line.
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web/src/app/api/matrix/token/route.ts (1)
204-268:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftDon't make a valid token depend on a destructive recovery path.
For legacy device IDs, this code resets the user's Matrix password before the replacement token/deviceId are safely persisted. If
loginUserorupdateEncryptedAccessTokenActionfails afterresetPassword(..., logout_devices: true), the request can revoke the user's working token and leave them locked out even thoughhasValidTokenwas already true. This needs an atomic or compensating rotation flow before merge.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/app/api/matrix/token/route.ts` around lines 204 - 268, The code currently calls matrixAuthClient.resetPassword(...) which may revoke the user's active token before the new token/device are persisted; change the rotation flow so revocation is only performed after a successful persistence: call resetPassword in a non-destructive mode (do not logout devices) or use a "rotate without logout" option if available, then call matrixAuthClient.loginUser(...) to obtain the new accessToken/deviceId, persist them via updateEncryptedAccessTokenAction(...), and only after successful persistence perform a final destructive logout (or reset with logout_devices:true) if you need to revoke old sessions; update the logic around resetPassword, loginUser, and updateEncryptedAccessTokenAction to follow this order and add error handling/compensation to avoid leaving the user locked out.packages/epics/src/common/human-chat-panel/human-chat-panel-call-stage.tsx (1)
1466-1481:⚠️ Potential issue | 🟠 Major | ⚡ Quick winRemote screenshare audio is muted permanently.
The tile now always renders
<video muted />, but the separate audible<audio>element is skipped forisShare. A remote screenshare that includes audio will therefore look connected while never producing sound. Render the audio element for remote share feeds too, or stop force-muting shares whose audio is not mirrored elsewhere.Suggested fix
- {!feed.isLocal() && !isShare ? ( + {!feed.isLocal() ? ( <audio ref={audioRef} autoPlay playsInline aria-hidden /> ) : null}Also applies to: 1597-1599
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/epics/src/common/human-chat-panel/human-chat-panel-call-stage.tsx` around lines 1466 - 1481, The video element in human-chat-panel-call-stage.tsx is always rendered with muted which mutes remote screenshare audio and the separate audible <audio> element is currently skipped when isShare is true; update the render logic in the component (look for hasVideo, isShare, the video element using ref and ariaLabel) so that for remote share feeds you still create/render an audio element bound to the same MediaStream (or stop forcing muted on streams that have distinct audio tracks), ensuring shares with audio produce sound while preserving autoplay/playsInline behavior.
♻️ Duplicate comments (2)
packages/core/src/matrix/client/hooks/use-space-group-call.ts (1)
818-824:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winRe-check join liveness after camera-unmute await.
After Line 820, the join can already be invalidated (cleanup/watchdog/leave), but the code continues into diagnostics and
setCallState('connected'). Add the same epoch/groupCall liveness guard here.🔧 Suggested fix
if (kind === 'video') { try { await gc.setLocalVideoMuted(false); } catch { /* camera permission / hardware — remain in call with video off */ } + if (joinEpoch !== joinEpochRef.current || groupCallRef.current !== gc) { + isJoiningRef.current = false; + return; + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/core/src/matrix/client/hooks/use-space-group-call.ts` around lines 818 - 824, After awaiting gc.setLocalVideoMuted(false) when kind === 'video', re-check the join liveness using the same epoch/groupCall guard used elsewhere in use-space-group-call (i.e., validate that the current epoch and groupCall are still the same/valid) before proceeding to diagnostics or calling setCallState('connected'); if the guard fails, abort further processing to avoid setting state for a stale/left call.packages/epics/src/common/human-chat-panel/human-chat-panel-mention-inbox.tsx (1)
104-111:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winRow activation is blocked by the interactive-target guard.
eventComesFromInteractiveChild()matches the row itself (role="button"+tabIndex=0), so bothonClickandonKeyDownexit early and the mention row never selects.🔧 Suggested fix
-function eventComesFromInteractiveChild(target: EventTarget | null): boolean { - return ( - target instanceof Element && - Boolean( - target.closest('a,button,[role="link"],[role="button"],[tabindex="0"]'), - ) - ); -} +function eventComesFromInteractiveChild( + target: EventTarget | null, + currentTarget: HTMLElement, +): boolean { + if (!(target instanceof Element)) return false; + const interactive = target.closest( + 'a,button,[role="link"],input,select,textarea', + ); + return Boolean(interactive && interactive !== currentTarget); +}- onClick={(e) => { - if (eventComesFromInteractiveChild(e.target)) return; + onClick={(e) => { + if (eventComesFromInteractiveChild(e.target, e.currentTarget)) return; onSelectMessage(row.eventId, row.roomId); }} onKeyDown={(e) => { - if (eventComesFromInteractiveChild(e.target)) return; + if (eventComesFromInteractiveChild(e.target, e.currentTarget)) return; selectMentionRowKeyDown(e, () => onSelectMessage(row.eventId, row.roomId), ); }}Also applies to: 184-193, 243-250
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/epics/src/common/human-chat-panel/human-chat-panel-mention-inbox.tsx` around lines 104 - 111, The guard is matching the row itself because Element.closest includes the element, so change eventComesFromInteractiveChild to only treat a match as interactive when the closest(...) result is a different element than the event target element (i.e., cast target to Element, call el.closest(...), then return Boolean(match && match !== el)); update the same pattern in the other occurrences referenced (around the blocks at 184-193 and 243-250) so clicks/keys on the row element itself are not treated as interactive-child events.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@package.json`:
- Around line 17-18: The npm scripts "format:check" and "format:fix" in
package.json use single-quoted glob arguments which break on Windows; update the
script values for "format:check" and "format:fix" to use JSON-escaped
double-quoted glob arguments instead of single quotes (i.e., replace the
single-quoted patterns passed to prettier with double-quoted patterns) so the
globs are recognized cross-platform.
In `@packages/core/src/matrix/client/hooks/space-group-call-utils.ts`:
- Around line 12-17: The permission-like error check removed 'NotReadableError'
and 'OverconstrainedError', which causes device-busy/constraint failures to be
misclassified; restore those two error names into the same conditional that
currently checks name === 'NotAllowedError' || name ===
'PermissionDismissedError' || name === 'SecurityError' so they return true like
the others (this affects the behavior relied upon by the tests in
use-space-group-call.test.ts and the PERMISSION_DENIED handling in
use-space-group-call.ts).
In `@packages/core/src/matrix/client/providers/matrix-provider.tsx`:
- Around line 1277-1280: The current implementation calls
ensureRoomCallPowerLevels(client, resolvedId) with void, causing a race where
callers can start a call before power-levels are reconciled; change this to
await the promise from ensureRoomCallPowerLevels so joinRoom waits for the
reconciliation to complete (use the resolvedId from joined.roomId and await
ensureRoomCallPowerLevels(client, resolvedId)), optionally surrounding the await
with try/catch if you want to surface or log reconciliation errors rather than
swallowing them.
In
`@packages/epics/src/common/human-chat-panel/human-chat-panel-call-join-strip.tsx`:
- Line 47: The current showAudioButton logic hides the audio CTA whenever
hasDurable (or durableMessage) is true and deviceCount === 0, preventing the
callLeftAudioIdle restart path even though onJoinAudio is wired; update the
condition so the durable idle banner does not suppress the audio action—compute
showAudioButton using deviceCount and the presence of the onJoinAudio handler
(e.g., treat audio available if deviceCount > 0 OR onJoinAudio is defined) and
remove/ignore the hasDurable/durableMessage check; adjust any related UI
branches that rely on showAudioButton (callLeftAudioIdle, onJoinAudio) to use
the new boolean.
In
`@packages/epics/src/common/human-chat-panel/human-chat-panel-in-call-controls.tsx`:
- Around line 117-146: The compact (non-full) camera button can collapse because
neutralBtn currently only provides color classes; update the className logic in
the onToggleCamera button (inside human-chat-panel-in-call-controls.tsx) so the
non-full variant always gets the fixed-size button shell (e.g., include
'inline-flex items-center justify-center' plus width/height classes) rather than
relying on the conditional that only applies when isFull or isLocalVideoMuted;
you can either add the size classes to neutralBtn or ensure the ternary that
picks between neutralBtn/baseBtn/camOffBtn also appends the fixed-size shell
when !isFull so the button remains a consistent hit target (adjust classes near
neutralBtn, baseBtn, camOffBtn and keep icon sizing via the icon class).
In `@packages/epics/src/common/human-right-panel.tsx`:
- Around line 1799-1804: The branch currently blocks cross-room mention routing
by requiring `current` to be truthy; remove the `current &&` check so the
condition reads effectively "if targetRoom exists and window is defined and
targetRoom !== current" (this will allow navigation when `current` is null
during cold load/join). Update the conditional around `targetRoom`/`current` in
the handler (referencing the `targetRoom` and `current` variables in
human-right-panel) and ensure downstream navigation code handles a null
`current` gracefully.
---
Outside diff comments:
In `@apps/web/src/app/api/matrix/token/route.ts`:
- Around line 204-268: The code currently calls
matrixAuthClient.resetPassword(...) which may revoke the user's active token
before the new token/device are persisted; change the rotation flow so
revocation is only performed after a successful persistence: call resetPassword
in a non-destructive mode (do not logout devices) or use a "rotate without
logout" option if available, then call matrixAuthClient.loginUser(...) to obtain
the new accessToken/deviceId, persist them via
updateEncryptedAccessTokenAction(...), and only after successful persistence
perform a final destructive logout (or reset with logout_devices:true) if you
need to revoke old sessions; update the logic around resetPassword, loginUser,
and updateEncryptedAccessTokenAction to follow this order and add error
handling/compensation to avoid leaving the user locked out.
In `@packages/epics/src/common/human-chat-panel/human-chat-panel-call-stage.tsx`:
- Around line 1466-1481: The video element in human-chat-panel-call-stage.tsx is
always rendered with muted which mutes remote screenshare audio and the separate
audible <audio> element is currently skipped when isShare is true; update the
render logic in the component (look for hasVideo, isShare, the video element
using ref and ariaLabel) so that for remote share feeds you still create/render
an audio element bound to the same MediaStream (or stop forcing muted on streams
that have distinct audio tracks), ensuring shares with audio produce sound while
preserving autoplay/playsInline behavior.
---
Duplicate comments:
In `@packages/core/src/matrix/client/hooks/use-space-group-call.ts`:
- Around line 818-824: After awaiting gc.setLocalVideoMuted(false) when kind ===
'video', re-check the join liveness using the same epoch/groupCall guard used
elsewhere in use-space-group-call (i.e., validate that the current epoch and
groupCall are still the same/valid) before proceeding to diagnostics or calling
setCallState('connected'); if the guard fails, abort further processing to avoid
setting state for a stale/left call.
In
`@packages/epics/src/common/human-chat-panel/human-chat-panel-mention-inbox.tsx`:
- Around line 104-111: The guard is matching the row itself because
Element.closest includes the element, so change eventComesFromInteractiveChild
to only treat a match as interactive when the closest(...) result is a different
element than the event target element (i.e., cast target to Element, call
el.closest(...), then return Boolean(match && match !== el)); update the same
pattern in the other occurrences referenced (around the blocks at 184-193 and
243-250) so clicks/keys on the row element itself are not treated as
interactive-child events.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: aaedef52-3734-4469-9f1d-e2eb2371e864
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!pnpm-lock.yaml
📒 Files selected for processing (30)
.vscode/settings.jsonapps/web/src/app/[lang]/dho/[id]/_components/breadcrumbs.tsxapps/web/src/app/api/matrix/token/route.tsdocs/requirements/matrix-voip-turn-server-setup.mddocs/requirements/voice-video-call-implementation-spec.mddocs/requirements/voice-video-call-matrix-tech-spec.mddocs/requirements/voice-video-call-phase-0-runbook.mdpackage.jsonpackages/core/src/coherence/lib/matrix-shared-secret.tspackages/core/src/matrix/client/hooks/space-group-call-telemetry.tspackages/core/src/matrix/client/hooks/space-group-call-utils.tspackages/core/src/matrix/client/hooks/use-matrix-user-ids-by-privy-subs.tspackages/core/src/matrix/client/hooks/use-space-group-call.tspackages/core/src/matrix/client/matrix-client-logger.tspackages/core/src/matrix/client/matrix-webrtc-env.tspackages/core/src/matrix/client/providers/matrix-provider.tsxpackages/epics/src/common/human-chat-panel/human-chat-message-link.tspackages/epics/src/common/human-chat-panel/human-chat-panel-call-banner.tsxpackages/epics/src/common/human-chat-panel/human-chat-panel-call-join-strip.tsxpackages/epics/src/common/human-chat-panel/human-chat-panel-call-stage.tsxpackages/epics/src/common/human-chat-panel/human-chat-panel-in-call-controls.tsxpackages/epics/src/common/human-chat-panel/human-chat-panel-mention-inbox.tsxpackages/epics/src/common/human-chat-panel/human-chat-panel-message-bubble.tsxpackages/epics/src/common/human-right-panel.tsxpackages/epics/src/spaces/components/space-search.tsxpackages/i18n/src/messages/de.jsonpackages/i18n/src/messages/en.jsonpackages/i18n/src/messages/es.jsonpackages/i18n/src/messages/fr.jsonpackages/i18n/src/messages/pt.json
💤 Files with no reviewable changes (2)
- packages/epics/src/spaces/components/space-search.tsx
- apps/web/src/app/[lang]/dho/[id]/_components/breadcrumbs.tsx
- Added additional error types to the group call permission validation. - Updated the join audio button logic to handle optional audio joining more gracefully. - Refactored the MatrixProvider to ensure proper handling of room call power levels.
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
packages/epics/src/common/human-right-panel.tsx (1)
1306-1320:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winDispose draft attachments in the membership-denied reset path.
This branch clears the room state but leaves
draftAttachmentsand their blob URLs alive. If a user had attached files before access is revoked, those URLs leak until unmount and the stale attachments reappear if access is restored.Suggested fix
if (roomId) { matrixRef.current.unregisterRoomListener(roomId); } + disposeDraftAttachmentUrls(draftAttachmentsRef.current); + setDraftAttachments([]); joinedRef.current = null; setRoomId(null); setMessages([]); setReplyDraft(null); setEditDraft(null); setInput(''); setError(null); + setSendingPending(null); setCallFullViewOpen(false);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/epics/src/common/human-right-panel.tsx` around lines 1306 - 1320, The reset branch inside the useEffect (the path that runs when mode === 'space' and hasSpaceChatAccess is false) currently clears room state but leaves draftAttachments and their blob URLs alive; update this branch to revoke any Blob URLs and clear the draft attachments state: locate the draftAttachments state variable and, before calling setRoomId(null)/setMessages([])/setReplyDraft(null)/setEditDraft(null)/setInput('') etc., iterate draftAttachments (or draftAttachmentsRef) and call URL.revokeObjectURL(...) for each attachment blobUrl (safely checking existence), then clear the attachments via setDraftAttachments([]) or setDraftAttachments(null) to prevent leaks and stale re-appearance when access is restored.
🤖 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/core/src/matrix/client/providers/matrix-provider.tsx`:
- Around line 186-188: The current blanket catch around await
ensureRoomCallPowerLevels(...) swallows all failures; change it to only handle
known permission-denial errors and re-throw everything else so callers can act
on failures. In the catch block for ensureRoomCallPowerLevels, inspect the
caught error (e.g., check err.errcode === 'M_FORBIDDEN' or err.name/instance
indicating a Matrix permission error) and log a warning for that case, but for
any other error re-throw it (throw error) after logging; reference
ensureRoomCallPowerLevels and the surrounding try/catch in matrix-provider.tsx
when applying this change.
In
`@packages/epics/src/common/human-chat-panel/human-chat-panel-call-join-strip.tsx`:
- Around line 84-99: The audio CTA's visual state doesn't match its DOM disabled
state when onJoinAudio is missing; update the button className logic in the
showAudioButton block (the button using onJoinAudio, disabled, busy) to include
the same predicate (!onJoinAudio) used for the disabled prop so the disabled
styling (cursor-not-allowed, opacity-50) is applied when onJoinAudio is absent
and the hover class (hover:bg-muted) is only applied when disabled, busy, and
onJoinAudio indicate the button is truly enabled.
In `@packages/epics/src/common/human-right-panel.tsx`:
- Around line 930-933: The computed drag offsets passed to setCallPopupOffset
(x: drag.startOffsetX + (e.clientX - drag.startClientX), y: drag.startOffsetY +
(e.clientY - drag.startClientY)) are unbounded; clamp both x and y to sane
viewport bounds before calling setCallPopupOffset so the popup cannot be dragged
completely off-screen. Calculate min/max using 0 as the top/left bounds and
(window.innerWidth - popupWidth) / (window.innerHeight - popupHeight) as the
right/bottom bounds (obtain popupWidth/popupHeight from a measured element or
constants), clamp the computed x/y to those limits, then call setCallPopupOffset
with the clamped values; keep references to setCallPopupOffset, callPopupOffset,
and the drag calculation expression to locate the change.
- Around line 2713-2723: The popup opens without moving keyboard focus into the
custom dialog, so when the trigger unmounts users lose their focus anchor; add a
ref for the dialog container (e.g. callFullViewDialogRef) and ensure the
container is programmatically focusable (tabIndex={-1} or role="dialog" with
focusable element), then when opening callFullViewDialogRef.current?.focus()
(use an effect or the same open handler where setCallFullViewOpen(true) is
invoked) and preserve the trigger element in callFullViewTriggerRef to restore
focus on close (the close button already does this). Update the dialog component
that renders in full-view to accept and focus that ref so keyboard users always
get focus inside the dialog when it opens.
---
Duplicate comments:
In `@packages/epics/src/common/human-right-panel.tsx`:
- Around line 1306-1320: The reset branch inside the useEffect (the path that
runs when mode === 'space' and hasSpaceChatAccess is false) currently clears
room state but leaves draftAttachments and their blob URLs alive; update this
branch to revoke any Blob URLs and clear the draft attachments state: locate the
draftAttachments state variable and, before calling
setRoomId(null)/setMessages([])/setReplyDraft(null)/setEditDraft(null)/setInput('')
etc., iterate draftAttachments (or draftAttachmentsRef) and call
URL.revokeObjectURL(...) for each attachment blobUrl (safely checking
existence), then clear the attachments via setDraftAttachments([]) or
setDraftAttachments(null) to prevent leaks and stale re-appearance when access
is restored.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: 5790116e-1fe8-464e-81fd-176245bb5a8a
📒 Files selected for processing (6)
package.jsonpackages/core/src/matrix/client/hooks/space-group-call-utils.tspackages/core/src/matrix/client/providers/matrix-provider.tsxpackages/epics/src/common/human-chat-panel/human-chat-panel-call-join-strip.tsxpackages/epics/src/common/human-chat-panel/human-chat-panel-in-call-controls.tsxpackages/epics/src/common/human-right-panel.tsx
… features - Added detailed error handling for permission issues in Matrix call power levels. - Implemented focus management for the call full view dialog when opened. - Refined the join audio button logic to account for additional states and improve user experience.
- Adjusted formatting for popup width calculation for better clarity. - Enhanced readability of the conditional check for active button element in full view dialog handling.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/core/src/matrix/client/providers/matrix-provider.tsx`:
- Around line 570-571: The call to ensureRoomCallPowerLevels is being awaited
inside createRoom/joinRoom which causes callers to fail if reconciliation
(network/transient errors) throws even though the room operation already
succeeded; change the flow so createRoom/joinRoom returns success immediately
after the server operation and run ensureRoomCallPowerLevels asynchronously
(fire-and-forget) with its own try/catch that logs errors (use processLogger or
existing logger) so reconciliation failures do not cause the caller to see a
room creation/join failure; locate the logic around ensureRoomCallPowerLevels
and the createRoom/joinRoom functions to implement async invocation and internal
error handling.
In `@packages/epics/src/common/human-right-panel.tsx`:
- Around line 211-223: The chat history session key is only room-scoped which
can leak messages across accounts; update key generation to include the current
user id so the storage is user-scoped. Locate usages around
CHAT_HISTORY_SESSION_PREFIX, readPersistedChatHistory and the writer counterpart
(e.g., wherever sessionStorage.setItem is used to persist chat history) and
change the key format to `${CHAT_HISTORY_SESSION_PREFIX}${userId}:${roomId}` (or
equivalent) and ensure all read and write calls (including the restore logic
that runs before fresh timeline data arrives) use the same user-scoped key so
cached rows are only returned for the active user.
- Around line 1330-1351: The effect that runs on membership denial currently
clears room, messages, reply/edit drafts, input and errors (joinedRef, roomId,
setMessages, setReplyDraft, setEditDraft, setInput, setError,
setCallFullViewOpen) but leaves draft attachments, pending-send row, and
composer/reaction/delete errors behind; update this cleanup to also reset
attachment drafts, any pending-send queue/row, and composer/reaction/delete
error state by calling the corresponding setters (e.g. setAttachmentDrafts,
clearPendingSends or setPendingSendRow, setComposerError, setReactionError,
setDeleteError or their actual names in the file) inside the same useEffect so
all UI/memory state is fully cleared on access loss.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: 0919aebc-a572-4503-8e9f-c9b98fc65d4b
📒 Files selected for processing (3)
packages/core/src/matrix/client/providers/matrix-provider.tsxpackages/epics/src/common/human-chat-panel/human-chat-panel-call-join-strip.tsxpackages/epics/src/common/human-right-panel.tsx
Multiline stdout from `vercel deploy` broke single-line GITHUB_OUTPUT writes, leaving preview_url empty and causing `vercel alias set ""` to fail. Extract the *.vercel.app URL explicitly; fail with logs if missing. Use the globally installed Vercel CLI for alias to match deploy and avoid npx tar noise. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/deploy-preview.yml (1)
190-200:⚠️ Potential issue | 🟠 MajorPin the Vercel CLI to a specific version.
Line 129 installs Vercel with
vercel@latest, which is not pinned. This means a future Vercel release can change thealias setcommand behavior or break compatibility without any workflow change, breaking reproducibility. Pin the installation to a specific version (e.g.,vercel@33.0.0).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/deploy-preview.yml around lines 190 - 200, The workflow currently installs the Vercel CLI with an unpinned tag (vercel@latest); update the install step to pin a specific, tested version (e.g., vercel@33.0.0) so the subsequent steps that call vercel alias set (and use envs VERCEL_TOKEN, PREVIEW_ALIAS_SUFFIX, PREVIEW_URL) remain reproducible and won't break when the CLI changes; locate the step that runs the Vercel installation and replace the unpinned reference with the chosen exact version.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In @.github/workflows/deploy-preview.yml:
- Around line 190-200: The workflow currently installs the Vercel CLI with an
unpinned tag (vercel@latest); update the install step to pin a specific, tested
version (e.g., vercel@33.0.0) so the subsequent steps that call vercel alias set
(and use envs VERCEL_TOKEN, PREVIEW_ALIAS_SUFFIX, PREVIEW_URL) remain
reproducible and won't break when the CLI changes; locate the step that runs the
Vercel installation and replace the unpinned reference with the chosen exact
version.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 65516d78-5b09-4b35-accc-3a7090124d92
📒 Files selected for processing (1)
.github/workflows/deploy-preview.yml
Includes CodeRabbit autofix batch in commit 2bba3a1 (pnpm override upper bounds, ICE pool clamp, join watchdog guard, deep-link host, mention inbox a11y + profile parity, URL paren trimming).
Summary by CodeRabbit
New Features
Bug Fixes
Improvements