feat(meet): redesign Meetings — platform selector + history master-detail#4308
Conversation
Prefill 'Your name in this meeting' from the Composio account that best matches the meeting platform (own account -> mailbox -> blank). Composio exposes only an account email, so derive a 'First Last' display name from the local part. Only prefills while the field is untouched, so it never clobbers manual input.
Phase 1A of the meetings redesign. Replace the single-platform inline form with a hero composer + Google Meet/Zoom/Teams/Webex platform chips, un-hardcoding the platform so the selection flows through to the join payload, URL placeholder, and display-name auto-fill source. Extract the new components into app/src/components/meetings/ (MeetingsPage, MeetComposer, PlatformChips, ActiveMeetingBanner, meetingUtils); MeetingBotsCard becomes a back-compat re-export shim. The chips are a pure selector — no connection is required to send the bot.
Phase 1B of the meetings redesign. Replace the flat expandable recent- calls list with a master-detail history view: a searchable, platform- filtered, date-grouped call rail beside a detail pane that lazy-loads each call's summary and transcript. Add a rich TranscriptViewer that parses the [MM:SS] [Name] line prefix into timestamped, speaker-labeled, role-colored lines with copy/download, and an ActionItemChecklist that surfaces executable items with a Run-with-OpenHuman action. Infer the platform from the meeting URL for per-row icons. Removes the old RecentCallsSection (now only used here).
# Conflicts: # app/src/components/skills/MeetingBotsCard.tsx
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds upcoming-meeting retrieval, per-event and per-platform join-policy handling, strict meeting URL validation, and a redesigned meetings UI with composer, active banner, upcoming table, history views, defaults drawer, and translated meeting-bot strings. ChangesMeetings Phase 3: Upcoming Meetings, Policy Overrides, and UI Redesign
Possibly related issues
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
Phase 2 of the meetings redesign. Add a full-width Upcoming table between the composer and history that lists calendar meetings with a conferencing link, the platform inferred from that link, invitee count, a relative/ absolute start time, and a per-meeting Auto/Ask/Skip join-policy toggle (local state this phase; persistence lands in Phase 3). A Join-now action appears on imminent meetings. Backend: new openhuman.meet_list_upcoming JSON-RPC fetches upcoming events via the existing Composio GOOGLECALENDAR_EVENTS_LIST path (reused, not a heartbeat refactor), extracts the join link from hangoutLink / conferenceData / description, infers the platform, and returns typed UpcomingMeeting records with the effective global join policy. Wired through agent_meetings ops/schemas + json_rpc_e2e coverage.
Phase 3 (final) of the meetings redesign. Make the Auto/Ask/Skip join policy persist and actually drive auto-join, with resolution order per-event override > per-platform default > global default. Backend: new meeting_event_policies SQLite table + meet_set_event_policy / meet_get_event_policies RPCs; add platform_auto_join_policies map to MeetConfig (serde-defaulted, backward compatible); resolve_effective_join_policy feeds both meet_list_upcoming (effective per-row policy) and the calendar auto-join candidate handler (policy source only — scheduler untouched); the heartbeat planner now threads the calendar_event_id through. Frontend: MeetDefaultsDrawer (gear button) for global + per-platform defaults and the existing listen-only/summarize/ingest settings; the Upcoming table's policy toggle persists via setEventPolicy with optimistic update + revert, and shows auto/ask hints per row.
The Upcoming table used bg-surface/50 + border-line/50, letting the dotted page background bleed through. Match the composer card styling (rounded-2xl border border-line bg-surface shadow-soft) so it reads as a solid card.
History rail: replace the full-width platform <select> with a compact, icon-only dropdown placed to the left of the search bar — it shows just the selected platform's logo when closed and the icon + name for each option when open. Reuse inferPlatformFromUrl for the per-row icons. History: auto-select the first (most recent) visible call so the detail pane is populated by default and re-snaps when the active filter hides the current selection.
RECENT CALLS rendered directly on the page, so the dotted background bled through. Wrap it in the same rounded-2xl border bg-surface shadow-soft card used by the composer and Upcoming sections.
- Optimistic join-policy revert no longer clobbers a newer concurrent change: revert only if the current value still equals the optimistic value this call set (UpcomingTable). - Route relative-time labels through i18n (skills.meetingBots.relative.*) instead of hardcoded English in formatWhen/formatRelativeTime; real translations added to all 14 locales. - Upcoming poll no longer flips back to the loading skeleton on each 60s background tick (only initial load + manual refresh show loading). - Dedupe MascotMeetPlatform into a MeetingPlatform alias. - Single source of truth for the Composio logo URL: export composioLogoUrl and have platformLogoUrl delegate to it.
- SECURITY: strict host validation on the calendar auto-join path — is_meeting_url now parses the URL and matches ALLOWED_HOSTS by exact host or proper dot-suffix (rejects meet.google.com.attacker.com), and handle_calendar_meeting_candidate validates before any bot:join. - Include in-progress meetings in the upcoming list (keep while effective_end >= now && start <= window_end) so ongoing meetings and their Join-now action no longer vanish at start time. - Consistent calendar-event id across UI, heartbeat, and webhook paths (id -> eventId -> icalUID), so a per-event skip/auto override is honored on the live trigger path; warn-log on unkeyable payloads. - Distinct synthetic dedup key (meet_url@start) for id-less events so they no longer collapse to a single 'unknown' row. - Respect the caller's limit for GOOGLECALENDAR_EVENTS_LIST maxResults. - Batch per-event policy lookups (one connection/query) instead of opening a fresh SQLite connection + migration per meeting. - Consolidate the triplicated URL/host/extraction helpers into ops.rs.
Set "platform" in the auto-join bot:join payload so Zoom/Teams/Webex meetings route to the right provider instead of defaulting to Google Meet. Resolved via the strict infer_platform_from_url (same host validation as the manual-join path; unknown host falls back to gmeet). Folds in the remaining unique change from tinyhumansai#4307 (superseded by this branch's host-detection consolidation) without its loose substring host match.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 06f51fa1f7
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| "connectionId": conn.id, | ||
| "timeMin": now.to_rfc3339(), | ||
| "timeMax": end_window.to_rfc3339(), | ||
| "maxResults": max_results, |
There was a problem hiding this comment.
Do not cap the calendar page before link filtering
Using the caller's display limit as Google Calendar maxResults means the RPC only inspects the first N calendar events, but the conferencing-link filter runs later in try_extract_meeting. For a calendar where the first 20 events in the 8-hour window are reminders/OOO/no-link items and a Meet/Zoom call is event 21, the Upcoming table incorrectly omits that valid meeting and its join-policy controls. Fetch a larger page or paginate until enough meeting-link events have been collected after filtering.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in f4a718f — page_size now fetches the API max (100) BEFORE the conferencing-link filter, so link-bearing events behind reminders/OOO aren't dropped; truncate(limit) still applies post-filter.
| log('[detail] no summary yet, scheduling retry in 2000ms for', record.request_id); | ||
| const timer = setTimeout(() => { | ||
| log('[detail] retrying detail load for', record.request_id); | ||
| void loadDetail(record.request_id); | ||
| }, 2000); |
There was a problem hiding this comment.
Stop retrying missing summaries indefinitely
This effect schedules another load every time the detail returns loaded without a summary, so any older call where summary generation failed or was never written will keep hitting meet_agent_get_call_detail every 2 seconds for as long as the detail pane stays open. The comment says this should retry once; track that a retry has already been attempted for the selected request_id or bound the polling.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 4ca9a78 — retry now fires at most once per request_id (retriedForRef set), no more 2s polling loop.
| const result = await getMeetCallDetail(requestId); | ||
| log('[detail] loaded detail for', requestId, 'hasSummary=%s', hasSummaryDetail(result)); | ||
| setDetail(result); | ||
| setStatus('loaded'); |
There was a problem hiding this comment.
Ignore stale call-detail responses
When the user switches between calls quickly, an earlier getMeetCallDetail request can resolve after a later selection and overwrite detail/status for the currently displayed record, showing the wrong transcript or summary under the new call header. Check that the resolved requestId still matches the current selection before calling setDetail and setStatus.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 4ca9a78 — stale getMeetCallDetail responses are ignored via a latestRequestIdRef guard before setDetail/setStatus.
There was a problem hiding this comment.
Actionable comments posted: 15
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/openhuman/agent_meetings/ops.rs (1)
400-408: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winReject plaintext meeting URLs.
These helpers still accept
http://links. Because they gate both calendar auto-join and upcoming-meeting extraction, a downgraded URL can be surfaced or auto-joined before any redirect, leaking meeting tokens over cleartext transport. Restrict this path to HTTPS only.Suggested fix
- if url.scheme() != "https" && url.scheme() != "http" { + if url.scheme() != "https" { return Err(format!( "invalid meeting URL: scheme `{}` not allowed", url.scheme() @@ - matches!(u.scheme(), "http" | "https") + u.scheme() == "https" && u.host_str().map(host_is_allowed).unwrap_or(false) @@ - (matches!(parsed.scheme(), "http" | "https") + (parsed.scheme() == "https" && parsed.host_str().map(host_is_allowed).unwrap_or(false)) .then(|| parsed.to_string())Also applies to: 433-478
🤖 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 `@src/openhuman/agent_meetings/ops.rs` around lines 400 - 408, The meeting URL validation and related helpers still allow plaintext http links, which must be rejected so only secure meeting URLs are accepted. Update validate_meeting_url and the other affected meeting-link parsing paths in the same area to enforce HTTPS-only by checking the parsed Url scheme and returning an error for anything other than https. Use the existing validate_meeting_url symbol as the main entry point and apply the same restriction consistently to the other helper(s) referenced in the diff so calendar auto-join and meeting extraction cannot pass through cleartext URLs.
🟡 Minor comments (9)
app/src/components/meetings/JoinPolicyToggle.tsx-36-72 (1)
36-72: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAdd keyboard roving navigation here.
role="radiogroup"/role="radio"should behave as a single tab stop with arrow-key movement, but these buttons are all separate tab stops and have no Left/Right/Up/Down handling. Switching to native radios or adding roving tabindex would close the gap.🤖 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 `@app/src/components/meetings/JoinPolicyToggle.tsx` around lines 36 - 72, The JoinPolicyToggle radiogroup currently uses separate button tab stops and lacks arrow-key navigation, so update the component to behave like a single radio group. In JoinPolicyToggle, implement roving tabindex (or switch to native radio inputs) so only the active option is tabbable, and add Left/Right/Up/Down key handling to move selection through SEGMENTS and call onChange. Keep the existing role="radiogroup" and role="radio" semantics aligned with the active value.app/src/components/meetings/MeetComposer.tsx-147-161 (1)
147-161: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winTrim the meeting URL before the RPC call.
The button-enable logic already treats whitespace-only padding as invalid, but the submit path still sends the raw
meetUrlintojoinMeetViaBackendBot(...). A pasted URL like" https://zoom.us/j/123 "will reach backend validation with the spaces intact and fail even though the form allowed submission.Suggested fix
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); setError(null); setSubmitting(true); hasSubmittedRef.current = true; + const normalizedMeetUrl = meetUrl.trim(); const meetingId = crypto.randomUUID(); log('[composer] submit platform=%s active=%s correlationId=%s', platform, !listenOnly, meetingId); try { @@ await joinMeetViaBackendBot({ - meetUrl, + meetUrl: normalizedMeetUrl, displayName: agentName, @@ }); // RPC was accepted — transition the UI to the joining / active banner. - dispatch(setBackendMeetJoining({ meetUrl: meetUrl.trim(), meetingId, listenOnly })); + dispatch(setBackendMeetJoining({ meetUrl: normalizedMeetUrl, meetingId, listenOnly }));🤖 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 `@app/src/components/meetings/MeetComposer.tsx` around lines 147 - 161, The submit path in MeetComposer.tsx is passing the untrimmed meetUrl into joinMeetViaBackendBot, which can break backend validation for pasted URLs with surrounding spaces. Trim meetUrl before the RPC call and use the trimmed value consistently for the join request and the setBackendMeetJoining dispatch, while keeping the existing validation behavior intact.app/src/components/meetings/__tests__/ActiveMeetingBanner.test.tsx-92-105 (1)
92-105: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUnawaited
waitFormakes these assertions non-enforcing.
it(...)here is synchronous and thewaitFor(...)promise is neither awaited nor returned, so the assertions inside it run on a detached microtask and can never fail the test — it passes even if "Close" never appears. The same pattern exists at Lines 174-176 in the mascot-transition test.Since
setBackendMeetLeftupdates the store synchronously, you can assert directly withoutwaitFor:💚 Proposed fix
store.dispatch(setBackendMeetLeft({ reason: 'done' })); - // After ended, "Leave" disappears and "Close" appears - waitFor(() => { - expect(screen.queryByRole('button', { name: /leave/i })).not.toBeInTheDocument(); - expect(screen.getByRole('button', { name: /close/i })).toBeInTheDocument(); - }); + // After ended, "Leave" disappears and "Close" appears + expect(screen.queryByRole('button', { name: /leave/i })).not.toBeInTheDocument(); + expect(screen.getByRole('button', { name: /close/i })).toBeInTheDocument();Apply the same change to the mascot-transition assertion at Lines 174-176.
🤖 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 `@app/src/components/meetings/__tests__/ActiveMeetingBanner.test.tsx` around lines 92 - 105, The ended-state checks in ActiveMeetingBanner.test.tsx are not enforced because waitFor is used without await/return, so the assertions can pass silently even when the UI does not update; remove the unnecessary waitFor and assert directly after dispatching setBackendMeetLeft, and apply the same synchronous assertion fix in the mascot-transition test that uses the same pattern.app/src/lib/i18n/id.ts-5169-5169 (1)
5169-5169: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winKeep the platform name as the product brand.
Google Temuireads like a literal translation; the UI should preserve the brand name (Google Meet) for consistency with the other platform labels.Suggested fix
- 'skills.meetingBots.platforms.gmeet': 'Google Temui', + 'skills.meetingBots.platforms.gmeet': 'Google Meet',🤖 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 `@app/src/lib/i18n/id.ts` at line 5169, Update the translation entry for skills.meetingBots.platforms.gmeet in the i18n/id.ts mapping so it preserves the product brand name instead of using a literal translation. Change the displayed label from the translated form to the brand-consistent “Google Meet”, matching the style used by the other platform labels in this locale file.src/openhuman/agent_meetings/upcoming.rs-325-345 (1)
325-345: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse the same 1-hour fallback in the window check.
When
end.dateTimeis missing, Line 336 falls back tostart_utc, but Lines 343-345 still serialize a synthetic 1-hour end. In-progress meetings without an end timestamp are therefore filtered out even though the returned payload assumes they last an hour.Suggested fix
- let effective_end = end_utc.unwrap_or(start_utc); + let effective_end = + end_utc.unwrap_or_else(|| start_utc + chrono::Duration::hours(1)); if effective_end < start_window || start_utc > end_window { return None; } @@ - let end_ms = end_utc - .map(|dt| dt.timestamp_millis().max(0) as u64) - .unwrap_or_else(|| start_ms + 3_600_000); // Default 1-hour duration + let end_ms = effective_end.timestamp_millis().max(0) as u64;🤖 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 `@src/openhuman/agent_meetings/upcoming.rs` around lines 325 - 345, The window check in the upcoming-meetings filter is using start_utc as the fallback end, which conflicts with the 1-hour synthetic end used later in the same routine. Update the in-progress meeting check in the upcoming.rs parsing logic to use the same 1-hour fallback as the end_ms serialization path (the code around end_utc, effective_end, and the interval check) so events without end.dateTime are not dropped prematurely.src/openhuman/agent_meetings/schemas.rs-305-309 (1)
305-309: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winUpdate the
join_policyschema description to match the effective-policy contract.
handle_list_upcomingnow applies per-event and per-platform overrides before returning meetings, so this schema text should not sayjoin_policyis only the global default.Suggested wording update
- organizer, and the global auto-join policy as the default join_policy.", + organizer, and the effective join_policy after per-event, per-platform, \ + and global defaults are resolved.",🤖 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 `@src/openhuman/agent_meetings/schemas.rs` around lines 305 - 309, The `join_policy` description in `handle_list_upcoming`/`ListUpcoming` still says it is only the global auto-join default, but the returned value now reflects the effective policy after per-event and per-platform overrides. Update the schema text in `schemas.rs` so it clearly states that `join_policy` is the resolved effective policy used for each meeting, not just the global default.app/src/components/meetings/__tests__/MeetDefaultsDrawer.test.tsx-87-95 (1)
87-95: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winRemove the
if (...)guards from these assertions.Both tests become false positives if the queried element disappears: the branch just never runs, so the suite still passes when the platform override select or backdrop stops rendering. Assert those elements exist first, then interact with them.
Also applies to: 103-108
🤖 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 `@app/src/components/meetings/__tests__/MeetDefaultsDrawer.test.tsx` around lines 87 - 95, The tests in MeetDefaultsDrawer.test.tsx are silently skipping critical interactions because of the conditional guards around the combobox assertions. Remove the if checks, explicitly assert the expected selects/backdrop exist first using the relevant screen queries in the affected test cases, and then perform the fireEvent.change/click and waitFor assertions so failures surface when the UI stops rendering those elements.app/src/components/meetings/HistorySection.tsx-24-63 (1)
24-63: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winGroup history by local day, not UTC.
todayKey()/yesterdayKey()use UTC calendar boundaries, but these buckets are rendered as user-facing “Today” / “Yesterday” labels. For users outside UTC, late-night calls can land in the wrong group even though their displayed times are local. Use local date parts, or the same timezone basis as the rendered timestamps, for grouping.🤖 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 `@app/src/components/meetings/HistorySection.tsx` around lines 24 - 63, The history grouping logic in groupRecords uses utcDayKey(), todayKey(), and yesterdayKey(), which buckets calls by UTC instead of the user’s local day. Update the day-key helpers to use local date boundaries (or the same timezone basis as the displayed timestamps) and keep groupRecords comparing against those local keys so calls fall under the correct Today/Yesterday labels.app/src/components/meetings/ActionItemChecklist.tsx-25-30 (1)
25-30: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winCheckbox state leaks between different meetings.
checkedis keyed only by array index and never reset whenitemschanges. If a user checks item 0 in one call and then opens another call, item 0 in the new checklist will render as checked too. Scope this cosmetic state to the current meeting/item identity instead of raw indices.Also applies to: 56-57
🤖 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 `@app/src/components/meetings/ActionItemChecklist.tsx` around lines 25 - 30, The checklist checkbox state in ActionItemChecklist leaks across meetings because checked is keyed only by array index and is never reset when items changes. Update the state in ActionItemChecklist so it is scoped to the current meeting/item identity instead of raw indices, and make handleCheck use a stable per-item key or reset the checked map when the items prop changes. Also update the rendering logic around checked so each checklist instance reflects only its own items.
🧹 Nitpick comments (2)
app/src/services/meetCallService.ts (1)
417-430: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winNarrow these RPC types to the actual platform/join-policy unions.
UpcomingTableimmediately casts these fields back toMeetingPlatform/JoinPolicy, so typos from mocks or future callers will compile and only fail at runtime. Keeping the frontend types aligned with the Rust RPC contract here removes those unsafe casts.Suggested diff
+export type MeetingJoinPolicy = 'auto' | 'ask' | 'skip'; + export interface UpcomingMeeting { calendar_event_id: string; title: string; /** Unix milliseconds */ start_time_ms: number; /** Unix milliseconds */ end_time_ms: number; meet_url: string | null; /** Platform slug: "gmeet" | "zoom" | "teams" | "webex" */ - platform: string | null; + platform: MeetingPlatform | null; participant_count: number | null; organizer: string | null; /** "auto" | "ask" | "skip" — local UI state only this phase */ - join_policy: string; + join_policy: MeetingJoinPolicy; calendar_source: string; } @@ interface CoreGetEventPoliciesResponse { ok: boolean; - policies: Record<string, string>; + policies: Record<string, MeetingJoinPolicy>; } @@ export async function getEventPolicies( calendarEventIds: string[] -): Promise<Record<string, string>> { +): Promise<Record<string, MeetingJoinPolicy>> {Also applies to: 468-470, 494-505
🤖 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 `@app/src/services/meetCallService.ts` around lines 417 - 430, The RPC-facing types in UpcomingMeeting are too loose because platform and join_policy are typed as string, even though UpcomingTable immediately narrows them back to MeetingPlatform and JoinPolicy. Update UpcomingMeeting (and the related RPC shapes used by UpcomingTable/MeetCallService) to use the actual MeetingPlatform and JoinPolicy unions instead of plain strings, so invalid values are rejected at compile time and the frontend stays aligned with the Rust contract.app/src/utils/tauriCommands/config.ts (1)
793-805: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winUse a platform-key union for
platform_auto_join_policies.The backend only accepts
gmeet | zoom | teams | webexkeys, butRecord<string, ...>lets invalid keys compile and pushes the failure to runtime. A partial map keyed by the supported platforms will keep this TS contract honest.🤖 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 `@app/src/utils/tauriCommands/config.ts` around lines 793 - 805, The `MeetSettingsUpdate` contract currently types `platform_auto_join_policies` as `Record<string, MeetAutoJoinPolicy>`, which allows unsupported keys to compile. Update this field to use a platform-key union for the supported platforms (`gmeet`, `zoom`, `teams`, `webex`) so the TypeScript type matches the backend contract, and keep the change aligned with the existing `MeetSettingsUpdate` interface and its related auto-join policy types.
🤖 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 `@app/src/components/meetings/HistoryDetail.tsx`:
- Around line 56-68: The loadDetail flow in HistoryDetail can apply stale
results when getMeetCallDetail resolves out of order after a newer request has
started. Update loadDetail to track the latest requestId (or use cancellation)
and, before calling setDetail, setStatus('loaded'), or setting
loadedForRef.current, verify the response still matches the current request.
Keep the current request guarded using the existing loadDetail function and
loadedForRef reference so older completions are ignored.
- Around line 86-97: The retry effect in HistoryDetail.tsx is re-scheduling
forever whenever a loaded meeting still has no summary. Add a per-request_id
guard in the HistoryDetail component around the useEffect that checks
status/detail/record?.request_id so loadDetail only gets called once for each
request_id, and ensure the timer setup is skipped after that first retry.
- Around line 71-84: The reset logic in HistoryDetail’s useEffect is triggering
the repo’s react-hooks/set-state-in-effect rule because it synchronously calls
setStatus and setDetail inside the effect body. Move the “clear on record
change” behavior out of this effect in HistoryDetail—either key the component by
record?.request_id or derive the displayed detail/status from the current record
so no state reset is needed here, while keeping loadDetail(record.request_id)
responsible only for loading.
In `@app/src/components/meetings/HistorySection.tsx`:
- Around line 145-152: The selection in HistorySection is left stale when
filteredRecords becomes empty because the useEffect exits early and preserves
selectedCallId, which can hide the mobile rail and trap the user on stale
details. Update the selection logic in HistorySection so the active call is
derived from filteredRecords (or explicitly cleared) instead of being patched
inside useEffect; prefer computing the selected item from the current
filteredRecords/selection state or resetting it through the existing selection
flow in the component rather than calling setSelectedCallId directly in the
effect.
In `@app/src/components/meetings/MeetComposer.tsx`:
- Around line 89-92: The MeetComposer effects are synchronously setting state
inside useEffect, which violates the repo’s react-hooks/set-state-in-effect rule
and will fail CI. Refactor the responsive prefill and error/submitting handling
so state is derived outside the effect or updated from the relevant
event/transition source instead of directly in the effect body. Use the
MeetComposer logic around resolvedDisplayName, setRespondTo, setError, and
setSubmitting to locate and remove these synchronous effect-based state updates.
In `@app/src/components/meetings/MeetDefaultsDrawer.tsx`:
- Around line 108-113: The initial-load failure path in MeetDefaultsDrawer
should not fall back to the editable form backed by default state; instead, keep
the live settings controls hidden or disabled until openhumanGetMeetSettings()
succeeds and a real snapshot is loaded. Update the loading/error flow around the
load effect and the form rendering so the normal save path cannot run from stale
defaults, and ensure any state used by the submit handlers remains gated by the
successful load state.
- Around line 122-143: The shared persistSeqRef guard in MeetDefaultsDrawer’s
persist helper is causing unrelated setting saves to cancel or hide each other’s
failures because each handler only sends a partial patch. Update the save flow
in persist and the individual setting handlers so request tracking is scoped per
setting/field (or replace the global sequence check with a refetch/reconcile
step after each save), ensuring a failed write for one control cannot be dropped
by a later success from another control.
In `@app/src/components/meetings/meetingUtils.ts`:
- Around line 119-125: The hostname matching in inferPlatformFromUrl is too
loose because it uses includes(), which can falsely पहचानise lookalike hosts as
meeting platforms. Update the checks in inferPlatformFromUrl to use exact
hostname comparison or dot-boundary suffix matching for meet.google.com,
zoom.us, teams.microsoft.com, and webex.com, so only real platform hosts are
labeled and used by the join-payload fallback.
In `@app/src/components/meetings/UpcomingTable.tsx`:
- Around line 141-143: Use a single effectivePlatform helper in UpcomingTable so
rendering, filtering, and filter-option generation all use the same platform
value. Right now filterMeetings and the filter list rely on meeting.platform
while row rendering falls back to inferPlatformFromUrl(meet_url), causing
null-platform meetings to display differently than they filter. Update the logic
around filterMeetings, the row rendering path, and the platform option
aggregation to reuse that helper everywhere.
In `@app/src/components/meetings/useUpcomingMeetings.ts`:
- Around line 44-78: Guard `fetchMeetings` in `useUpcomingMeetings` against
overlapping/out-of-order responses, since `mountedRef` only prevents updates
after unmount. Add a request/version token (or similar latest-request check)
inside `fetchMeetings` and only call `setMeetings`, `setError`, and
`setLoading(false)` for the most recent invocation triggered by the `useEffect`
poll, `refresh()`, or `lookaheadMinutes`/`limit` changes.
In `@src/openhuman/agent_meetings/calendar.rs`:
- Line 302: The MeetingSession records created in
handle_calendar_meeting_candidate are dropping the resolved calendar_event_id by
still initializing it as None. Update both MeetingSession initializer paths in
this function so they persist calendar_event_id.clone() instead, matching the
new argument already used for policy resolution. Keep the change aligned across
both the auto-join and prompt session branches so the persisted session retains
the calendar-event association.
In `@src/openhuman/agent_meetings/ops.rs`:
- Around line 497-509: The helper extract_calendar_event_id_from_payload
currently checks the wrapper payload before payload["data"], which can cause
mismatched override keys when the envelope has its own top-level id. Update this
function to prefer the nested event object under data first, and only fall back
to the root payload afterward, while still using extract_calendar_event_id for
the actual id/eventId/icalUID selection.
In `@src/openhuman/agent_meetings/store.rs`:
- Around line 30-34: The persisted meeting policy values are currently
unrestricted, so invalid strings can be stored and later returned by
handle_get_event_policies and resolve_effective_join_policy_with_overrides.
Update the meeting_event_policies schema to add a DB-level CHECK on policy, and
tighten set_event_policy so it validates the incoming value against the allowed
join policy set before writing. Use the existing policy-handling paths as the
source of truth and reject any invalid value before insert/update.
In `@src/openhuman/agent_meetings/upcoming.rs`:
- Around line 81-94: The upcoming-meetings flow is swallowing upstream calendar
errors by turning them into empty vectors and treating that as “no meetings.”
Update `fetch_connections`, `fetch_events_for_connection`, and the caller logic
in `upcoming` so Composio/Google failures are propagated as errors instead of
returning `Vec::new()`, and only return an empty list when the provider truly
has no data. Make sure the early return after `calendar_connections.is_empty()`
is reserved for the real “no calendar connected” case, and keep the same
error-handling behavior consistent in the other affected paths referenced by
`fetch_connections` and `fetch_events_for_connection`.
In `@src/openhuman/config/schemas/controllers.rs`:
- Around line 650-662: The update_meet_settings parser in
controllers::update_meet_settings currently validates only policy values and
allows any platform key to be persisted. Add key validation in the raw_map
iteration so only the supported platform keys (gmeet, zoom, teams, webex) are
accepted before inserting into parsed, and return an error plus warn log for any
unsupported key; keep the existing AutoJoinPolicy matching intact and apply the
check in the same loop where policy_str is parsed.
---
Outside diff comments:
In `@src/openhuman/agent_meetings/ops.rs`:
- Around line 400-408: The meeting URL validation and related helpers still
allow plaintext http links, which must be rejected so only secure meeting URLs
are accepted. Update validate_meeting_url and the other affected meeting-link
parsing paths in the same area to enforce HTTPS-only by checking the parsed Url
scheme and returning an error for anything other than https. Use the existing
validate_meeting_url symbol as the main entry point and apply the same
restriction consistently to the other helper(s) referenced in the diff so
calendar auto-join and meeting extraction cannot pass through cleartext URLs.
---
Minor comments:
In `@app/src/components/meetings/__tests__/ActiveMeetingBanner.test.tsx`:
- Around line 92-105: The ended-state checks in ActiveMeetingBanner.test.tsx are
not enforced because waitFor is used without await/return, so the assertions can
pass silently even when the UI does not update; remove the unnecessary waitFor
and assert directly after dispatching setBackendMeetLeft, and apply the same
synchronous assertion fix in the mascot-transition test that uses the same
pattern.
In `@app/src/components/meetings/__tests__/MeetDefaultsDrawer.test.tsx`:
- Around line 87-95: The tests in MeetDefaultsDrawer.test.tsx are silently
skipping critical interactions because of the conditional guards around the
combobox assertions. Remove the if checks, explicitly assert the expected
selects/backdrop exist first using the relevant screen queries in the affected
test cases, and then perform the fireEvent.change/click and waitFor assertions
so failures surface when the UI stops rendering those elements.
In `@app/src/components/meetings/ActionItemChecklist.tsx`:
- Around line 25-30: The checklist checkbox state in ActionItemChecklist leaks
across meetings because checked is keyed only by array index and is never reset
when items changes. Update the state in ActionItemChecklist so it is scoped to
the current meeting/item identity instead of raw indices, and make handleCheck
use a stable per-item key or reset the checked map when the items prop changes.
Also update the rendering logic around checked so each checklist instance
reflects only its own items.
In `@app/src/components/meetings/HistorySection.tsx`:
- Around line 24-63: The history grouping logic in groupRecords uses
utcDayKey(), todayKey(), and yesterdayKey(), which buckets calls by UTC instead
of the user’s local day. Update the day-key helpers to use local date boundaries
(or the same timezone basis as the displayed timestamps) and keep groupRecords
comparing against those local keys so calls fall under the correct
Today/Yesterday labels.
In `@app/src/components/meetings/JoinPolicyToggle.tsx`:
- Around line 36-72: The JoinPolicyToggle radiogroup currently uses separate
button tab stops and lacks arrow-key navigation, so update the component to
behave like a single radio group. In JoinPolicyToggle, implement roving tabindex
(or switch to native radio inputs) so only the active option is tabbable, and
add Left/Right/Up/Down key handling to move selection through SEGMENTS and call
onChange. Keep the existing role="radiogroup" and role="radio" semantics aligned
with the active value.
In `@app/src/components/meetings/MeetComposer.tsx`:
- Around line 147-161: The submit path in MeetComposer.tsx is passing the
untrimmed meetUrl into joinMeetViaBackendBot, which can break backend validation
for pasted URLs with surrounding spaces. Trim meetUrl before the RPC call and
use the trimmed value consistently for the join request and the
setBackendMeetJoining dispatch, while keeping the existing validation behavior
intact.
In `@app/src/lib/i18n/id.ts`:
- Line 5169: Update the translation entry for skills.meetingBots.platforms.gmeet
in the i18n/id.ts mapping so it preserves the product brand name instead of
using a literal translation. Change the displayed label from the translated form
to the brand-consistent “Google Meet”, matching the style used by the other
platform labels in this locale file.
In `@src/openhuman/agent_meetings/schemas.rs`:
- Around line 305-309: The `join_policy` description in
`handle_list_upcoming`/`ListUpcoming` still says it is only the global auto-join
default, but the returned value now reflects the effective policy after
per-event and per-platform overrides. Update the schema text in `schemas.rs` so
it clearly states that `join_policy` is the resolved effective policy used for
each meeting, not just the global default.
In `@src/openhuman/agent_meetings/upcoming.rs`:
- Around line 325-345: The window check in the upcoming-meetings filter is using
start_utc as the fallback end, which conflicts with the 1-hour synthetic end
used later in the same routine. Update the in-progress meeting check in the
upcoming.rs parsing logic to use the same 1-hour fallback as the end_ms
serialization path (the code around end_utc, effective_end, and the interval
check) so events without end.dateTime are not dropped prematurely.
---
Nitpick comments:
In `@app/src/services/meetCallService.ts`:
- Around line 417-430: The RPC-facing types in UpcomingMeeting are too loose
because platform and join_policy are typed as string, even though UpcomingTable
immediately narrows them back to MeetingPlatform and JoinPolicy. Update
UpcomingMeeting (and the related RPC shapes used by
UpcomingTable/MeetCallService) to use the actual MeetingPlatform and JoinPolicy
unions instead of plain strings, so invalid values are rejected at compile time
and the frontend stays aligned with the Rust contract.
In `@app/src/utils/tauriCommands/config.ts`:
- Around line 793-805: The `MeetSettingsUpdate` contract currently types
`platform_auto_join_policies` as `Record<string, MeetAutoJoinPolicy>`, which
allows unsupported keys to compile. Update this field to use a platform-key
union for the supported platforms (`gmeet`, `zoom`, `teams`, `webex`) so the
TypeScript type matches the backend contract, and keep the change aligned with
the existing `MeetSettingsUpdate` interface and its related auto-join policy
types.
🪄 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: CHILL
Plan: Pro
Run ID: 9b0b2909-601a-4b1a-a793-168770dfbdf7
📒 Files selected for processing (65)
app/src/components/composio/toolkitMeta.tsxapp/src/components/meetings/ActionItemChecklist.tsxapp/src/components/meetings/ActiveMeetingBanner.tsxapp/src/components/meetings/HistoryDetail.tsxapp/src/components/meetings/HistoryRail.tsxapp/src/components/meetings/HistorySection.tsxapp/src/components/meetings/JoinPolicyToggle.tsxapp/src/components/meetings/MeetComposer.tsxapp/src/components/meetings/MeetDefaultsDrawer.tsxapp/src/components/meetings/MeetingsPage.tsxapp/src/components/meetings/PlatformChips.tsxapp/src/components/meetings/TranscriptViewer.tsxapp/src/components/meetings/UpcomingTable.tsxapp/src/components/meetings/__tests__/ActionItemChecklist.test.tsxapp/src/components/meetings/__tests__/ActiveMeetingBanner.test.tsxapp/src/components/meetings/__tests__/HistoryDetail.test.tsxapp/src/components/meetings/__tests__/HistoryRail.test.tsxapp/src/components/meetings/__tests__/HistorySection.test.tsxapp/src/components/meetings/__tests__/JoinPolicyToggle.test.tsxapp/src/components/meetings/__tests__/MeetComposer.test.tsxapp/src/components/meetings/__tests__/MeetDefaultsDrawer.test.tsxapp/src/components/meetings/__tests__/PlatformChips.test.tsxapp/src/components/meetings/__tests__/TranscriptViewer.test.tsxapp/src/components/meetings/__tests__/UpcomingTable.test.tsxapp/src/components/meetings/__tests__/meetingUtils.test.tsapp/src/components/meetings/__tests__/useUpcomingMeetings.test.tsapp/src/components/meetings/meetingUtils.tsapp/src/components/meetings/useUpcomingMeetings.tsapp/src/components/skills/MeetingBotsCard.tsxapp/src/components/skills/RecentCallsSection.test.tsxapp/src/components/skills/RecentCallsSection.tsxapp/src/components/skills/__tests__/MeetingBotsCard.test.tsxapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.tsapp/src/pages/Skills.tsxapp/src/pages/__tests__/Skills.meetings-tab.test.tsxapp/src/services/__tests__/meetCallService.test.tsapp/src/services/meetCallService.tsapp/src/utils/tauriCommands/config.tssrc/core/all.rssrc/openhuman/agent_meetings/calendar.rssrc/openhuman/agent_meetings/mod.rssrc/openhuman/agent_meetings/ops.rssrc/openhuman/agent_meetings/schemas.rssrc/openhuman/agent_meetings/store.rssrc/openhuman/agent_meetings/types.rssrc/openhuman/agent_meetings/upcoming.rssrc/openhuman/config/ops/ui.rssrc/openhuman/config/schema/meet.rssrc/openhuman/config/schemas/controllers.rssrc/openhuman/config/schemas/helpers.rssrc/openhuman/subconscious/heartbeat/planner/mod.rstests/json_rpc_e2e.rs
💤 Files with no reviewable changes (2)
- app/src/components/skills/RecentCallsSection.test.tsx
- app/src/components/skills/RecentCallsSection.tsx
…calendar The calendar auto-join / ask-to-join engine was gated behind heartbeat.notify_meetings (a reminder-notifications toggle, off by default), so setting a meeting's join policy to Auto/Ask silently did nothing. Introduce meet.watch_calendar as a dedicated master switch: - Planner polls the calendar + runs auto-join when notify_meetings OR watch_calendar is on; in watch-calendar-only mode it suppresses the plain reminder card (auto-join/ask still fire), so joining is decoupled from reminder notifications. - watch_calendar (serde-default false) threaded through the meet-settings get/update RPC. - Defaults drawer gains a top master switch; the Upcoming table shows an inline hint when watching is off so the Auto/Ask toggles aren't silently inert.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/openhuman/subconscious/heartbeat/planner/mod.rs (2)
168-170: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winDon’t pass fallback event IDs into per-event policy resolution.
source_event_idcan be the collector fallback"calendar-event"when the payload has no stableid/eventId/icalUID. Passing it asSome(...)lets unrelated no-id events share a per-event override key, which can incorrectly change auto-join behavior. PassNonefor fallback IDs or preserve the original optional calendar ID separately.🤖 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 `@src/openhuman/subconscious/heartbeat/planner/mod.rs` around lines 168 - 170, Do not pass the collector fallback value from `event.source_event_id` into per-event policy resolution, because that makes unrelated no-id events share the same override key. In the planner code that feeds `meet_set_event_policy` and the per-event policy tier, only forward a stable calendar identifier as `Some(...)`; if `source_event_id` is the fallback `"calendar-event"`, pass `None` instead or keep the original optional calendar ID separate so fallback events do not pick up shared overrides.
45-64: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy liftMove the new planner behavior out of
mod.rs.This adds watch-calendar gating, policy forwarding, and suppressed-card dispatch logic directly in
mod.rs. Please move the implementation into a sibling planner module and keepmod.rsto exports/wiring. As per coding guidelines,src/openhuman/**/mod.rs: “mod.rs files should be export-focused only: mod/pub mod, pub use, and controller schema wiring; no business logic.”Also applies to: 168-170, 195-209
🤖 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 `@src/openhuman/subconscious/heartbeat/planner/mod.rs` around lines 45 - 64, The new heartbeat planner behavior is too much business logic in mod.rs; move the watch-calendar gating, policy forwarding, and suppressed-card dispatch into a sibling planner module, and keep mod.rs limited to exports/wiring. Locate the logic in the heartbeat planner entrypoint and related helpers such as the planner function that collects reminders/meetings, then extract that implementation into the appropriate sibling module while preserving the existing public surface via mod/pub mod and pub use. Ensure mod.rs only re-exports the planner pieces and controller/schema wiring, with no event-planning logic remaining.Source: Coding guidelines
🤖 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 `@app/src/components/meetings/__tests__/MeetDefaultsDrawer.test.tsx`:
- Around line 150-160: The rollback test in MeetDefaultsDrawer is too weak
because it can pass while never observing the optimistic update. Update the test
around the watch-my-calendar switch to use a deferred rejected promise for
updateMock, then assert the switch becomes checked after the click before
triggering the rejection and verifying it rolls back to unchecked; use the
existing test helpers and the switch queried by its accessible name to keep it
robust.
---
Outside diff comments:
In `@src/openhuman/subconscious/heartbeat/planner/mod.rs`:
- Around line 168-170: Do not pass the collector fallback value from
`event.source_event_id` into per-event policy resolution, because that makes
unrelated no-id events share the same override key. In the planner code that
feeds `meet_set_event_policy` and the per-event policy tier, only forward a
stable calendar identifier as `Some(...)`; if `source_event_id` is the fallback
`"calendar-event"`, pass `None` instead or keep the original optional calendar
ID separate so fallback events do not pick up shared overrides.
- Around line 45-64: The new heartbeat planner behavior is too much business
logic in mod.rs; move the watch-calendar gating, policy forwarding, and
suppressed-card dispatch into a sibling planner module, and keep mod.rs limited
to exports/wiring. Locate the logic in the heartbeat planner entrypoint and
related helpers such as the planner function that collects reminders/meetings,
then extract that implementation into the appropriate sibling module while
preserving the existing public surface via mod/pub mod and pub use. Ensure
mod.rs only re-exports the planner pieces and controller/schema wiring, with no
event-planning logic remaining.
🪄 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: CHILL
Plan: Pro
Run ID: aaf6473a-0577-4c3a-a64f-f0851a3e5cd4
📒 Files selected for processing (26)
app/src/components/meetings/MeetDefaultsDrawer.tsxapp/src/components/meetings/MeetingsPage.tsxapp/src/components/meetings/UpcomingTable.tsxapp/src/components/meetings/__tests__/MeetDefaultsDrawer.test.tsxapp/src/components/meetings/__tests__/UpcomingTable.test.tsxapp/src/components/settings/panels/__tests__/MeetingSettingsPanel.test.tsxapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.tsapp/src/utils/tauriCommands/config.tssrc/openhuman/config/ops/ui.rssrc/openhuman/config/schema/meet.rssrc/openhuman/config/schemas/controllers.rssrc/openhuman/config/schemas/helpers.rssrc/openhuman/subconscious/heartbeat/planner/mod.rs
✅ Files skipped from review due to trivial changes (4)
- app/src/components/settings/panels/tests/MeetingSettingsPanel.test.tsx
- app/src/lib/i18n/pl.ts
- app/src/lib/i18n/fr.ts
- app/src/lib/i18n/bn.ts
🚧 Files skipped from review as they are similar to previous changes (20)
- app/src/lib/i18n/ru.ts
- app/src/lib/i18n/id.ts
- app/src/components/meetings/MeetingsPage.tsx
- app/src/lib/i18n/de.ts
- src/openhuman/config/schema/meet.rs
- app/src/lib/i18n/hi.ts
- app/src/utils/tauriCommands/config.ts
- app/src/lib/i18n/en.ts
- src/openhuman/config/ops/ui.rs
- app/src/lib/i18n/ko.ts
- src/openhuman/config/schemas/controllers.rs
- app/src/lib/i18n/zh-CN.ts
- src/openhuman/config/schemas/helpers.rs
- app/src/components/meetings/UpcomingTable.tsx
- app/src/lib/i18n/pt.ts
- app/src/components/meetings/MeetDefaultsDrawer.tsx
- app/src/lib/i18n/es.ts
- app/src/lib/i18n/it.ts
- app/src/components/meetings/tests/UpcomingTable.test.tsx
- app/src/lib/i18n/ar.ts
- HistoryDetail: ignore out-of-order getMeetCallDetail responses (guard by latest request id); retry missing summaries at most once per call; remove reset-in-effect (remount via key) to satisfy set-state-in-effect. - HistorySection: clear selection when a filter empties the list. - MeetComposer: derive the prefilled display name at render instead of in an effect (satisfies react-hooks/set-state-in-effect), behavior preserved. - meetingUtils: inferPlatformFromUrl parses the hostname and matches by exact/dot-suffix instead of substring (no spoofed-host match). - useUpcomingMeetings: ignore stale out-of-order fetch responses. - UpcomingTable: filter by the effective (inferred) platform shown per row.
- upcoming.rs: fetch the full API page (maxResults=100) BEFORE the conferencing-link filter, so a Meet/Zoom event behind reminders/OOO in the first N isn't silently dropped; still truncate(limit) post-filter. - upcoming.rs: propagate genuine Composio fetch failures (Result) instead of swallowing them as an empty list; no-connection still returns empty. - calendar.rs: persist the resolved calendar_event_id on the auto-join / ask MeetingSession so per-event policy + dedup can key off it. - ops.rs: extract_calendar_event_id_from_payload prefers the nested data.* event id over the wrapper's id (events.list vs webhook shapes). - store.rs: set_event_policy rejects values outside auto|ask|skip. - controllers.rs: update_meet_settings rejects unknown platform slugs in platform_auto_join_policies (allowlist gmeet|zoom|teams|webex).
# Conflicts: # app/src/components/skills/MeetingBotsCard.tsx
main added resolveMeetingBotMascotId (legacy-mascot allowlist so manifest- only mascot ids fall back to the legacy color) to the old MeetingBotsCard, which our shim dropped. Port it into meetingUtils + MeetComposer so the backend bot still gets a recognized mascot id. Drop the obsolete RecentCallsSection test cases that auto-merged from main (that surface is now HistorySection, covered by its own specs) and add unit tests for the ported resolver.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@app/src/components/meetings/__tests__/HistoryDetail.test.tsx`:
- Around line 252-305: The stale-transcript test in HistoryDetail.test.tsx is
not validating actual transcript content because the current TranscriptViewer
mock only exposes a test id/count. Update the mock used by HistoryDetail so it
either renders each transcript line’s content or otherwise records the received
props, then change the assertion in the rapid record switch test to verify the
late transcript content remains and that the stale early content is not
rendered. Use the TranscriptViewer mock and the HistoryDetail/record switch test
case to locate the fix.
In `@src/openhuman/agent_meetings/upcoming.rs`:
- Around line 265-275: The fallback error text in the GOOGLECALENDAR_EVENTS_LIST
failure path is contradictory and should match the `r.successful == false`
branch. Update the `detail` handling in `upcoming.rs` so the default string used
by the `tracing::warn!` call and the returned `Err(...)` in the `Ok(r)` arm of
the upcoming meetings flow reflects an unsuccessful response consistently, using
the same symbols (`r.error`, `detail`, `conn.id`, and the `"[meet:upcoming]
GOOGLECALENDAR_EVENTS_LIST..."` message) to locate and fix the fallback text.
🪄 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: CHILL
Plan: Pro
Run ID: ee0a9631-0114-47df-b58b-bd153709a311
📒 Files selected for processing (31)
app/src/components/meetings/HistoryDetail.tsxapp/src/components/meetings/HistorySection.tsxapp/src/components/meetings/MeetComposer.tsxapp/src/components/meetings/UpcomingTable.tsxapp/src/components/meetings/__tests__/HistoryDetail.test.tsxapp/src/components/meetings/__tests__/HistorySection.test.tsxapp/src/components/meetings/__tests__/UpcomingTable.test.tsxapp/src/components/meetings/__tests__/meetingUtils.test.tsapp/src/components/meetings/__tests__/useUpcomingMeetings.test.tsapp/src/components/meetings/meetingUtils.tsapp/src/components/meetings/useUpcomingMeetings.tsapp/src/components/skills/__tests__/MeetingBotsCard.test.tsxapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.tssrc/openhuman/agent_meetings/calendar.rssrc/openhuman/agent_meetings/ops.rssrc/openhuman/agent_meetings/store.rssrc/openhuman/agent_meetings/upcoming.rssrc/openhuman/config/schemas/controllers.rs
✅ Files skipped from review due to trivial changes (7)
- app/src/lib/i18n/id.ts
- app/src/lib/i18n/pl.ts
- app/src/lib/i18n/zh-CN.ts
- app/src/lib/i18n/de.ts
- app/src/lib/i18n/ar.ts
- app/src/lib/i18n/pt.ts
- app/src/lib/i18n/es.ts
🚧 Files skipped from review as they are similar to previous changes (15)
- app/src/lib/i18n/ko.ts
- app/src/lib/i18n/ru.ts
- app/src/lib/i18n/fr.ts
- src/openhuman/agent_meetings/store.rs
- app/src/lib/i18n/it.ts
- app/src/components/meetings/tests/UpcomingTable.test.tsx
- app/src/lib/i18n/bn.ts
- app/src/components/meetings/MeetComposer.tsx
- app/src/lib/i18n/en.ts
- app/src/lib/i18n/hi.ts
- app/src/components/meetings/UpcomingTable.tsx
- app/src/components/meetings/HistorySection.tsx
- app/src/components/meetings/HistoryDetail.tsx
- src/openhuman/agent_meetings/calendar.rs
- src/openhuman/agent_meetings/ops.rs
| it('shows the correct transcript and ignores stale content after rapid record switch', async () => { | ||
| let resolveEarly!: (v: MeetCallDetail) => void; | ||
|
|
||
| const earlyDetail: MeetCallDetail = { | ||
| request_id: 'req-early', | ||
| summary: null, | ||
| transcript: [{ role: 'participant', content: 'EARLY STALE CONTENT' }], | ||
| }; | ||
| const lateDetail: MeetCallDetail = { | ||
| request_id: 'req-late', | ||
| summary: null, | ||
| transcript: [{ role: 'participant', content: 'LATE FRESH CONTENT' }], | ||
| }; | ||
|
|
||
| getMeetCallDetailMock.mockImplementation((reqId: string) => { | ||
| if (reqId === 'req-early') { | ||
| return new Promise(r => { resolveEarly = r; }); | ||
| } | ||
| return Promise.resolve(lateDetail); | ||
| }); | ||
|
|
||
| const earlyRecord: MeetCallRecord = { | ||
| ...record, | ||
| request_id: 'req-early', | ||
| meet_url: 'https://meet.google.com/early-abc', | ||
| }; | ||
| const lateRecord: MeetCallRecord = { | ||
| ...record, | ||
| request_id: 'req-late', | ||
| meet_url: 'https://meet.google.com/late-xyz', | ||
| }; | ||
|
|
||
| const { rerender } = renderWithProviders(<HistoryDetail record={earlyRecord} />); | ||
|
|
||
| // Wait for the early fetch to start. | ||
| await waitFor(() => expect(getMeetCallDetailMock).toHaveBeenCalledWith('req-early')); | ||
|
|
||
| // Switch while early is still in-flight. | ||
| rerender(<HistoryDetail record={lateRecord} />); | ||
|
|
||
| // Late (current) record's transcript should appear. | ||
| await waitFor(() => { | ||
| expect(screen.getByTestId('transcript')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| // Deliver the stale early response. | ||
| await act(async () => { | ||
| resolveEarly(earlyDetail); | ||
| await Promise.resolve(); | ||
| }); | ||
|
|
||
| // Stale early content must not replace the live late content. | ||
| expect(screen.queryByText('EARLY STALE CONTENT')).toBeNull(); | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
The stale-transcript assertion is currently vacuous.
This file’s TranscriptViewer mock only renders a test id/count, so Line 304 stays null even if stale transcript lines are passed through. Please assert the mock props or have the mock render line.content so this test actually catches stale transcript overwrites.
🤖 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 `@app/src/components/meetings/__tests__/HistoryDetail.test.tsx` around lines
252 - 305, The stale-transcript test in HistoryDetail.test.tsx is not validating
actual transcript content because the current TranscriptViewer mock only exposes
a test id/count. Update the mock used by HistoryDetail so it either renders each
transcript line’s content or otherwise records the received props, then change
the assertion in the rapid record switch test to verify the late transcript
content remains and that the stale early content is not rendered. Use the
TranscriptViewer mock and the HistoryDetail/record switch test case to locate
the fix.
| Ok(r) => { | ||
| let detail = r.error.as_deref().unwrap_or("unsuccessful=true"); | ||
| tracing::warn!( | ||
| connection_id = %conn.id, | ||
| error = %detail, | ||
| "[meet:upcoming] GOOGLECALENDAR_EVENTS_LIST returned unsuccessful" | ||
| ); | ||
| Err(format!( | ||
| "[meet:upcoming] GOOGLECALENDAR_EVENTS_LIST unsuccessful for connection {}: {detail}", | ||
| conn.id | ||
| )) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the contradictory fallback error text.
Line 266 is in the r.successful == false branch, but the fallback message says unsuccessful=true. That string can bubble into the returned Err(...), so logs or UI will describe the failure state incorrectly.
Proposed fix
- let detail = r.error.as_deref().unwrap_or("unsuccessful=true");
+ let detail = r.error.as_deref().unwrap_or("successful=false");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Ok(r) => { | |
| let detail = r.error.as_deref().unwrap_or("unsuccessful=true"); | |
| tracing::warn!( | |
| connection_id = %conn.id, | |
| error = %detail, | |
| "[meet:upcoming] GOOGLECALENDAR_EVENTS_LIST returned unsuccessful" | |
| ); | |
| Err(format!( | |
| "[meet:upcoming] GOOGLECALENDAR_EVENTS_LIST unsuccessful for connection {}: {detail}", | |
| conn.id | |
| )) | |
| Ok(r) => { | |
| let detail = r.error.as_deref().unwrap_or("successful=false"); | |
| tracing::warn!( | |
| connection_id = %conn.id, | |
| error = %detail, | |
| "[meet:upcoming] GOOGLECALENDAR_EVENTS_LIST returned unsuccessful" | |
| ); | |
| Err(format!( | |
| "[meet:upcoming] GOOGLECALENDAR_EVENTS_LIST unsuccessful for connection {}: {detail}", | |
| conn.id | |
| )) |
🤖 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 `@src/openhuman/agent_meetings/upcoming.rs` around lines 265 - 275, The
fallback error text in the GOOGLECALENDAR_EVENTS_LIST failure path is
contradictory and should match the `r.successful == false` branch. Update the
`detail` handling in `upcoming.rs` so the default string used by the
`tracing::warn!` call and the returned `Err(...)` in the `Ok(r)` arm of the
upcoming meetings flow reflects an unsuccessful response consistently, using the
same symbols (`r.error`, `detail`, `conn.id`, and the `"[meet:upcoming]
GOOGLECALENDAR_EVENTS_LIST..."` message) to locate and fix the fallback text.
- Don't render the editable controls after a failed initial settings load: track loaded/loadError and show an error + retry instead, so a save can't push a partial patch built from hard-coded defaults (which could wipe platform_auto_join_policies). - Replace the single global persistSeqRef with a per-setting-key sequence map so a failed save for one control is no longer masked by a successful save of another; superseded responses for the same setting are still ignored.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@app/src/components/meetings/__tests__/MeetDefaultsDrawer.test.tsx`:
- Around line 229-247: The optimistic rollback test for the auto-join setting is
too weak because it only checks the final reverted value in
MeetDefaultsDrawer.test.tsx. In the test around the updateMock / rejectAutoJoin
flow, first assert that the combobox actually changes to the optimistic value
('always') after fireEvent.change and before rejecting, then keep the existing
revert assertion to confirm it returns to 'ask_each_time'. Use the existing
selects[0], rejectAutoJoin, and screen.getAllByRole('combobox') references to
place the added assertion in the same test.
🪄 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: CHILL
Plan: Pro
Run ID: 0ea7b678-8e16-4eb9-b333-d8abd14c5d05
📒 Files selected for processing (2)
app/src/components/meetings/MeetDefaultsDrawer.tsxapp/src/components/meetings/__tests__/MeetDefaultsDrawer.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- app/src/components/meetings/MeetDefaultsDrawer.tsx
| // Optimistically change setting A (auto_join_policy: 'ask_each_time' → 'always') | ||
| const selects = screen.getAllByRole('combobox'); | ||
| fireEvent.change(selects[0], { target: { value: 'always' } }); | ||
|
|
||
| // Optimistically change setting B (listen_only: true → false) | ||
| const listenSwitch = screen.getByRole('switch', { name: /listen.only/i }); | ||
| fireEvent.click(listenSwitch); | ||
|
|
||
| // Wait for both saves to have been dispatched | ||
| await waitFor(() => expect(updateMock).toHaveBeenCalledTimes(2)); | ||
|
|
||
| // Save B (listen_only) has already resolved successfully; now reject save A | ||
| rejectAutoJoin(new Error('network error')); | ||
|
|
||
| // Setting A should revert to its original value | ||
| await waitFor(() => { | ||
| const autoJoinSelect = screen.getAllByRole('combobox')[0]; | ||
| expect(autoJoinSelect).toHaveValue('ask_each_time'); | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Assert the optimistic value before rejecting so the revert assertion is meaningful.
Setting A's original value after load is 'ask_each_time', and the final assertion at Line 246 also expects 'ask_each_time'. Because the start state and the post-revert state are identical, this test can pass even if the optimistic change → revert path never actually executed. Assert that selects[0] becomes 'always' after the change (and before rejectAutoJoin), so the revert genuinely demonstrates a state transition. Setting B (Lines 234-235, 250-251) already avoids this since true → false differs from its start state.
This is the same weakness pattern previously flagged on the watch-calendar rollback test (lines 150-160).
💚 Suggested hardening
const selects = screen.getAllByRole('combobox');
fireEvent.change(selects[0], { target: { value: 'always' } });
+ // Confirm the optimistic update actually applied before we reject.
+ expect(screen.getAllByRole('combobox')[0]).toHaveValue('always');📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Optimistically change setting A (auto_join_policy: 'ask_each_time' → 'always') | |
| const selects = screen.getAllByRole('combobox'); | |
| fireEvent.change(selects[0], { target: { value: 'always' } }); | |
| // Optimistically change setting B (listen_only: true → false) | |
| const listenSwitch = screen.getByRole('switch', { name: /listen.only/i }); | |
| fireEvent.click(listenSwitch); | |
| // Wait for both saves to have been dispatched | |
| await waitFor(() => expect(updateMock).toHaveBeenCalledTimes(2)); | |
| // Save B (listen_only) has already resolved successfully; now reject save A | |
| rejectAutoJoin(new Error('network error')); | |
| // Setting A should revert to its original value | |
| await waitFor(() => { | |
| const autoJoinSelect = screen.getAllByRole('combobox')[0]; | |
| expect(autoJoinSelect).toHaveValue('ask_each_time'); | |
| }); | |
| // Optimistically change setting A (auto_join_policy: 'ask_each_time' → 'always') | |
| const selects = screen.getAllByRole('combobox'); | |
| fireEvent.change(selects[0], { target: { value: 'always' } }); | |
| // Confirm the optimistic update actually applied before we reject. | |
| expect(screen.getAllByRole('combobox')[0]).toHaveValue('always'); | |
| // Optimistically change setting B (listen_only: true → false) | |
| const listenSwitch = screen.getByRole('switch', { name: /listen.only/i }); | |
| fireEvent.click(listenSwitch); | |
| // Wait for both saves to have been dispatched | |
| await waitFor(() => expect(updateMock).toHaveBeenCalledTimes(2)); | |
| // Save B (listen_only) has already resolved successfully; now reject save A | |
| rejectAutoJoin(new Error('network error')); | |
| // Setting A should revert to its original value | |
| await waitFor(() => { | |
| const autoJoinSelect = screen.getAllByRole('combobox')[0]; | |
| expect(autoJoinSelect).toHaveValue('ask_each_time'); | |
| }); |
🤖 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 `@app/src/components/meetings/__tests__/MeetDefaultsDrawer.test.tsx` around
lines 229 - 247, The optimistic rollback test for the auto-join setting is too
weak because it only checks the final reverted value in
MeetDefaultsDrawer.test.tsx. In the test around the updateMock / rejectAutoJoin
flow, first assert that the combobox actually changes to the optimistic value
('always') after fireEvent.change and before rejecting, then keep the existing
revert assertion to confirm it returns to 'ask_each_time'. Use the existing
selects[0], rejectAutoJoin, and screen.getAllByRole('combobox') references to
place the added assertion in the same test.
…et-settings RPC schema The update_meet_settings controller schema (schema_defs.rs) only listed auto_join_policy/listen_only_default/etc., so core's validate_params rejected watch_calendar (and platform_auto_join_policies) as an 'unknown param' BEFORE the handler ran — even though the Rust struct + handler accepted them. Declare both in the update inputs and get outputs so the Defaults drawer can actually persist them.
The review-fix agents ran cargo check / eslint but not cargo fmt / prettier, leaving formatting that fails the Rust Quality + Frontend Checks CI lanes. Reformat the affected agent_meetings + config Rust and the app/src/components/meetings + i18n files. No logic changes.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@src/openhuman/config/schemas/schema_defs.rs`:
- Around line 500-510: The meet settings response schema is treating optional
fields as plain types even though `handle_get_meet_settings` serializes unset
`Option` values as null. Update the relevant `FieldSchema` entries in the schema
definition so the `platform_auto_join_policies` and `watch_calendar` fields use
`TypeSchema::Option(...)` around their current types, keeping the existing field
names and comments intact.
🪄 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: CHILL
Plan: Pro
Run ID: da0a0474-8fab-4956-94bb-a007a342f10d
📒 Files selected for processing (48)
app/src/components/meetings/ActionItemChecklist.tsxapp/src/components/meetings/HistoryDetail.tsxapp/src/components/meetings/HistoryRail.tsxapp/src/components/meetings/HistorySection.tsxapp/src/components/meetings/JoinPolicyToggle.tsxapp/src/components/meetings/MeetComposer.tsxapp/src/components/meetings/MeetDefaultsDrawer.tsxapp/src/components/meetings/MeetingsPage.tsxapp/src/components/meetings/PlatformChips.tsxapp/src/components/meetings/TranscriptViewer.tsxapp/src/components/meetings/UpcomingTable.tsxapp/src/components/meetings/__tests__/ActionItemChecklist.test.tsxapp/src/components/meetings/__tests__/ActiveMeetingBanner.test.tsxapp/src/components/meetings/__tests__/HistoryDetail.test.tsxapp/src/components/meetings/__tests__/HistoryRail.test.tsxapp/src/components/meetings/__tests__/HistorySection.test.tsxapp/src/components/meetings/__tests__/JoinPolicyToggle.test.tsxapp/src/components/meetings/__tests__/MeetComposer.test.tsxapp/src/components/meetings/__tests__/MeetDefaultsDrawer.test.tsxapp/src/components/meetings/__tests__/TranscriptViewer.test.tsxapp/src/components/meetings/__tests__/UpcomingTable.test.tsxapp/src/components/meetings/__tests__/useUpcomingMeetings.test.tsapp/src/components/meetings/useUpcomingMeetings.tsapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/pages/Skills.tsxapp/src/services/__tests__/meetCallService.test.tssrc/openhuman/agent_meetings/calendar.rssrc/openhuman/agent_meetings/ops.rssrc/openhuman/agent_meetings/schemas.rssrc/openhuman/agent_meetings/store.rssrc/openhuman/agent_meetings/upcoming.rssrc/openhuman/config/schema/meet.rssrc/openhuman/config/schemas/controllers.rssrc/openhuman/config/schemas/schema_defs.rssrc/openhuman/subconscious/heartbeat/planner/mod.rstests/json_rpc_e2e.rs
✅ Files skipped from review due to trivial changes (8)
- app/src/lib/i18n/ru.ts
- app/src/lib/i18n/es.ts
- app/src/lib/i18n/bn.ts
- app/src/lib/i18n/pl.ts
- app/src/lib/i18n/ko.ts
- app/src/lib/i18n/ar.ts
- app/src/lib/i18n/it.ts
- app/src/lib/i18n/pt.ts
🚧 Files skipped from review as they are similar to previous changes (39)
- app/src/components/meetings/tests/HistoryRail.test.tsx
- app/src/components/meetings/tests/JoinPolicyToggle.test.tsx
- app/src/pages/Skills.tsx
- app/src/components/meetings/TranscriptViewer.tsx
- app/src/lib/i18n/de.ts
- app/src/components/meetings/tests/TranscriptViewer.test.tsx
- app/src/components/meetings/JoinPolicyToggle.tsx
- app/src/components/meetings/HistorySection.tsx
- app/src/components/meetings/ActionItemChecklist.tsx
- app/src/lib/i18n/hi.ts
- src/openhuman/config/schema/meet.rs
- app/src/components/meetings/MeetingsPage.tsx
- tests/json_rpc_e2e.rs
- app/src/lib/i18n/id.ts
- app/src/components/meetings/HistoryRail.tsx
- app/src/lib/i18n/en.ts
- app/src/components/meetings/useUpcomingMeetings.ts
- app/src/components/meetings/tests/ActionItemChecklist.test.tsx
- app/src/components/meetings/PlatformChips.tsx
- app/src/lib/i18n/fr.ts
- src/openhuman/subconscious/heartbeat/planner/mod.rs
- app/src/components/meetings/tests/ActiveMeetingBanner.test.tsx
- app/src/components/meetings/tests/HistorySection.test.tsx
- src/openhuman/agent_meetings/schemas.rs
- src/openhuman/agent_meetings/store.rs
- app/src/components/meetings/tests/HistoryDetail.test.tsx
- app/src/services/tests/meetCallService.test.ts
- app/src/components/meetings/HistoryDetail.tsx
- app/src/components/meetings/MeetComposer.tsx
- app/src/components/meetings/MeetDefaultsDrawer.tsx
- app/src/components/meetings/tests/MeetDefaultsDrawer.test.tsx
- src/openhuman/config/schemas/controllers.rs
- src/openhuman/agent_meetings/ops.rs
- app/src/components/meetings/tests/useUpcomingMeetings.test.ts
- src/openhuman/agent_meetings/upcoming.rs
- app/src/components/meetings/tests/MeetComposer.test.tsx
- app/src/components/meetings/UpcomingTable.tsx
- src/openhuman/agent_meetings/calendar.rs
- app/src/components/meetings/tests/UpcomingTable.test.tsx
| FieldSchema { | ||
| name: "platform_auto_join_policies", | ||
| ty: TypeSchema::Json, | ||
| comment: "Per-platform auto-join overrides keyed by platform slug.", | ||
| required: false, | ||
| }, | ||
| FieldSchema { | ||
| name: "watch_calendar", | ||
| ty: TypeSchema::Bool, | ||
| comment: "Whether the heartbeat watches the calendar to drive auto-join / ask.", | ||
| required: false, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Schema type / validator definitions =="
rg -n -C3 'enum TypeSchema|struct FieldSchema|required:\s*false|TypeSchema::Option|fn .*validate|validate_.*schema|ControllerSchema' src/openhuman -g '!**/target/**'
echo
echo "== Other optional output-field patterns in schema_defs.rs =="
rg -n -C2 'outputs: vec!\[|required:\s*false|TypeSchema::Option' src/openhuman/config/schemas/schema_defs.rs
echo
echo "== Meet-settings handler / payload construction =="
rg -n -C4 '"get_meet_settings"|platform_auto_join_policies|watch_calendar' src/openhuman -g '!**/target/**'Repository: tinyhumansai/openhuman
Length of output: 50379
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Relevant file outlines =="
ast-grep outline src/openhuman/config/schemas/schema_defs.rs --view expanded | sed -n '1,220p'
echo
ast-grep outline src/openhuman/core/*.rs --view expanded 2>/dev/null | sed -n '1,220p' || true
echo
echo "== Search for TypeSchema definition and validation =="
rg -n -C3 'enum TypeSchema|struct FieldSchema|impl .*TypeSchema|validate.*TypeSchema|required:\s*false|Option\(Box::new\(TypeSchema::Json\)\)|Option\(Box::new\(TypeSchema::Bool\)\)' src -g '!**/target/**'Repository: tinyhumansai/openhuman
Length of output: 50379
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Relevant file outline: schema_defs.rs =="
ast-grep outline src/openhuman/config/schemas/schema_defs.rs --view expanded | sed -n '1,240p'
echo
echo "== TypeSchema / validator definitions =="
rg -n -C3 'enum TypeSchema|struct FieldSchema|impl .*TypeSchema|validate.*TypeSchema|required:\s*false|Option\(Box::new\(TypeSchema::Json\)\)|Option\(Box::new\(TypeSchema::Bool\)\)' src -g '!**/target/**'Repository: tinyhumansai/openhuman
Length of output: 50379
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find core schema types =="
fd -a '.*(schema|controller|type).*\.rs' src/openhuman src | sed -n '1,120p'
echo
echo "== Search for TypeSchema and FieldSchema definitions only =="
rg -n -C2 'enum TypeSchema|struct FieldSchema|type TypeSchema|struct ControllerSchema|required:\s*false' src/openhuman/core src/openhuman -g '!**/target/**' | head -n 200
echo
echo "== Search for output validation / serialization path =="
rg -n -C2 'outputs: vec|output_field\(|validate.*output|validate.*schema|required.*output|serde_json::Value::Null|null' src/openhuman -g '!**/target/**' | head -n 250Repository: tinyhumansai/openhuman
Length of output: 22571
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Focused search: schema types and validation =="
rg -n -C2 'enum TypeSchema|struct FieldSchema|struct ControllerSchema|required:\s*false|TypeSchema::Option|serde_json::Value::Null|null' src/openhuman \
-g '!**/target/**' \
-g '*.rs' | head -n 250Repository: tinyhumansai/openhuman
Length of output: 17686
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Locate schema_defs.rs fields around get_meet_settings =="
sed -n '480,515p' src/openhuman/config/schemas/schema_defs.rs | cat -n
echo
echo "== Locate the corresponding handler / response construction =="
rg -n -C4 'get_meet_settings|platform_auto_join_policies|watch_calendar' src/openhuman -g '!**/target/**' -g '*.rs' | head -n 120
echo
echo "== Search for validation semantics around required false / null =="
rg -n -C3 'required:\s*false|skip_serializing_if|Option<|Null|may be null|omit' src/openhuman -g '!**/target/**' -g '*.rs' | head -n 200Repository: tinyhumansai/openhuman
Length of output: 25265
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Meet settings helper structs =="
sed -n '100,170p' src/openhuman/config/schemas/helpers.rs | cat -n
echo
echo "== get_meet_settings handler =="
rg -n -C6 'handle_get_meet_settings|get_meet_settings' src/openhuman/config/schemas/controllers.rs
echo
echo "== Serialization hints in config schema files =="
rg -n -C2 'skip_serializing_if|serde::Serialize|Option<|serde_json::to_value|json!\(' src/openhuman/config/schemas src/openhuman/config -g '*.rs' | head -n 200Repository: tinyhumansai/openhuman
Length of output: 20011
Align the meet settings response schema with nullable fields
handle_get_meet_settings serializes these values directly from Option fields, so unset settings become null. required: false is not enough here; wrap both outputs in TypeSchema::Option(...) so the validator matches the actual payload.
src/openhuman/config/schemas/schema_defs.rs:500-510
🤖 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 `@src/openhuman/config/schemas/schema_defs.rs` around lines 500 - 510, The meet
settings response schema is treating optional fields as plain types even though
`handle_get_meet_settings` serializes unset `Option` values as null. Update the
relevant `FieldSchema` entries in the schema definition so the
`platform_auto_join_policies` and `watch_calendar` fields use
`TypeSchema::Option(...)` around their current types, keeping the existing field
names and comments intact.
run_event_planner_tick_for_config short-circuited the planner unless a notify_* category was on — so watch_calendar (which only gates the inner planner) never actually let calendar collection / auto-join run. Add watch_calendar to this outer gate too, matching evaluate_and_dispatch, so enabling 'Watch my calendar' actually drives auto-join.
The redesign replaced the single-platform inline form with a platform selector, so the spec's 'Zoom/Teams absent' assertions are now wrong. Assert the four platform radio chips are present instead.
CI note: the
|
Issues
Closes #4275 — Redesign Meetings UI (multi-platform, auto-fill, beta banner)
Closes #4302 — Support non-Google-Meet links (Zoom / Teams / Webex platform selector)
Closes #4301 — Forward detected meeting platform in the
bot:joinpayloadAdvances #3505 (Meeting Assistant epic) — proactive auto-join (calendar
watch_calendar→ heartbeat), notes & summaries surfaced.Related #4305 — surfaces
auto_summarize_policy("Post-call summary") in the new Meeting Defaults drawer.Related #4304 — active-meeting view shows live bot replies/captions; full live-transcript console is a deferred follow-up (not closed by this PR).
Summary
Full redesign of the Meetings feature ("Send OpenHuman to a meeting"). The meetings tab becomes a full-width, multi-platform workspace and finally surfaces the calendar + transcript/summary data the backend already captures. Shipped in four reviewable phases (one commit each).
Phase 1A — Composer + platform selector
app/src/components/meetings/tree:MeetingsPage,MeetComposer,PlatformChips,ActiveMeetingBanner,meetingUtils.MeetingBotsCardis now a back-compat re-export shim.Phase 1B — History master-detail + rich transcript
TranscriptViewerparses the[MM:SS] [Name]line prefix into timestamped, speaker-labeled, role-colored lines, with Copy + Download.ActionItemChecklistsurfaces executable action items with a "Run with OpenHuman" action. OldRecentCallsSectionremoved.Phase 2 — Upcoming table +
meet_list_upcomingRPCopenhuman.meet_list_upcomingJSON-RPC fetches upcoming events via the existing ComposioGOOGLECALENDAR_EVENTS_LISTpath (reused, not a heartbeat refactor), extracts the join link fromhangoutLink/conferenceData/ description, infers the platform, and returns typed records.Phase 3 — Join-policy persistence + Defaults drawer
meeting_event_policiesSQLite table +meet_set_event_policy/meet_get_event_policiesRPCs;platform_auto_join_policiesmap added toMeetConfig(serde-defaulted, backward compatible);resolve_effective_join_policyfeeds bothmeet_list_upcomingand the calendar auto-join candidate handler (policy source only — the scheduler is untouched; the heartbeat planner threads thecalendar_event_idthrough).MeetDefaultsDrawer(gear button) for global + per-platform defaults; the Upcoming toggle persists viasetEventPolicywith optimistic update + revert.Notes / follow-ups
/chatwithout prefilling the action text yet (no trivial prefill mechanism) — TODO left inline.Test plan
pnpm typecheckcleanGGML_NATIVE=OFF cargo checkcleanjson_rpc_e2epolicy round-trip passpnpm i18n:checkparity clean across all 14 locales (new keys fully translated)Summary by CodeRabbit