You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(meetings): build the v2 action slot on the resolver - #3313
Closes#1775 · epic #1765 · targets the integration branch feat/meeting-details-v2 (nothing reaches main until release PR #3249)
Summary
E2-01 adds the V2 action slot: the rail card's one control. It is driven by resolveActionSlot (E0-02), so no viewer ever gets V1's silent empty rail.
components/action-slot/
Always renders exactly one kind and carries it as data-kind, across all nine kinds.
none still renders as an empty slot, hidden with empty:hidden, rather than being absent (FR-020, SC-004).
join is complete. The other eight kinds get one line of real copy each until the issue that owns them builds the full design (see Decisions below). Visitors get a Sign in button that keeps the page URL, ?password= included, as returnTo.
The prototype's "Public meeting. Anyone with this link can join." shows under Join, only when privacy.openToPublic is true.
The early-join rule moves here from the time banner (decided on feat(meetings): add the v2 time banner #3297). Before the window, a viewer who will be able to join once it opens sees "You can join up to N minutes before the start time." The banner's before-line is now just "Starts in …".
components/join-action/: the Join control (FR-027).
States: one button with data-state loading, ready or error. Loading is also the server render, since the URL is fetched in the browser only.
Fetch:getPublicMeetingJoinUrl with the viewer's email, as V1 does. It refetches only when the meeting id, password or email changes, not on every lookup emission.
Link check: only http(s) links reach the anchor or window.open.
Errors: an error offers Try again. NOT_REGISTERED_FOR_MEETING explains that the account's email is not on the invite list; joining with another email is the guest form's job (E2-06).
Auto-join: V1's auto-join is kept, and ?zoom_redirect=false turns it off. With noopener, window.open always returns null, so V1's "Popup Blocked" branch could never run. V2 shows one toast that points at Join in case the popup was blocked.
components/organizer/: "Organized by" under a divider in the rail card, as in the prototype (FR-014, moved from E1-03).
The organizer is the owner, else the creator (resolveMeetingOrganizer).
It is absent for anonymous viewers and when nobody resolves.
The name falls back to the username, then the email, so a missing name never renders as "undefined". It wraps rather than truncating, so no hover-only tooltip is needed.
MeetingDetailsStateService gains privacy, viewerRole, actionSlot, joinsInWindow and signInHref.
The header and identity bar now read privacy and signInHref from it instead of computing their own.
The sign-in tests moved to the service spec.
Shared:MeetingJoinUrlState.
Specs:
testid-contract.md gets the new testids and a meeting-action-join-button[data-state] section, and drops the rail placeholder.
requirements.md records FR-029 and updates FR-012.
Decisions (project owner, 2026-10-06)
FR-029: inside the join window the slot is Join only, as in V1. To revisit with the RSVP card (E2-05) if late RSVPs become a need.
Interim copy: kinds other than join show one line of copy until their own issues land:
register: E2-02
invitation-required: E2-03, which already uses its contract testid
rsvp: E2-04 / E2-05
guest-join: E2-06
rsvp-unavailable: N-01
tools: E4
Deviation from the issue
The issue asks to keep V1's join-meeting-button-immediate / -error / -loading / -form testids. testid-contract.md, written after the issue, forbids V2 from sharing any V1 value. V2 therefore uses one meeting-action-join-button with data-state, and V1 keeps its own testids. This is noted in the contract.
Validation
Tests: V2 page specs 154; all meetings specs 872 before the review fixes; shared 3587. yarn build, check-types, lint:check (0 errors) and format:check are green.
Local visual check: run on a worktree dev server with the gate forced on and the meeting and join-URL calls stubbed. I checked join ready, join error (restricted, NOT_REGISTERED_FOR_MEETING), before the window (RSVP plus the early-join hint), and an anonymous visitor in the window (sign in, organizer hidden). All matched the prototype's rail. The temporary preview edits were reverted before any commit.
Pre-PR review: one round with the general, repo-code and learnings reviewers, all findings fixed in 59e1a6d:
http(s)-only join links
a stable fetch key
an honest auto-join toast
no hover-only tooltip on the organizer name
a missing px comment
the shared privacy signal
a zoom_redirect test that could not fail (both changed tests mutation-checked)
/lfx-self-serve-pr-readiness against the integration branch: READY. Its one nit is diff size, +1140, about 590 of which are specs and spec docs.
Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Essentials
Run ID: 960f48c8-72f7-4894-abd0-6ad2360bd8d7
You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.
Use the checkbox below for a quick retry:
🔍 Trigger review
Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
Medium Risk
Join flow touches meeting URLs, auto-open behavior, and sign-in returnTo (including query params); mistakes could affect access messaging or duplicate meeting tabs, but logic largely mirrors V1 with added validation and tests.
Overview
Adds the V2 meeting details action rail: a single ActionSlotKind from MeetingDetailsStateService (via resolveActionSlot), so viewers always see copy or a control instead of V1’s empty rail.
MeetingActionSlotComponent switches on all nine kinds: full Join via MeetingJoinActionComponent (fetch join URL, loading/ready/error, http(s) validation, V1 auto-join with claimAutoJoin and ?zoom_redirect=false), interim messages and Sign in for visitors, public-meeting explainer, and early-join hint moved out of the time banner. MeetingOrganizerComponent shows “Organized by” under a divider for signed-in users.
MeetingDetailsStateService centralizes privacy, viewerRole, actionSlot, joinsInWindow, signInHref, pastAccessKnown, and past-load ended time state; header and identity bar consume those signals. Page shell wires the new rail pieces and drops the rail placeholder. Specs and testid-contract.md / requirements.md document FR-012, FR-029, and new testids.
Reviewed by Cursor Bugbot for commit 7fff9e8. Bugbot is set up for automated code reviews on this repo. Configure here.
@nunoeufrasio I'm starting review 1 of this pull request now (started 2026-10-06 10:10 UTC). This usually takes a few minutes — I'll update this comment with the summary when I'm done.
Hi @nunoeufrasio, thanks for a well-scoped and well-documented PR.
The action slot is cleanly driven by resolveActionSlot, none renders as an empty slot rather than a missing one, and the Join control handles loading/ready/error, http(s)-only links and a stable fetch key. Moving privacy and signInHref into the state service removes duplicated logic from the header and identity bar. I found no correctness, security, or privacy problems.
⚪ Nit: 1 issue: module-level isHttpUrl helper belongs in @lfx-one/shared/utils
✅ Approved
➡️ This auto-review agent is a preview. Please send feedback to @dealako.
Before the join window, resolveUpcomingActionSlot() returns rsvp for organizers whenever RSVP tracking is enabled, even if meeting.invited is false. This message therefore incorrectly tells those organizers they are on the invite list. Use neutral copy here until the RSVP card lands, or check meeting.invited before claiming the viewer is invited.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1775 · epic #1765 · targets the integration branch
feat/meeting-details-v2(nothing reachesmainuntil release PR #3249)Summary
E2-01 adds the V2 action slot: the rail card's one control. It is driven by
resolveActionSlot(E0-02), so no viewer ever gets V1's silent empty rail.components/action-slot/data-kind, across all nine kinds.nonestill renders as an empty slot, hidden withempty:hidden, rather than being absent (FR-020, SC-004).joinis complete. The other eight kinds get one line of real copy each until the issue that owns them builds the full design (see Decisions below). Visitors get a Sign in button that keeps the page URL,?password=included, asreturnTo.privacy.openToPublicis true.components/join-action/: the Join control (FR-027).data-stateloading, ready or error. Loading is also the server render, since the URL is fetched in the browser only.getPublicMeetingJoinUrlwith the viewer's email, as V1 does. It refetches only when the meeting id, password or email changes, not on every lookup emission.http(s)links reach the anchor orwindow.open.NOT_REGISTERED_FOR_MEETINGexplains that the account's email is not on the invite list; joining with another email is the guest form's job (E2-06).?zoom_redirect=falseturns it off. Withnoopener,window.openalways returns null, so V1's "Popup Blocked" branch could never run. V2 shows one toast that points at Join in case the popup was blocked.components/organizer/: "Organized by" under a divider in the rail card, as in the prototype (FR-014, moved from E1-03).resolveMeetingOrganizer).MeetingDetailsStateServicegainsprivacy,viewerRole,actionSlot,joinsInWindowandsignInHref.privacyandsignInHreffrom it instead of computing their own.MeetingJoinUrlState.testid-contract.mdgets the new testids and ameeting-action-join-button[data-state]section, and drops therailplaceholder.requirements.mdrecords FR-029 and updates FR-012.Decisions (project owner, 2026-10-06)
joinshow one line of copy until their own issues land:register: E2-02invitation-required: E2-03, which already uses its contract testidrsvp: E2-04 / E2-05guest-join: E2-06rsvp-unavailable: N-01tools: E4Deviation from the issue
The issue asks to keep V1's
join-meeting-button-immediate/-error/-loading/-formtestids.testid-contract.md, written after the issue, forbids V2 from sharing any V1 value. V2 therefore uses onemeeting-action-join-buttonwithdata-state, and V1 keeps its own testids. This is noted in the contract.Validation
yarn build,check-types,lint:check(0 errors) andformat:checkare green.NOT_REGISTERED_FOR_MEETING), before the window (RSVP plus the early-join hint), and an anonymous visitor in the window (sign in, organizer hidden). All matched the prototype's rail. The temporary preview edits were reverted before any commit.http(s)-only join linksprivacysignalzoom_redirecttest that could not fail (both changed tests mutation-checked)/lfx-self-serve-pr-readinessagainst the integration branch: READY. Its one nit is diff size, +1140, about 590 of which are specs and spec docs./preflight: passes.🤖 Generated with Claude Code