Skip to content
This repository was archived by the owner on Aug 17, 2026. It is now read-only.

Fix active huddle discovery - #68

Merged
100yenadmin merged 3 commits into
mainfrom
fix/63-huddle-discovery
Jul 28, 2026
Merged

Fix active huddle discovery#68
100yenadmin merged 3 commits into
mainfrom
fix/63-huddle-discovery

Conversation

@100yenadmin

Copy link
Copy Markdown
Member

Summary

  • reconstruct huddle lifecycle state in a shared native helper
  • subscribe the sidebar to authorized channel huddle lifecycle events and show a small active-huddle badge
  • keep current-channel huddle join behavior native; no Electric/Supabase broker
  • fix the E2E mock relay to honor all channel ids in multi-#h live filters

Fixes #63. Part of #36/#1.

Local proof on a5f0871

  • pnpm -C desktop exec node --import ./test-loader.mjs --experimental-strip-types --test src/features/huddle/lib/activeHuddleState.test.mjs src/features/huddle/lib/huddleCardState.test.mjs
  • pnpm -C desktop exec biome check src/features/huddle/lib/activeHuddleState.ts src/features/huddle/lib/activeHuddleState.test.mjs src/features/huddle/lib/useActiveHuddlesByChannelId.ts src/features/huddle/components/HuddleIndicator.tsx src/features/sidebar/ui/AppSidebar.tsx src/features/sidebar/ui/CustomChannelSection.tsx src/features/sidebar/ui/SidebarSection.tsx src/shared/api/relayClientSession.ts src/testing/e2eBridge.ts tests/e2e/channels.spec.ts
  • pnpm -C desktop typecheck
  • pnpm -C desktop build:e2e
  • pnpm -C desktop exec playwright test --project=smoke tests/e2e/channels.spec.ts -g "sidebar surfaces active huddles|huddle rollback end event"
  • git diff --check

Proof boundary

This proves source behavior and mock-relay discovery only. It does not claim installed two-client huddle runtime proof; that remains for the cumulative Internal Canary artifact.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 55 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d4ba1cad-17e5-406f-b195-6302ba484ba0

📥 Commits

Reviewing files that changed from the base of the PR and between 6833404 and 7aa7eb0.

📒 Files selected for processing (7)
  • desktop/src/features/huddle/components/HuddleIndicator.tsx
  • desktop/src/features/huddle/lib/activeHuddleState.test.mjs
  • desktop/src/features/huddle/lib/activeHuddleState.ts
  • desktop/src/features/huddle/lib/useActiveHuddlesByChannelId.ts
  • desktop/src/features/sidebar/ui/SidebarSection.tsx
  • desktop/src/shared/api/relayClientSession.ts
  • desktop/tests/e2e/channels.spec.ts
📝 Walkthrough

Walkthrough

The change centralizes active huddle reconstruction, adds multi-channel relay subscriptions, propagates summaries through sidebar sections, and renders participant badges. Replay tests and an end-to-end test cover active, ended, stale, isolated, and late-mounted huddle states.

Changes

Active huddle discovery

Layer / File(s) Summary
Huddle reconstruction contract and replay
desktop/src/features/huddle/lib/activeHuddleState.ts, desktop/src/features/huddle/lib/activeHuddleState.test.mjs, desktop/src/features/huddle/components/HuddleIndicator.tsx
Lifecycle events are parsed and replayed into ActiveHuddleSummary values, with tests for ordering, termination, staleness, missing starts, participant tracking, and channel isolation.
Multi-channel relay subscription
desktop/src/features/huddle/lib/useActiveHuddlesByChannelId.ts, desktop/src/shared/api/relayClientSession.ts
The sidebar subscribes to huddle events for multiple channel IDs, deduplicates events, refreshes expiry state, and disposes combined subscriptions.
Sidebar huddle indicator wiring
desktop/src/features/sidebar/ui/AppSidebar.tsx, desktop/src/features/sidebar/ui/CustomChannelSection.tsx, desktop/src/features/sidebar/ui/SidebarSection.tsx
Active huddle summaries flow through channel sections to ChannelMenuButton, which conditionally renders a headphones badge with the participant count.
Mock subscription and sidebar validation
desktop/src/testing/e2eBridge.ts, desktop/tests/e2e/channels.spec.ts
Mock live subscriptions collect all channel filter values, and end-to-end coverage verifies the indicator appears after start and disappears after end.

Estimated code review effort: 4 (Complex) | ~60 minutes

Suggested reviewers: wesbillman, wpfleger96

Sequence Diagram(s)

sequenceDiagram
  participant AppSidebar
  participant useActiveHuddlesByChannelId
  participant RelayClient
  participant HuddleState
  participant ChannelMenuButton
  AppSidebar->>useActiveHuddlesByChannelId: provide channel IDs
  useActiveHuddlesByChannelId->>RelayClient: subscribe to huddle events
  RelayClient-->>useActiveHuddlesByChannelId: deliver lifecycle events
  useActiveHuddlesByChannelId->>HuddleState: reconstruct active summaries
  HuddleState-->>useActiveHuddlesByChannelId: return summaries by parent channel
  useActiveHuddlesByChannelId-->>AppSidebar: provide active huddles
  AppSidebar->>ChannelMenuButton: pass channel huddle summary
  ChannelMenuButton-->>AppSidebar: render participant badge
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed PASS (94%): Title matches the core change: fixing active huddle discovery.
Description check ✅ Passed PASS (96%): The description accurately tracks the shared helper, sidebar badge, native joining, and mock relay updates.
Linked Issues check ✅ Passed PASS (89%): It reconstructs relay history, preserves native joins, hides ended huddles, and adds replay/visibility coverage for #63.
Out of Scope Changes check ✅ Passed PASS (92%): The diff stays on active-huddle discovery and related plumbing; no unrelated feature work stands out.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/63-huddle-discovery

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

@100yenadmin
100yenadmin force-pushed the fix/63-huddle-discovery branch from a5f0871 to 63dab0b Compare July 28, 2026 16:08
@100yenadmin
100yenadmin marked this pull request as ready for review July 28, 2026 16:14

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 63dab0b55e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +403 to +405
kinds: [48100, 48101, 48102, 48103],
"#h": ids,
limit: Math.min(1000, Math.max(100, ids.length * 20)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep live huddle subscriptions channel-scoped

When the sidebar contains more than one channel, this multi-value #h filter is classified as global by extract_channel_id_from_filters (crates/buzz-relay/src/handlers/req.rs:1009-1032). The registry intentionally does not fan channel-scoped events out to global subscriptions (crates/buzz-relay/src/subscription.rs:271-327), so the callback receives historical replay but no newly published 48100–48103 events; the sidebar badge therefore does not update live under the real relay, although the modified mock does. Use independently channel-scoped subscriptions or add real multi-channel subscription support.

Useful? React with 👍 / 👎.

Comment on lines +36 to +40
function updateState() {
if (disposed) return;
setActiveHuddles(
reconstructActiveHuddlesByParentChannel(seenEvents.values()),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Schedule active huddle expiry

If a start event is followed by no lifecycle event, updateState runs only when that event arrives, so the Date.now()-based one-hour stale check in reconstruction is never evaluated again. After the ephemeral channel expires, the sidebar can continue showing a joinable huddle indefinitely and send users to an archived channel; schedule a recomputation for the nearest expiry or maintain a periodic expiry timer.

Useful? React with 👍 / 👎.

Comment on lines +394 to +397
async subscribeToHuddleEventsForChannels(
channelIds: string[],
onEvent: (event: RelayEvent) => void,
) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Document the new multi-channel subscription API

subscribeToHuddleEventsForChannels is a new public method on the exported RelayClient, but it has no doc comment. Document its historical/live behavior, channel handling, and result limits as required for new public APIs.

AGENTS.md reference: AGENTS.md:L116-L119

Useful? React with 👍 / 👎.

@100yenadmin
100yenadmin force-pushed the fix/63-huddle-discovery branch from 63dab0b to 40dddcd Compare July 28, 2026 16:29

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 40dddcd3ee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +137 to +138
const freshnessAnchor = state.startedAt ?? state.lastEventAt;
if (isHuddleStartStale(freshnessAnchor, nowMs)) continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep inferred huddle expiry anchored to its start

When the 100-event subscription window drops the start event, startedAt is null and this falls back to the most recent participant event. A join or leave at minute 59 therefore keeps the sidebar badge joinable until minute 119, even though the ephemeral channel's TTL is fixed at one hour from creation, so users can be routed to an archived channel. Fresh evidence beyond the previously reported timer issue is this missing-start path, which still extends the expiry after the timer was added; recover the original start time or avoid extending expiry from participant activity.

Useful? React with 👍 / 👎.

);
}

export function huddleParentChannelId(event: RelayEvent): string | null {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Document the exported huddle state API

The new module exports its summary type, lifecycle parsers, and reconstruction helper without doc comments, leaving ordering, stale-event handling, and one-huddle-per-parent selection as undocumented API contracts. Add doc comments to these new exports as required by the repository contributor rules.

AGENTS.md reference: AGENTS.md:L116-L119

Useful? React with 👍 / 👎.

@100yenadmin
100yenadmin force-pushed the fix/63-huddle-discovery branch from 40dddcd to 6833404 Compare July 28, 2026 16:44

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
desktop/src/features/huddle/components/HuddleIndicator.tsx (1)

48-97: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Duplicate live relay subscriptions for the same channel's huddle events when it's both open and visible in the sidebar. HuddleIndicator and useActiveHuddlesByChannelId each independently call into RelayClient with the identical filter (kinds 48100-48103, #h: [channelId]) and each maintain their own seenEvents/reconstruction, because there's no shared subscription/state layer between the "current channel" indicator and the sidebar-wide hook.

  • desktop/src/features/huddle/components/HuddleIndicator.tsx#L48-L97: currently opens its own subscribeToHuddleEvents(channelId, ...); consider sourcing activeHuddle for the currently-open channel from the same map useActiveHuddlesByChannelId already computes at the sidebar/app level (passed down as a prop) instead of re-subscribing.
  • desktop/src/features/huddle/lib/useActiveHuddlesByChannelId.ts#L26-L93: already subscribes to the current channel's huddle events as part of the full channel set — this is the subscription HuddleIndicator should be able to reuse.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/src/features/huddle/components/HuddleIndicator.tsx` around lines 48 -
97, The current-channel indicator creates a duplicate relay subscription instead
of reusing sidebar state. In
desktop/src/features/huddle/components/HuddleIndicator.tsx lines 48-97, remove
the local subscribeToHuddleEvents, seenEvents, reconstruction, and cleanup flow,
and consume the active-huddle entry from the map produced by
useActiveHuddlesByChannelId via a prop or existing shared state. In
desktop/src/features/huddle/lib/useActiveHuddlesByChannelId.ts lines 26-93,
retain its existing subscription and reconstruction as the single source for
both views; no direct change is required unless needed to expose the map to
HuddleIndicator.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@desktop/src/features/huddle/components/HuddleIndicator.tsx`:
- Around line 165-168: The participant-count floor logic is duplicated and can
diverge between badges. Add and export a shared getHuddleParticipantCount helper
from activeHuddleState.ts, then update HuddleIndicator.tsx lines 165-168 and
SidebarSection.tsx lines 156-187 to use it instead of local Math.max(1,
participantPubkeys.size) calculations, preserving the current minimum of one.

In `@desktop/src/shared/api/relayClientSession.ts`:
- Around line 391-409: Update subscribeToHuddleEventsForChannels to use
Promise.allSettled when subscribing across ids, retain the disposers from
fulfilled results, and dispose them all if any subscription rejects before
rethrowing or surfacing the failure. Preserve the existing returned cleanup
function for the case where every channel subscription succeeds.

In `@desktop/tests/e2e/channels.spec.ts`:
- Around line 219-274: The local waitForMockHuddleSubscription helper duplicates
the shared live-subscription polling logic. Remove it and replace its call sites
with waitForMockLiveSubscription(page, "random", KIND_HUDDLE_STARTED), relying
on the existing beforeEach-installed mock bridge.

---

Outside diff comments:
In `@desktop/src/features/huddle/components/HuddleIndicator.tsx`:
- Around line 48-97: The current-channel indicator creates a duplicate relay
subscription instead of reusing sidebar state. In
desktop/src/features/huddle/components/HuddleIndicator.tsx lines 48-97, remove
the local subscribeToHuddleEvents, seenEvents, reconstruction, and cleanup flow,
and consume the active-huddle entry from the map produced by
useActiveHuddlesByChannelId via a prop or existing shared state. In
desktop/src/features/huddle/lib/useActiveHuddlesByChannelId.ts lines 26-93,
retain its existing subscription and reconstruction as the single source for
both views; no direct change is required unless needed to expose the map to
HuddleIndicator.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fd6af4ee-a43d-4fdb-9283-499b4cac069b

📥 Commits

Reviewing files that changed from the base of the PR and between 0db5fc4 and 6833404.

📒 Files selected for processing (10)
  • desktop/src/features/huddle/components/HuddleIndicator.tsx
  • desktop/src/features/huddle/lib/activeHuddleState.test.mjs
  • desktop/src/features/huddle/lib/activeHuddleState.ts
  • desktop/src/features/huddle/lib/useActiveHuddlesByChannelId.ts
  • desktop/src/features/sidebar/ui/AppSidebar.tsx
  • desktop/src/features/sidebar/ui/CustomChannelSection.tsx
  • desktop/src/features/sidebar/ui/SidebarSection.tsx
  • desktop/src/shared/api/relayClientSession.ts
  • desktop/src/testing/e2eBridge.ts
  • desktop/tests/e2e/channels.spec.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (10)
  • GitHub Check: Desktop E2E Integration (2/2)
  • GitHub Check: Desktop E2E Integration (1/2)
  • GitHub Check: Desktop Core
  • GitHub Check: Desktop Smoke E2E (4)
  • GitHub Check: Desktop Build (macOS)
  • GitHub Check: Desktop Smoke E2E (2)
  • GitHub Check: Desktop Smoke E2E (3)
  • GitHub Check: Desktop Smoke E2E (1)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (rust)
🧰 Additional context used
📓 Path-based instructions (3)
desktop/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use rem-based Tailwind text tokens rather than arbitrary px, rem, or em text-size literals; prefer stock tokens, text-2xs, or text-3xs.

Files:

  • desktop/src/testing/e2eBridge.ts
  • desktop/src/features/huddle/lib/activeHuddleState.ts
  • desktop/src/features/sidebar/ui/AppSidebar.tsx
  • desktop/src/features/huddle/lib/useActiveHuddlesByChannelId.ts
  • desktop/src/features/sidebar/ui/CustomChannelSection.tsx
  • desktop/src/shared/api/relayClientSession.ts
  • desktop/src/features/huddle/components/HuddleIndicator.tsx
  • desktop/src/features/sidebar/ui/SidebarSection.tsx
desktop/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

desktop/src/**/*.{ts,tsx}: Chat body and author text must use text-base; surrounding timeline elements should use deliberate steps on the stock text ramp.
When adding a community-scoped module-level cache, Map, or class instance, add its reset to resetCommunityState() in desktop/src/features/communities/useCommunityInit.ts.

Files:

  • desktop/src/testing/e2eBridge.ts
  • desktop/src/features/huddle/lib/activeHuddleState.ts
  • desktop/src/features/sidebar/ui/AppSidebar.tsx
  • desktop/src/features/huddle/lib/useActiveHuddlesByChannelId.ts
  • desktop/src/features/sidebar/ui/CustomChannelSection.tsx
  • desktop/src/shared/api/relayClientSession.ts
  • desktop/src/features/huddle/components/HuddleIndicator.tsx
  • desktop/src/features/sidebar/ui/SidebarSection.tsx
desktop/tests/e2e/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

desktop/tests/e2e/**/*.ts: Every desktop Playwright E2E test must call installMockBridge(page) for mock Tauri IPC.
Call page.addInitScript before installMockBridge(page) when seeding local storage or other initial state.
Call waitForMockLiveSubscription(page, channelName) before emitting mock live messages.
Call waitForAnimations(page) before every page.screenshot() or locator.screenshot().
Scope screenshots to their subject with locator.screenshot() or an appropriate clip; verify multiple screenshots have distinct hashes before posting.

Files:

  • desktop/tests/e2e/channels.spec.ts
🔇 Additional comments (10)
desktop/src/features/huddle/lib/activeHuddleState.ts (2)

1-137: LGTM!


138-158: 🎯 Functional Correctness

Start-based staleness is intentional here. HUDDLE_JOINABLE_WINDOW_SECONDS is a hard joinable TTL from startedAt, and both reconstructActiveHuddlesByParentChannel and useActiveHuddlesByChannelId use the same window. Confidence 92%.

			> Likely an incorrect or invalid review comment.
desktop/src/features/huddle/lib/activeHuddleState.test.mjs (1)

1-182: LGTM!

desktop/src/features/huddle/components/HuddleIndicator.tsx (1)

14-19: LGTM!

Also applies to: 44-45, 57-63, 168-168

desktop/src/shared/api/relayClientSession.ts (1)

361-366: LGTM!

desktop/src/features/sidebar/ui/AppSidebar.tsx (1)

16-16: LGTM!

Also applies to: 242-242, 633-633, 685-685, 741-741, 783-783, 831-831

desktop/src/features/sidebar/ui/CustomChannelSection.tsx (1)

58-58: LGTM!

Also applies to: 342-386, 445-446, 459-462, 552-593, 752-754

desktop/src/features/sidebar/ui/SidebarSection.tsx (1)

8-8: LGTM!

Also applies to: 21-21, 289-301, 350-356, 382-410, 485-488

desktop/tests/e2e/channels.spec.ts (1)

18-18: LGTM!

desktop/src/testing/e2eBridge.ts (1)

8753-8790: 🎯 Functional Correctness

No issue: live #h filters stay single-channel — all current callers pass one channel id, so the GLOBAL_MOCK_SUBSCRIPTION fallback is not exercised and does not over-broadcast. Confidence 94%.

			> Likely an incorrect or invalid review comment.

Comment thread desktop/src/features/huddle/components/HuddleIndicator.tsx Outdated
Comment thread desktop/src/shared/api/relayClientSession.ts Outdated
Comment thread desktop/tests/e2e/channels.spec.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 68334040c8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +398 to +404
const disposes = await Promise.all(
ids.map((id) =>
this.subscribe(
{ kinds: [48100, 48101, 48102, 48103], "#h": [id], limit: 100 },
onEvent,
),
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Throttle the initial channel subscription burst

When a user has many joined channels, this Promise.all immediately sends one REQ per channel; the relay's default admission budget is only 50 WebSocket events per five-second burst (crates/buzz-auth/src/rate_limit.rs and crates/buzz-relay/src/admission.rs), shared with the rest of desktop startup. A sidebar with roughly 50 channels—or fewer when other startup subscriptions run—therefore gets CLOSED responses and can also delay the visible timeline while retries wait for the rate-limit window. Batch these initial REQs as reconnect replay already does in desktop/src/shared/api/relayReconnectReplay.ts.

Useful? React with 👍 / 👎.

Comment on lines +400 to +402
this.subscribe(
{ kinds: [48100, 48101, 48102, 48103], "#h": [id], limit: 100 },
onEvent,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the start event beyond the lifecycle row limit

For an active huddle that accumulates more than 100 join/leave events, a fresh sidebar or header subscription receives only the newest 100 rows, so the start event is omitted; reconstruction then explicitly discards the state at activeHuddleState.ts:141, hiding the active huddle and its join action until another session starts. Fresh evidence beyond the earlier missing-start expiry concern is that this revision now drops startless histories entirely rather than merely anchoring them incorrectly; fetch the recent start separately or otherwise guarantee it remains in the replay.

Useful? React with 👍 / 👎.

Comment on lines 398 to 405
const disposes = await Promise.all(
ids.map((id) =>
this.subscribe(
{ kinds: [48100, 48101, 48102, 48103], "#h": [id], limit: 100 },
onEvent,
),
),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Dispose subscriptions when batch setup fails

If a socket failure makes any per-channel subscribe reject after other calls in this Promise.all have succeeded, the method rejects without returning or invoking the successful disposers. Those live subscriptions remain registered in RelayClient; after the channel list changes, the old hook cannot close them and the replacement effect adds another full set, leaking subscriptions until a community disconnect and potentially reaching the relay's 1,024-subscription cap. Clean up every successfully created subscription on the rejection path.

Useful? React with 👍 / 👎.

Comment on lines +12 to +14
export function useActiveHuddlesByChannelId(
channels: readonly Channel[],
): ReadonlyMap<string, ActiveHuddleSummary> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Document the exported huddle discovery hook

Add a doc comment to this new exported hook describing its per-channel historical/live subscription behavior, expiry handling, and returned summary map; without it, the new public API leaves important lifecycle semantics undocumented and violates the repository's explicit public-API requirement.

AGENTS.md reference: AGENTS.md:L116-L119

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1e7463978

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +152 to +153
const existing = activeByParent.get(state.parentChannelId);
if (existing && existing.lastEventAt >= state.lastEventAt) continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not revive a superseded huddle

When an older session has no 48103 row (a failure mode already acknowledged by HuddleIndicator) and a newer huddle in the same parent subsequently starts and ends, the newer ended state is skipped while this comparison still selects the older non-ended state. The sidebar and header then resurface the superseded huddle and offer a link to its already archived ephemeral channel. Treat a newer start as superseding prior sessions for that parent, even after the newer session ends.

Useful? React with 👍 / 👎.

Comment on lines +92 to +96
const sorted = [...events].sort(
(left, right) =>
left.created_at - right.created_at ||
eventOrder(left.kind) - eventOrder(right.kind) ||
left.id.localeCompare(right.id),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve participant transitions within the same second

When a participant disconnects and rejoins within one second, both relay-generated lifecycle rows have the same second-resolution created_at, but this ordering always places the 48101 join before the 48102 leave regardless of their actual emission order. Reconstruction therefore deletes the rejoined participant and understates the active count in both the sidebar and header; preserve a causal ordering for same-second join/leave transitions instead of ordering them by kind.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7aa7eb03d3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +396 to +399
for (const id of ids)
disposes.push(
await this.subscribe(
{ kinds: [48100, 48101, 48102, 48103], "#h": [id], limit: 500 },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Pace per-channel subscription setup

For users with roughly 50 or more joined channels, this sequential loop can still exhaust the relay's 50-events-per-five-second admission budget, especially alongside other startup subscriptions. Fresh evidence beyond the previously reported Promise.all burst is that subscribe() resolves as soon as EOSE arrives—and even a rate-limited CLOSED resolves its readiness promise—so await does not enforce pacing and the loop can emit REQs as quickly as the relay answers them. The resulting CLOSED retries delay huddle discovery and can rate-limit unrelated startup traffic; explicitly pace or batch these requests through the existing replay/rate-limit machinery.

Useful? React with 👍 / 👎.

Comment on lines +75 to +78
case KIND_HUDDLE_PARTICIPANT_LEFT:
return 1;
case KIND_HUDDLE_PARTICIPANT_JOINED:
return 2;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve same-second join-then-leave transitions

When a participant joins and then disconnects within the same second while other people keep the huddle active, both relay-generated rows have the same second-resolution created_at; this fixed leave-before-join ordering replays the leave first and then re-adds the participant, overstating the sidebar and header count. Fresh evidence beyond the prior rejoin report is this inverse transition: changing the global tiebreak fixes leave-then-rejoin but necessarily breaks join-then-leave, so same-second lifecycle rows need a causal ordering rather than a kind-only order.

Useful? React with 👍 / 👎.

Comment on lines +70 to +72
if (disposed || seenEvents.has(event.id)) return;
seenEvents.set(event.id, event);
updateState();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Coalesce historical lifecycle reconstruction

When several joined channels have substantial lifecycle history, the replay callback invokes updateState() once per returned row, and each invocation re-sorts every row accumulated so far and publishes a fresh Map. With the new 500-row limit per channel, startup can therefore perform quadratic replay work on the UI thread and repeatedly invalidate the entire sidebar, causing a visible freeze for large or high-churn communities; coalesce buffered historical callbacks and reconstruct once per batch, while retaining the previous map when its content is unchanged.

AGENTS.md reference: AGENTS.md:L422-L422

Useful? React with 👍 / 👎.

@100yenadmin
100yenadmin merged commit e4565f4 into main Jul 28, 2026
30 checks passed
@100yenadmin
100yenadmin deleted the fix/63-huddle-discovery branch July 28, 2026 18:01
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Upstream bug: make active huddles discoverable to authorized channel members

1 participant