@ai-matrx/meet 0.7.78 → 0.7.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -2938,6 +2938,21 @@ interface MeetingRecordBundle {
2938
2938
  * therefore make the same distinction as decisions and action items.
2939
2939
  */
2940
2940
  declare function activeWrapUpSummary(notes: readonly MeetingNote[]): MeetingNote | null;
2941
+ /**
2942
+ * The four durable reads behind a record, as the database returned them, plus
2943
+ * what the reader could not see. `assembleMeetingRecord` turns them into the
2944
+ * bundle; a cache that keeps them can apply a realtime row to one part and
2945
+ * assemble again, without reading the other three (0.7.79).
2946
+ */
2947
+ interface MeetingRecordParts {
2948
+ readonly attendees: readonly MeetingAttendee[];
2949
+ /** Every note row (all kinds, superseded included), oldest first. */
2950
+ readonly notes: readonly MeetingNote[];
2951
+ readonly transcript: readonly TranscriptSegment[];
2952
+ /** Newest first; archived recordings are not here. */
2953
+ readonly recordings: readonly RecordingRecord[];
2954
+ readonly restrictions: readonly MeetingRecordRestriction[];
2955
+ }
2941
2956
  /**
2942
2957
  * Load the record parts the reader may request; state all restrictions.
2943
2958
  *
@@ -2946,6 +2961,10 @@ declare function activeWrapUpSummary(notes: readonly MeetingNote[]): MeetingNote
2946
2961
  * fail for exactly the reader who most needs the page to work.
2947
2962
  */
2948
2963
  declare function loadMeetingRecord(repository: MeetRepository, meetingId: MeetingId, meeting: MeetingRecord, reader?: Pick<MeetIdentity, "userId">): Promise<MeetingRecordBundle>;
2964
+ /** The four reads, each with its own verdict (see `loadMeetingRecord`). */
2965
+ declare function loadMeetingRecordParts(repository: MeetRepository, meetingId: MeetingId, reader?: Pick<MeetIdentity, "userId">): Promise<MeetingRecordParts>;
2966
+ /** The bundle a reader sees, from the parts. Grouping and ordering only (rule 3). */
2967
+ declare function assembleMeetingRecord(meeting: MeetingRecord, parts: MeetingRecordParts): MeetingRecordBundle;
2949
2968
  /**
2950
2969
  * 🚨 **A READ THAT WAS REFUSED IS NOT AN EMPTY RESULT (MRI-A16).**
2951
2970
  *
@@ -3594,4 +3613,104 @@ interface InRoomClientOptions {
3594
3613
  }
3595
3614
  declare function createInRoomClient(options: InRoomClientOptions): InRoomClient;
3596
3615
 
3597
- export { ATTENDANCE_CSV_HEADER, type AccessTokenSource, type AdmissionState, type AnnounceResult, type ApplyRoomEventArgs, type AskArgs, type AttendanceKind, type AttendanceRow, type AudioProcessing, CALL_INVITE_KIND, CALL_INVITE_VERSION, CAPTION_LANGUAGES, CONSENT_NOTICE_ATTRIBUTE, type CallCenter, type CallCenterOptions, type CallId, type CallInvite, type CallInviteHandlerOptions, type CallInvitePayload, type CallMode, type CallState, type CallablePerson, type CollabAction, type CollabPanel, type CollabRequest, type CollabResult, type CollabSnapshot, type ConnectionQuality, type CreateCallInviteInput, type CreateMeetingInput, DATA_TOPICS, DEFAULT_EFFECTS, DEFAULT_PAGE_SIZE, type DataTopic, type DeviceKind, type DeviceManager, type DeviceManagerOptions, type DeviceSnapshot, type DeviceStorage, type DriverConnectionState, type DriverParticipant, type DriverTrackKind, type DriverTranscription, EMPTY_COLLAB, END_MEETING_NOT_SERVED, type EffectsStorage, type EndMeetingResult, FILE_SESSION_PATH, type FollowUpDraft, type FollowUpRecipient, type FollowUpSendResult, GUEST_RECORD_ACCESS_CHOICES, GUEST_RECORD_ACCESS_LEVELS, GUEST_RECORD_ACCESS_METADATA_KEY, GUEST_RECORD_NOT_SHARED, type GuestRecordAccess, type GuestRecordAccessResolution, type GuestRecordAccessSetting, IDLE_MEDIA, IDLE_TRACK, type InRoomClient, type InRoomClientOptions, type InviteeInput, type InviteeRole, type JoinRequest, type JsonObject, type JsonValue, LISTEN_ONLY_NOTICE, type LibraryRecording, type LiveKitDriverOptions, type LobbyEntry, type LocalMediaState, type LocalTrackState, MEETING_INVITE_KIND, MEETING_INVITE_VERSION, MEET_ANSWER_CONTEXT_EVENT, MEET_ANSWER_EVENTS, MEET_EVENTS, MEET_ROUTES, MEET_SCHEMA, MODERATION_ACTIONS, type MediaDevicesLike, type MediaEffects, type MediaUnavailable, type MeetAi, type MeetAiAnswer, type MeetAiOptions, type MeetAnswerCoverage, type MeetApi, type MeetApiOptions, type MeetApiRequest, type MeetBreakoutBroadcast, type MeetBreakoutPlan, type MeetBreakoutRoom, type MeetCollabSettings, type MeetDevice, MeetError, type MeetErrorCode, type MeetEvent, type MeetFileMedia, type MeetGrants, type MeetIdentity, type MeetParticipant, type MeetPoll, type MeetPollOption, type MeetPollState, type MeetQuestion, type MeetQuestionState, type MeetRepository, type MeetRoomDriver, type MeetSnapshot, type MeetStore, type MeetStroke, type MeetingActivities, type MeetingAttendee, type MeetingChanges, type MeetingChatHistory, type MeetingId, type MeetingInviteHandlerOptions, type MeetingInvitePayload, type MeetingInvitee, type MeetingKind, type MeetingNote, type MeetingNoteKind, type MeetingOccurrence, type MeetingRecord, type MeetingRecordBundle, type MeetingRecordRestriction, type MeetingRecordRestrictionNotice, type MeetingSearchHit, type MeetingSearchSource, type MeetingSession, type MeetingSessionOptions, type MeetingSettings, type ModerationAction, type ModerationNotice, type ModerationRequest, type ModerationResult, NOTE_TAKER_IDENTITY_PREFIX, NOTE_TAKER_METADATA_KEY, NO_GRANTS, type NameBook, type NoteTakerDispatch, type NoteTakerRefusal, type NoteTakerState, type NoteTakerStatus, ORGANIZATION_HEADER, type OccurrenceOverride, type OccurrenceState, type OrganizationId, PARTICIPANT_CAN_RECORD_ATTRIBUTE, PARTICIPANT_ROLE_ATTRIBUTE, type ParticipantIdentity, type ParticipantRole, REACTION_TTL_MS, REFUSED_ABSENCE, ROLE_RANK, ROOM_EVENTS, ROOM_EVENT_PREFIX, ROOM_TOKEN_HEADER, RPCS, type RecordingController, type RecordingControllerOptions, type RecordingPolicy, type RecordingRecord, type RecordingState, type RepositoryOptions, type RoomChatMessage, type RoomDriverFactory, type RoomDriverHandlers, type RoomEngine, type RoomEngineOptions, type RoomEventOutcome, type RoomEventTopic, type RoomName, type RoomPhase, type RoomReaction, type RoomToken, type RoomTokenStorage, type RsvpAnswer, type RsvpInvitation, type RsvpState, type ScheduleMeetingInput, type Stage, type StageArgs, type StageLayout, TABLES, TRANSCRIPTION_ATTRIBUTES, TRANSCRIPTION_TOPIC, type TokenClient, type TokenClientOptions, type TokenRequest, type TranscriptBlock, type TranscriptFormat, type TranscriptMatch, type TranscriptSegment, type TranscriptSource, type UpcomingOccurrence, type UserId, type VideoEffect, type WrapUpAnnouncement, type WrapUpStatus, absenceSentence, activeWrapUpSummary, announceMeeting, applyCollabPacket, applyRoomEvent, asCallId, asMeetingId, asOrganizationId, asParticipantIdentity, asRoomName, asUserId, assignedRoom, attendanceCsv, attendanceReport, attendanceTotals, attendeeKey, autoAssign, breakoutRemainingMs, callInviteAction, callInviteText, callStateLabel, canModerate, canModerateParticipant, canTransition, captionSegment, changesPatch, clockPosition, collabFromState, computeStage, countLabel, createCallCenter, createCallInviteHandler, createDeviceManager, createInRoomClient, createLiveKitDriver, createMeetAi, createMeetApi, createMeetRepository, createMeetStore, createMeetingInviteHandler, createMeetingSession, createMemoryDeviceStorage, createRecordingController, createRoomEngine, createTokenClient, createWebDeviceStorage, createWebEffectsStorage, createWebRoomTokenStorage, defaultCaptionLanguage, displayNameFor, fileDownloadPath, gridColumns, groupTranscript, guestRecordAccessExplanation, guestRecordAccessLabel, guestRecordAccessSetting, hasEnded, hostUserIds, invalidResponse, isGuest, isGuestIdentity, isHostRole, isListenOnly, isNoteTaker, isRealName, isRoomEventTopic, learnName, learnNames, listenOnlyNotice, loadFollowUpDraft, loadGuestMeetingRecord, loadMeetingActivities, loadMeetingChat, loadMeetingRecord, mapConnectionState, mapQuality, meetingInviteAction, meetingLength, meetingLink, meetingPath, meetingSlug, meetingTopic, meetingWhen, mergeCaption, mergeRecord, normalizeDeviceError, normalizeMeetError, normalizeSpoken, noteTakerIdentity, noteTakerLabel, noteTakerParticipant, noteTakerStatus, notesDocument, orderParticipants, parseRoomToken, participantSummary, playableRecordingFileId, projectInvitee, projectLibraryRecording, projectNote, projectOccurrence, projectOverride, projectParticipant, projectRsvpInvitation, projectSearchHit, projectSegment, projectUpcoming, pushMeetingToGoogleCalendar, readServerRefusal, readableTranscript, recordingLabel, recordingNotice, recordingOffsetMs, roleOf, roomTopic, searchTranscript, sendFollowUp, setGuestRecordAccess, settingsPatch, snapshotParticipant, snippetRuns, summarizeMeetingRecordRestrictions, transcriptDocument, unavailableFrom, uniqueAttendees, userTopic, wasRefused, withItem, wrapUpStatus };
3616
+ /**
3617
+ * ONE MEETING RECORD PER READER PER TAB — read once, kept current by one
3618
+ * ref-counted channel (0.7.79).
3619
+ *
3620
+ * 🚨 THE DEFECT THIS CLOSES. `useMeetingRecord` held the bundle in its own
3621
+ * component state and read all four parts (`meet_participants`, `meet_notes`,
3622
+ * `meet_transcript_segments`, `meet_recordings`) in a mount effect. A host that
3623
+ * hides a view without unmounting its data — a board tile asleep under React
3624
+ * `<Activity>`, which tears every effect down and runs it again on wake — read
3625
+ * the whole record again on every wake, on a Remove + Undo, and once more for
3626
+ * every second view of the same meeting (the record tab, the meeting's agent
3627
+ * surface, five "meeting notes" tiles). The view owned the data.
3628
+ *
3629
+ * THE SHAPE. One entry per (reader, meeting). The first view reads; every later
3630
+ * view — the same view after a wake or a remount included — renders the entry
3631
+ * and reads nothing. A read already in flight is joined. A forced read (Retry,
3632
+ * a reconnect) supersedes it, and an answer that lands after a newer read was
3633
+ * asked is DROPPED (the late-answer guard), so an old answer can never paint
3634
+ * over a newer one.
3635
+ *
3636
+ * LIVE WHILE HELD. A member's entry (a direct read, kept as its four parts) is
3637
+ * patched row by row from the database's change feed for the four tables, then
3638
+ * assembled again — one row, never four reads. The channel opens on the first
3639
+ * holder and closes `MEETING_RECORD_GRACE_MS` after the last one lets go, so a
3640
+ * wake or an Undo inside the grace costs nothing. Past the grace nothing
3641
+ * watched the meeting, so the entry is marked stale and the next view reads it
3642
+ * once. A guest's record arrives through the server as a finished bundle; it
3643
+ * has no change feed and is kept as it is.
3644
+ *
3645
+ * Framework-free on purpose: `react/hooks.ts` is one reader of it.
3646
+ */
3647
+
3648
+ /** How long a meeting's channel and kept record outlive the last view of it. */
3649
+ declare const MEETING_RECORD_GRACE_MS = 60000;
3650
+ interface MeetingRecordFailure {
3651
+ readonly message: string;
3652
+ readonly remedy: string;
3653
+ }
3654
+ interface MeetingRecordState {
3655
+ readonly loading: boolean;
3656
+ readonly record: MeetingRecordBundle | null;
3657
+ /** A failure that stopped the WHOLE record loading (never kept as an answer). */
3658
+ readonly failure: MeetingRecordFailure | null;
3659
+ }
3660
+ /** What a read produced: the four parts (patchable), or a finished bundle (a guest's). */
3661
+ type MeetingRecordAnswer = {
3662
+ readonly kind: "parts";
3663
+ readonly meeting: MeetingRecord;
3664
+ readonly parts: MeetingRecordParts;
3665
+ } | {
3666
+ readonly kind: "bundle";
3667
+ readonly bundle: MeetingRecordBundle;
3668
+ };
3669
+ /** One changed row, already projected by the repository. `null` + `removedId` = gone. */
3670
+ type MeetingRecordChange = {
3671
+ readonly part: "transcript";
3672
+ readonly row: MeetingRecordParts["transcript"][number] | null;
3673
+ readonly removedId?: string | undefined;
3674
+ } | {
3675
+ readonly part: "notes";
3676
+ readonly row: MeetingRecordParts["notes"][number] | null;
3677
+ readonly removedId?: string | undefined;
3678
+ } | {
3679
+ readonly part: "attendance";
3680
+ readonly row: MeetingRecordParts["attendees"][number] | null;
3681
+ readonly removedId?: string | undefined;
3682
+ } | {
3683
+ readonly part: "recording";
3684
+ readonly row: MeetingRecordParts["recordings"][number] | null;
3685
+ readonly removedId?: string | undefined;
3686
+ };
3687
+ interface MeetingRecordCache {
3688
+ /** The kept state for `key` (stable until it changes). */
3689
+ state(key: string | null): MeetingRecordState;
3690
+ subscribe(listener: () => void): () => void;
3691
+ /**
3692
+ * Read unless the entry is kept and current (or a read is in flight — joined).
3693
+ * `force` reads anyway and supersedes a read in flight. Never rejects: a
3694
+ * failure is in the state.
3695
+ */
3696
+ ensure(key: string, read: () => Promise<MeetingRecordAnswer>, options?: {
3697
+ force?: boolean;
3698
+ }): Promise<void>;
3699
+ /** The meeting row moved (a rename, it ended): assemble again — no read. */
3700
+ setMeeting(key: string, meeting: MeetingRecord): void;
3701
+ /** Apply one changed row from the change feed — no read. */
3702
+ apply(key: string, change: MeetingRecordChange): void;
3703
+ /**
3704
+ * Hold the live half: `open` on the first holder, its close a grace after the
3705
+ * last. `owner` is what the channel runs on (the realtime manager): a new
3706
+ * owner means the old one's channels are gone, so it opens again.
3707
+ */
3708
+ hold(key: string, open: () => () => void, owner?: unknown): () => void;
3709
+ /** Tests only. */
3710
+ reset(): void;
3711
+ }
3712
+ declare function createMeetingRecordCache(options?: {
3713
+ graceMs?: number;
3714
+ }): MeetingRecordCache;
3715
+
3716
+ export { ATTENDANCE_CSV_HEADER, type AccessTokenSource, type AdmissionState, type AnnounceResult, type ApplyRoomEventArgs, type AskArgs, type AttendanceKind, type AttendanceRow, type AudioProcessing, CALL_INVITE_KIND, CALL_INVITE_VERSION, CAPTION_LANGUAGES, CONSENT_NOTICE_ATTRIBUTE, type CallCenter, type CallCenterOptions, type CallId, type CallInvite, type CallInviteHandlerOptions, type CallInvitePayload, type CallMode, type CallState, type CallablePerson, type CollabAction, type CollabPanel, type CollabRequest, type CollabResult, type CollabSnapshot, type ConnectionQuality, type CreateCallInviteInput, type CreateMeetingInput, DATA_TOPICS, DEFAULT_EFFECTS, DEFAULT_PAGE_SIZE, type DataTopic, type DeviceKind, type DeviceManager, type DeviceManagerOptions, type DeviceSnapshot, type DeviceStorage, type DriverConnectionState, type DriverParticipant, type DriverTrackKind, type DriverTranscription, EMPTY_COLLAB, END_MEETING_NOT_SERVED, type EffectsStorage, type EndMeetingResult, FILE_SESSION_PATH, type FollowUpDraft, type FollowUpRecipient, type FollowUpSendResult, GUEST_RECORD_ACCESS_CHOICES, GUEST_RECORD_ACCESS_LEVELS, GUEST_RECORD_ACCESS_METADATA_KEY, GUEST_RECORD_NOT_SHARED, type GuestRecordAccess, type GuestRecordAccessResolution, type GuestRecordAccessSetting, IDLE_MEDIA, IDLE_TRACK, type InRoomClient, type InRoomClientOptions, type InviteeInput, type InviteeRole, type JoinRequest, type JsonObject, type JsonValue, LISTEN_ONLY_NOTICE, type LibraryRecording, type LiveKitDriverOptions, type LobbyEntry, type LocalMediaState, type LocalTrackState, MEETING_INVITE_KIND, MEETING_INVITE_VERSION, MEETING_RECORD_GRACE_MS, MEET_ANSWER_CONTEXT_EVENT, MEET_ANSWER_EVENTS, MEET_EVENTS, MEET_ROUTES, MEET_SCHEMA, MODERATION_ACTIONS, type MediaDevicesLike, type MediaEffects, type MediaUnavailable, type MeetAi, type MeetAiAnswer, type MeetAiOptions, type MeetAnswerCoverage, type MeetApi, type MeetApiOptions, type MeetApiRequest, type MeetBreakoutBroadcast, type MeetBreakoutPlan, type MeetBreakoutRoom, type MeetCollabSettings, type MeetDevice, MeetError, type MeetErrorCode, type MeetEvent, type MeetFileMedia, type MeetGrants, type MeetIdentity, type MeetParticipant, type MeetPoll, type MeetPollOption, type MeetPollState, type MeetQuestion, type MeetQuestionState, type MeetRepository, type MeetRoomDriver, type MeetSnapshot, type MeetStore, type MeetStroke, type MeetingActivities, type MeetingAttendee, type MeetingChanges, type MeetingChatHistory, type MeetingId, type MeetingInviteHandlerOptions, type MeetingInvitePayload, type MeetingInvitee, type MeetingKind, type MeetingNote, type MeetingNoteKind, type MeetingOccurrence, type MeetingRecord, type MeetingRecordAnswer, type MeetingRecordBundle, type MeetingRecordCache, type MeetingRecordChange, type MeetingRecordFailure, type MeetingRecordParts, type MeetingRecordRestriction, type MeetingRecordRestrictionNotice, type MeetingRecordState, type MeetingSearchHit, type MeetingSearchSource, type MeetingSession, type MeetingSessionOptions, type MeetingSettings, type ModerationAction, type ModerationNotice, type ModerationRequest, type ModerationResult, NOTE_TAKER_IDENTITY_PREFIX, NOTE_TAKER_METADATA_KEY, NO_GRANTS, type NameBook, type NoteTakerDispatch, type NoteTakerRefusal, type NoteTakerState, type NoteTakerStatus, ORGANIZATION_HEADER, type OccurrenceOverride, type OccurrenceState, type OrganizationId, PARTICIPANT_CAN_RECORD_ATTRIBUTE, PARTICIPANT_ROLE_ATTRIBUTE, type ParticipantIdentity, type ParticipantRole, REACTION_TTL_MS, REFUSED_ABSENCE, ROLE_RANK, ROOM_EVENTS, ROOM_EVENT_PREFIX, ROOM_TOKEN_HEADER, RPCS, type RecordingController, type RecordingControllerOptions, type RecordingPolicy, type RecordingRecord, type RecordingState, type RepositoryOptions, type RoomChatMessage, type RoomDriverFactory, type RoomDriverHandlers, type RoomEngine, type RoomEngineOptions, type RoomEventOutcome, type RoomEventTopic, type RoomName, type RoomPhase, type RoomReaction, type RoomToken, type RoomTokenStorage, type RsvpAnswer, type RsvpInvitation, type RsvpState, type ScheduleMeetingInput, type Stage, type StageArgs, type StageLayout, TABLES, TRANSCRIPTION_ATTRIBUTES, TRANSCRIPTION_TOPIC, type TokenClient, type TokenClientOptions, type TokenRequest, type TranscriptBlock, type TranscriptFormat, type TranscriptMatch, type TranscriptSegment, type TranscriptSource, type UpcomingOccurrence, type UserId, type VideoEffect, type WrapUpAnnouncement, type WrapUpStatus, absenceSentence, activeWrapUpSummary, announceMeeting, applyCollabPacket, applyRoomEvent, asCallId, asMeetingId, asOrganizationId, asParticipantIdentity, asRoomName, asUserId, assembleMeetingRecord, assignedRoom, attendanceCsv, attendanceReport, attendanceTotals, attendeeKey, autoAssign, breakoutRemainingMs, callInviteAction, callInviteText, callStateLabel, canModerate, canModerateParticipant, canTransition, captionSegment, changesPatch, clockPosition, collabFromState, computeStage, countLabel, createCallCenter, createCallInviteHandler, createDeviceManager, createInRoomClient, createLiveKitDriver, createMeetAi, createMeetApi, createMeetRepository, createMeetStore, createMeetingInviteHandler, createMeetingRecordCache, createMeetingSession, createMemoryDeviceStorage, createRecordingController, createRoomEngine, createTokenClient, createWebDeviceStorage, createWebEffectsStorage, createWebRoomTokenStorage, defaultCaptionLanguage, displayNameFor, fileDownloadPath, gridColumns, groupTranscript, guestRecordAccessExplanation, guestRecordAccessLabel, guestRecordAccessSetting, hasEnded, hostUserIds, invalidResponse, isGuest, isGuestIdentity, isHostRole, isListenOnly, isNoteTaker, isRealName, isRoomEventTopic, learnName, learnNames, listenOnlyNotice, loadFollowUpDraft, loadGuestMeetingRecord, loadMeetingActivities, loadMeetingChat, loadMeetingRecord, loadMeetingRecordParts, mapConnectionState, mapQuality, meetingInviteAction, meetingLength, meetingLink, meetingPath, meetingSlug, meetingTopic, meetingWhen, mergeCaption, mergeRecord, normalizeDeviceError, normalizeMeetError, normalizeSpoken, noteTakerIdentity, noteTakerLabel, noteTakerParticipant, noteTakerStatus, notesDocument, orderParticipants, parseRoomToken, participantSummary, playableRecordingFileId, projectInvitee, projectLibraryRecording, projectNote, projectOccurrence, projectOverride, projectParticipant, projectRsvpInvitation, projectSearchHit, projectSegment, projectUpcoming, pushMeetingToGoogleCalendar, readServerRefusal, readableTranscript, recordingLabel, recordingNotice, recordingOffsetMs, roleOf, roomTopic, searchTranscript, sendFollowUp, setGuestRecordAccess, settingsPatch, snapshotParticipant, snippetRuns, summarizeMeetingRecordRestrictions, transcriptDocument, unavailableFrom, uniqueAttendees, userTopic, wasRefused, withItem, wrapUpStatus };
package/dist/index.d.ts CHANGED
@@ -2938,6 +2938,21 @@ interface MeetingRecordBundle {
2938
2938
  * therefore make the same distinction as decisions and action items.
2939
2939
  */
2940
2940
  declare function activeWrapUpSummary(notes: readonly MeetingNote[]): MeetingNote | null;
2941
+ /**
2942
+ * The four durable reads behind a record, as the database returned them, plus
2943
+ * what the reader could not see. `assembleMeetingRecord` turns them into the
2944
+ * bundle; a cache that keeps them can apply a realtime row to one part and
2945
+ * assemble again, without reading the other three (0.7.79).
2946
+ */
2947
+ interface MeetingRecordParts {
2948
+ readonly attendees: readonly MeetingAttendee[];
2949
+ /** Every note row (all kinds, superseded included), oldest first. */
2950
+ readonly notes: readonly MeetingNote[];
2951
+ readonly transcript: readonly TranscriptSegment[];
2952
+ /** Newest first; archived recordings are not here. */
2953
+ readonly recordings: readonly RecordingRecord[];
2954
+ readonly restrictions: readonly MeetingRecordRestriction[];
2955
+ }
2941
2956
  /**
2942
2957
  * Load the record parts the reader may request; state all restrictions.
2943
2958
  *
@@ -2946,6 +2961,10 @@ declare function activeWrapUpSummary(notes: readonly MeetingNote[]): MeetingNote
2946
2961
  * fail for exactly the reader who most needs the page to work.
2947
2962
  */
2948
2963
  declare function loadMeetingRecord(repository: MeetRepository, meetingId: MeetingId, meeting: MeetingRecord, reader?: Pick<MeetIdentity, "userId">): Promise<MeetingRecordBundle>;
2964
+ /** The four reads, each with its own verdict (see `loadMeetingRecord`). */
2965
+ declare function loadMeetingRecordParts(repository: MeetRepository, meetingId: MeetingId, reader?: Pick<MeetIdentity, "userId">): Promise<MeetingRecordParts>;
2966
+ /** The bundle a reader sees, from the parts. Grouping and ordering only (rule 3). */
2967
+ declare function assembleMeetingRecord(meeting: MeetingRecord, parts: MeetingRecordParts): MeetingRecordBundle;
2949
2968
  /**
2950
2969
  * 🚨 **A READ THAT WAS REFUSED IS NOT AN EMPTY RESULT (MRI-A16).**
2951
2970
  *
@@ -3594,4 +3613,104 @@ interface InRoomClientOptions {
3594
3613
  }
3595
3614
  declare function createInRoomClient(options: InRoomClientOptions): InRoomClient;
3596
3615
 
3597
- export { ATTENDANCE_CSV_HEADER, type AccessTokenSource, type AdmissionState, type AnnounceResult, type ApplyRoomEventArgs, type AskArgs, type AttendanceKind, type AttendanceRow, type AudioProcessing, CALL_INVITE_KIND, CALL_INVITE_VERSION, CAPTION_LANGUAGES, CONSENT_NOTICE_ATTRIBUTE, type CallCenter, type CallCenterOptions, type CallId, type CallInvite, type CallInviteHandlerOptions, type CallInvitePayload, type CallMode, type CallState, type CallablePerson, type CollabAction, type CollabPanel, type CollabRequest, type CollabResult, type CollabSnapshot, type ConnectionQuality, type CreateCallInviteInput, type CreateMeetingInput, DATA_TOPICS, DEFAULT_EFFECTS, DEFAULT_PAGE_SIZE, type DataTopic, type DeviceKind, type DeviceManager, type DeviceManagerOptions, type DeviceSnapshot, type DeviceStorage, type DriverConnectionState, type DriverParticipant, type DriverTrackKind, type DriverTranscription, EMPTY_COLLAB, END_MEETING_NOT_SERVED, type EffectsStorage, type EndMeetingResult, FILE_SESSION_PATH, type FollowUpDraft, type FollowUpRecipient, type FollowUpSendResult, GUEST_RECORD_ACCESS_CHOICES, GUEST_RECORD_ACCESS_LEVELS, GUEST_RECORD_ACCESS_METADATA_KEY, GUEST_RECORD_NOT_SHARED, type GuestRecordAccess, type GuestRecordAccessResolution, type GuestRecordAccessSetting, IDLE_MEDIA, IDLE_TRACK, type InRoomClient, type InRoomClientOptions, type InviteeInput, type InviteeRole, type JoinRequest, type JsonObject, type JsonValue, LISTEN_ONLY_NOTICE, type LibraryRecording, type LiveKitDriverOptions, type LobbyEntry, type LocalMediaState, type LocalTrackState, MEETING_INVITE_KIND, MEETING_INVITE_VERSION, MEET_ANSWER_CONTEXT_EVENT, MEET_ANSWER_EVENTS, MEET_EVENTS, MEET_ROUTES, MEET_SCHEMA, MODERATION_ACTIONS, type MediaDevicesLike, type MediaEffects, type MediaUnavailable, type MeetAi, type MeetAiAnswer, type MeetAiOptions, type MeetAnswerCoverage, type MeetApi, type MeetApiOptions, type MeetApiRequest, type MeetBreakoutBroadcast, type MeetBreakoutPlan, type MeetBreakoutRoom, type MeetCollabSettings, type MeetDevice, MeetError, type MeetErrorCode, type MeetEvent, type MeetFileMedia, type MeetGrants, type MeetIdentity, type MeetParticipant, type MeetPoll, type MeetPollOption, type MeetPollState, type MeetQuestion, type MeetQuestionState, type MeetRepository, type MeetRoomDriver, type MeetSnapshot, type MeetStore, type MeetStroke, type MeetingActivities, type MeetingAttendee, type MeetingChanges, type MeetingChatHistory, type MeetingId, type MeetingInviteHandlerOptions, type MeetingInvitePayload, type MeetingInvitee, type MeetingKind, type MeetingNote, type MeetingNoteKind, type MeetingOccurrence, type MeetingRecord, type MeetingRecordBundle, type MeetingRecordRestriction, type MeetingRecordRestrictionNotice, type MeetingSearchHit, type MeetingSearchSource, type MeetingSession, type MeetingSessionOptions, type MeetingSettings, type ModerationAction, type ModerationNotice, type ModerationRequest, type ModerationResult, NOTE_TAKER_IDENTITY_PREFIX, NOTE_TAKER_METADATA_KEY, NO_GRANTS, type NameBook, type NoteTakerDispatch, type NoteTakerRefusal, type NoteTakerState, type NoteTakerStatus, ORGANIZATION_HEADER, type OccurrenceOverride, type OccurrenceState, type OrganizationId, PARTICIPANT_CAN_RECORD_ATTRIBUTE, PARTICIPANT_ROLE_ATTRIBUTE, type ParticipantIdentity, type ParticipantRole, REACTION_TTL_MS, REFUSED_ABSENCE, ROLE_RANK, ROOM_EVENTS, ROOM_EVENT_PREFIX, ROOM_TOKEN_HEADER, RPCS, type RecordingController, type RecordingControllerOptions, type RecordingPolicy, type RecordingRecord, type RecordingState, type RepositoryOptions, type RoomChatMessage, type RoomDriverFactory, type RoomDriverHandlers, type RoomEngine, type RoomEngineOptions, type RoomEventOutcome, type RoomEventTopic, type RoomName, type RoomPhase, type RoomReaction, type RoomToken, type RoomTokenStorage, type RsvpAnswer, type RsvpInvitation, type RsvpState, type ScheduleMeetingInput, type Stage, type StageArgs, type StageLayout, TABLES, TRANSCRIPTION_ATTRIBUTES, TRANSCRIPTION_TOPIC, type TokenClient, type TokenClientOptions, type TokenRequest, type TranscriptBlock, type TranscriptFormat, type TranscriptMatch, type TranscriptSegment, type TranscriptSource, type UpcomingOccurrence, type UserId, type VideoEffect, type WrapUpAnnouncement, type WrapUpStatus, absenceSentence, activeWrapUpSummary, announceMeeting, applyCollabPacket, applyRoomEvent, asCallId, asMeetingId, asOrganizationId, asParticipantIdentity, asRoomName, asUserId, assignedRoom, attendanceCsv, attendanceReport, attendanceTotals, attendeeKey, autoAssign, breakoutRemainingMs, callInviteAction, callInviteText, callStateLabel, canModerate, canModerateParticipant, canTransition, captionSegment, changesPatch, clockPosition, collabFromState, computeStage, countLabel, createCallCenter, createCallInviteHandler, createDeviceManager, createInRoomClient, createLiveKitDriver, createMeetAi, createMeetApi, createMeetRepository, createMeetStore, createMeetingInviteHandler, createMeetingSession, createMemoryDeviceStorage, createRecordingController, createRoomEngine, createTokenClient, createWebDeviceStorage, createWebEffectsStorage, createWebRoomTokenStorage, defaultCaptionLanguage, displayNameFor, fileDownloadPath, gridColumns, groupTranscript, guestRecordAccessExplanation, guestRecordAccessLabel, guestRecordAccessSetting, hasEnded, hostUserIds, invalidResponse, isGuest, isGuestIdentity, isHostRole, isListenOnly, isNoteTaker, isRealName, isRoomEventTopic, learnName, learnNames, listenOnlyNotice, loadFollowUpDraft, loadGuestMeetingRecord, loadMeetingActivities, loadMeetingChat, loadMeetingRecord, mapConnectionState, mapQuality, meetingInviteAction, meetingLength, meetingLink, meetingPath, meetingSlug, meetingTopic, meetingWhen, mergeCaption, mergeRecord, normalizeDeviceError, normalizeMeetError, normalizeSpoken, noteTakerIdentity, noteTakerLabel, noteTakerParticipant, noteTakerStatus, notesDocument, orderParticipants, parseRoomToken, participantSummary, playableRecordingFileId, projectInvitee, projectLibraryRecording, projectNote, projectOccurrence, projectOverride, projectParticipant, projectRsvpInvitation, projectSearchHit, projectSegment, projectUpcoming, pushMeetingToGoogleCalendar, readServerRefusal, readableTranscript, recordingLabel, recordingNotice, recordingOffsetMs, roleOf, roomTopic, searchTranscript, sendFollowUp, setGuestRecordAccess, settingsPatch, snapshotParticipant, snippetRuns, summarizeMeetingRecordRestrictions, transcriptDocument, unavailableFrom, uniqueAttendees, userTopic, wasRefused, withItem, wrapUpStatus };
3616
+ /**
3617
+ * ONE MEETING RECORD PER READER PER TAB — read once, kept current by one
3618
+ * ref-counted channel (0.7.79).
3619
+ *
3620
+ * 🚨 THE DEFECT THIS CLOSES. `useMeetingRecord` held the bundle in its own
3621
+ * component state and read all four parts (`meet_participants`, `meet_notes`,
3622
+ * `meet_transcript_segments`, `meet_recordings`) in a mount effect. A host that
3623
+ * hides a view without unmounting its data — a board tile asleep under React
3624
+ * `<Activity>`, which tears every effect down and runs it again on wake — read
3625
+ * the whole record again on every wake, on a Remove + Undo, and once more for
3626
+ * every second view of the same meeting (the record tab, the meeting's agent
3627
+ * surface, five "meeting notes" tiles). The view owned the data.
3628
+ *
3629
+ * THE SHAPE. One entry per (reader, meeting). The first view reads; every later
3630
+ * view — the same view after a wake or a remount included — renders the entry
3631
+ * and reads nothing. A read already in flight is joined. A forced read (Retry,
3632
+ * a reconnect) supersedes it, and an answer that lands after a newer read was
3633
+ * asked is DROPPED (the late-answer guard), so an old answer can never paint
3634
+ * over a newer one.
3635
+ *
3636
+ * LIVE WHILE HELD. A member's entry (a direct read, kept as its four parts) is
3637
+ * patched row by row from the database's change feed for the four tables, then
3638
+ * assembled again — one row, never four reads. The channel opens on the first
3639
+ * holder and closes `MEETING_RECORD_GRACE_MS` after the last one lets go, so a
3640
+ * wake or an Undo inside the grace costs nothing. Past the grace nothing
3641
+ * watched the meeting, so the entry is marked stale and the next view reads it
3642
+ * once. A guest's record arrives through the server as a finished bundle; it
3643
+ * has no change feed and is kept as it is.
3644
+ *
3645
+ * Framework-free on purpose: `react/hooks.ts` is one reader of it.
3646
+ */
3647
+
3648
+ /** How long a meeting's channel and kept record outlive the last view of it. */
3649
+ declare const MEETING_RECORD_GRACE_MS = 60000;
3650
+ interface MeetingRecordFailure {
3651
+ readonly message: string;
3652
+ readonly remedy: string;
3653
+ }
3654
+ interface MeetingRecordState {
3655
+ readonly loading: boolean;
3656
+ readonly record: MeetingRecordBundle | null;
3657
+ /** A failure that stopped the WHOLE record loading (never kept as an answer). */
3658
+ readonly failure: MeetingRecordFailure | null;
3659
+ }
3660
+ /** What a read produced: the four parts (patchable), or a finished bundle (a guest's). */
3661
+ type MeetingRecordAnswer = {
3662
+ readonly kind: "parts";
3663
+ readonly meeting: MeetingRecord;
3664
+ readonly parts: MeetingRecordParts;
3665
+ } | {
3666
+ readonly kind: "bundle";
3667
+ readonly bundle: MeetingRecordBundle;
3668
+ };
3669
+ /** One changed row, already projected by the repository. `null` + `removedId` = gone. */
3670
+ type MeetingRecordChange = {
3671
+ readonly part: "transcript";
3672
+ readonly row: MeetingRecordParts["transcript"][number] | null;
3673
+ readonly removedId?: string | undefined;
3674
+ } | {
3675
+ readonly part: "notes";
3676
+ readonly row: MeetingRecordParts["notes"][number] | null;
3677
+ readonly removedId?: string | undefined;
3678
+ } | {
3679
+ readonly part: "attendance";
3680
+ readonly row: MeetingRecordParts["attendees"][number] | null;
3681
+ readonly removedId?: string | undefined;
3682
+ } | {
3683
+ readonly part: "recording";
3684
+ readonly row: MeetingRecordParts["recordings"][number] | null;
3685
+ readonly removedId?: string | undefined;
3686
+ };
3687
+ interface MeetingRecordCache {
3688
+ /** The kept state for `key` (stable until it changes). */
3689
+ state(key: string | null): MeetingRecordState;
3690
+ subscribe(listener: () => void): () => void;
3691
+ /**
3692
+ * Read unless the entry is kept and current (or a read is in flight — joined).
3693
+ * `force` reads anyway and supersedes a read in flight. Never rejects: a
3694
+ * failure is in the state.
3695
+ */
3696
+ ensure(key: string, read: () => Promise<MeetingRecordAnswer>, options?: {
3697
+ force?: boolean;
3698
+ }): Promise<void>;
3699
+ /** The meeting row moved (a rename, it ended): assemble again — no read. */
3700
+ setMeeting(key: string, meeting: MeetingRecord): void;
3701
+ /** Apply one changed row from the change feed — no read. */
3702
+ apply(key: string, change: MeetingRecordChange): void;
3703
+ /**
3704
+ * Hold the live half: `open` on the first holder, its close a grace after the
3705
+ * last. `owner` is what the channel runs on (the realtime manager): a new
3706
+ * owner means the old one's channels are gone, so it opens again.
3707
+ */
3708
+ hold(key: string, open: () => () => void, owner?: unknown): () => void;
3709
+ /** Tests only. */
3710
+ reset(): void;
3711
+ }
3712
+ declare function createMeetingRecordCache(options?: {
3713
+ graceMs?: number;
3714
+ }): MeetingRecordCache;
3715
+
3716
+ export { ATTENDANCE_CSV_HEADER, type AccessTokenSource, type AdmissionState, type AnnounceResult, type ApplyRoomEventArgs, type AskArgs, type AttendanceKind, type AttendanceRow, type AudioProcessing, CALL_INVITE_KIND, CALL_INVITE_VERSION, CAPTION_LANGUAGES, CONSENT_NOTICE_ATTRIBUTE, type CallCenter, type CallCenterOptions, type CallId, type CallInvite, type CallInviteHandlerOptions, type CallInvitePayload, type CallMode, type CallState, type CallablePerson, type CollabAction, type CollabPanel, type CollabRequest, type CollabResult, type CollabSnapshot, type ConnectionQuality, type CreateCallInviteInput, type CreateMeetingInput, DATA_TOPICS, DEFAULT_EFFECTS, DEFAULT_PAGE_SIZE, type DataTopic, type DeviceKind, type DeviceManager, type DeviceManagerOptions, type DeviceSnapshot, type DeviceStorage, type DriverConnectionState, type DriverParticipant, type DriverTrackKind, type DriverTranscription, EMPTY_COLLAB, END_MEETING_NOT_SERVED, type EffectsStorage, type EndMeetingResult, FILE_SESSION_PATH, type FollowUpDraft, type FollowUpRecipient, type FollowUpSendResult, GUEST_RECORD_ACCESS_CHOICES, GUEST_RECORD_ACCESS_LEVELS, GUEST_RECORD_ACCESS_METADATA_KEY, GUEST_RECORD_NOT_SHARED, type GuestRecordAccess, type GuestRecordAccessResolution, type GuestRecordAccessSetting, IDLE_MEDIA, IDLE_TRACK, type InRoomClient, type InRoomClientOptions, type InviteeInput, type InviteeRole, type JoinRequest, type JsonObject, type JsonValue, LISTEN_ONLY_NOTICE, type LibraryRecording, type LiveKitDriverOptions, type LobbyEntry, type LocalMediaState, type LocalTrackState, MEETING_INVITE_KIND, MEETING_INVITE_VERSION, MEETING_RECORD_GRACE_MS, MEET_ANSWER_CONTEXT_EVENT, MEET_ANSWER_EVENTS, MEET_EVENTS, MEET_ROUTES, MEET_SCHEMA, MODERATION_ACTIONS, type MediaDevicesLike, type MediaEffects, type MediaUnavailable, type MeetAi, type MeetAiAnswer, type MeetAiOptions, type MeetAnswerCoverage, type MeetApi, type MeetApiOptions, type MeetApiRequest, type MeetBreakoutBroadcast, type MeetBreakoutPlan, type MeetBreakoutRoom, type MeetCollabSettings, type MeetDevice, MeetError, type MeetErrorCode, type MeetEvent, type MeetFileMedia, type MeetGrants, type MeetIdentity, type MeetParticipant, type MeetPoll, type MeetPollOption, type MeetPollState, type MeetQuestion, type MeetQuestionState, type MeetRepository, type MeetRoomDriver, type MeetSnapshot, type MeetStore, type MeetStroke, type MeetingActivities, type MeetingAttendee, type MeetingChanges, type MeetingChatHistory, type MeetingId, type MeetingInviteHandlerOptions, type MeetingInvitePayload, type MeetingInvitee, type MeetingKind, type MeetingNote, type MeetingNoteKind, type MeetingOccurrence, type MeetingRecord, type MeetingRecordAnswer, type MeetingRecordBundle, type MeetingRecordCache, type MeetingRecordChange, type MeetingRecordFailure, type MeetingRecordParts, type MeetingRecordRestriction, type MeetingRecordRestrictionNotice, type MeetingRecordState, type MeetingSearchHit, type MeetingSearchSource, type MeetingSession, type MeetingSessionOptions, type MeetingSettings, type ModerationAction, type ModerationNotice, type ModerationRequest, type ModerationResult, NOTE_TAKER_IDENTITY_PREFIX, NOTE_TAKER_METADATA_KEY, NO_GRANTS, type NameBook, type NoteTakerDispatch, type NoteTakerRefusal, type NoteTakerState, type NoteTakerStatus, ORGANIZATION_HEADER, type OccurrenceOverride, type OccurrenceState, type OrganizationId, PARTICIPANT_CAN_RECORD_ATTRIBUTE, PARTICIPANT_ROLE_ATTRIBUTE, type ParticipantIdentity, type ParticipantRole, REACTION_TTL_MS, REFUSED_ABSENCE, ROLE_RANK, ROOM_EVENTS, ROOM_EVENT_PREFIX, ROOM_TOKEN_HEADER, RPCS, type RecordingController, type RecordingControllerOptions, type RecordingPolicy, type RecordingRecord, type RecordingState, type RepositoryOptions, type RoomChatMessage, type RoomDriverFactory, type RoomDriverHandlers, type RoomEngine, type RoomEngineOptions, type RoomEventOutcome, type RoomEventTopic, type RoomName, type RoomPhase, type RoomReaction, type RoomToken, type RoomTokenStorage, type RsvpAnswer, type RsvpInvitation, type RsvpState, type ScheduleMeetingInput, type Stage, type StageArgs, type StageLayout, TABLES, TRANSCRIPTION_ATTRIBUTES, TRANSCRIPTION_TOPIC, type TokenClient, type TokenClientOptions, type TokenRequest, type TranscriptBlock, type TranscriptFormat, type TranscriptMatch, type TranscriptSegment, type TranscriptSource, type UpcomingOccurrence, type UserId, type VideoEffect, type WrapUpAnnouncement, type WrapUpStatus, absenceSentence, activeWrapUpSummary, announceMeeting, applyCollabPacket, applyRoomEvent, asCallId, asMeetingId, asOrganizationId, asParticipantIdentity, asRoomName, asUserId, assembleMeetingRecord, assignedRoom, attendanceCsv, attendanceReport, attendanceTotals, attendeeKey, autoAssign, breakoutRemainingMs, callInviteAction, callInviteText, callStateLabel, canModerate, canModerateParticipant, canTransition, captionSegment, changesPatch, clockPosition, collabFromState, computeStage, countLabel, createCallCenter, createCallInviteHandler, createDeviceManager, createInRoomClient, createLiveKitDriver, createMeetAi, createMeetApi, createMeetRepository, createMeetStore, createMeetingInviteHandler, createMeetingRecordCache, createMeetingSession, createMemoryDeviceStorage, createRecordingController, createRoomEngine, createTokenClient, createWebDeviceStorage, createWebEffectsStorage, createWebRoomTokenStorage, defaultCaptionLanguage, displayNameFor, fileDownloadPath, gridColumns, groupTranscript, guestRecordAccessExplanation, guestRecordAccessLabel, guestRecordAccessSetting, hasEnded, hostUserIds, invalidResponse, isGuest, isGuestIdentity, isHostRole, isListenOnly, isNoteTaker, isRealName, isRoomEventTopic, learnName, learnNames, listenOnlyNotice, loadFollowUpDraft, loadGuestMeetingRecord, loadMeetingActivities, loadMeetingChat, loadMeetingRecord, loadMeetingRecordParts, mapConnectionState, mapQuality, meetingInviteAction, meetingLength, meetingLink, meetingPath, meetingSlug, meetingTopic, meetingWhen, mergeCaption, mergeRecord, normalizeDeviceError, normalizeMeetError, normalizeSpoken, noteTakerIdentity, noteTakerLabel, noteTakerParticipant, noteTakerStatus, notesDocument, orderParticipants, parseRoomToken, participantSummary, playableRecordingFileId, projectInvitee, projectLibraryRecording, projectNote, projectOccurrence, projectOverride, projectParticipant, projectRsvpInvitation, projectSearchHit, projectSegment, projectUpcoming, pushMeetingToGoogleCalendar, readServerRefusal, readableTranscript, recordingLabel, recordingNotice, recordingOffsetMs, roleOf, roomTopic, searchTranscript, sendFollowUp, setGuestRecordAccess, settingsPatch, snapshotParticipant, snippetRuns, summarizeMeetingRecordRestrictions, transcriptDocument, unavailableFrom, uniqueAttendees, userTopic, wasRefused, withItem, wrapUpStatus };
package/dist/index.js CHANGED
@@ -826,13 +826,13 @@ function mergeCaption(list, caption) {
826
826
  return list.map((entry, position) => position === index ? caption : entry);
827
827
  }
828
828
  function mergeRecord(list, record) {
829
- const byId = list.findIndex((entry) => entry.source === "record" && entry.id === record.id);
830
- if (byId !== -1) {
831
- const existing = list[byId];
829
+ const byId2 = list.findIndex((entry) => entry.source === "record" && entry.id === record.id);
830
+ if (byId2 !== -1) {
831
+ const existing = list[byId2];
832
832
  if (existing !== void 0 && existing.text === record.text && existing.final === record.final) {
833
833
  return list;
834
834
  }
835
- return list.map((entry, position) => position === byId ? record : entry);
835
+ return list.map((entry, position) => position === byId2 ? record : entry);
836
836
  }
837
837
  const captionIndex = list.findIndex((entry) => supersedes(record, entry));
838
838
  if (captionIndex === -1) return [...list, record];
@@ -5662,6 +5662,9 @@ function restrictionFor(part, error) {
5662
5662
  };
5663
5663
  }
5664
5664
  async function loadMeetingRecord(repository, meetingId, meeting, reader = { userId: null }) {
5665
+ return assembleMeetingRecord(meeting, await loadMeetingRecordParts(repository, meetingId, reader));
5666
+ }
5667
+ async function loadMeetingRecordParts(repository, meetingId, reader = { userId: null }) {
5665
5668
  const restrictions = [];
5666
5669
  async function attempt(part, run, empty) {
5667
5670
  if (reader.userId === null) {
@@ -5689,6 +5692,10 @@ async function loadMeetingRecord(repository, meetingId, meeting, reader = { user
5689
5692
  []
5690
5693
  )
5691
5694
  ]);
5695
+ return { attendees, notes, transcript, recordings, restrictions };
5696
+ }
5697
+ function assembleMeetingRecord(meeting, parts) {
5698
+ const { attendees, notes, transcript, recordings, restrictions } = parts;
5692
5699
  return {
5693
5700
  meeting,
5694
5701
  attendees,
@@ -6033,6 +6040,210 @@ async function setGuestRecordAccess(args) {
6033
6040
  };
6034
6041
  }
6035
6042
  var GUEST_RECORD_NOT_SHARED = NOT_SHARED;
6043
+
6044
+ // src/core/record-cache.ts
6045
+ var MEETING_RECORD_GRACE_MS = 6e4;
6046
+ var INITIAL = { loading: true, record: null, failure: null };
6047
+ function upsert2(rows, row, removedId, order) {
6048
+ const id = row?.id ?? removedId;
6049
+ if (id === void 0) return rows;
6050
+ const rest = rows.filter((existing) => existing.id !== id);
6051
+ return row === null ? rest : [...rest, row].sort(order);
6052
+ }
6053
+ var byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
6054
+ var ascending = (a, b) => a === b ? 0 : a === null ? 1 : b === null ? -1 : a < b ? -1 : 1;
6055
+ function createMeetingRecordCache(options = {}) {
6056
+ const graceMs = options.graceMs ?? MEETING_RECORD_GRACE_MS;
6057
+ const entries = /* @__PURE__ */ new Map();
6058
+ const listeners = /* @__PURE__ */ new Set();
6059
+ const notify = () => {
6060
+ for (const listener of listeners) listener();
6061
+ };
6062
+ const entryOf = (key) => {
6063
+ let entry = entries.get(key);
6064
+ if (!entry) {
6065
+ entry = {
6066
+ state: INITIAL,
6067
+ meeting: null,
6068
+ parts: null,
6069
+ fresh: false,
6070
+ seq: 0,
6071
+ inflight: null,
6072
+ holders: 0,
6073
+ close: null,
6074
+ owner: null,
6075
+ grace: null
6076
+ };
6077
+ entries.set(key, entry);
6078
+ }
6079
+ return entry;
6080
+ };
6081
+ const assemble = (entry) => {
6082
+ if (entry.meeting === null || entry.parts === null) return;
6083
+ entry.state = { loading: false, failure: null, record: assembleMeetingRecord(entry.meeting, entry.parts) };
6084
+ notify();
6085
+ };
6086
+ return {
6087
+ state: (key) => key === null ? INITIAL : entries.get(key)?.state ?? INITIAL,
6088
+ subscribe(listener) {
6089
+ listeners.add(listener);
6090
+ return () => {
6091
+ listeners.delete(listener);
6092
+ };
6093
+ },
6094
+ ensure(key, read, { force = false } = {}) {
6095
+ const entry = entryOf(key);
6096
+ if (!force) {
6097
+ if (entry.inflight !== null) return entry.inflight;
6098
+ if (entry.fresh) return Promise.resolve();
6099
+ }
6100
+ const seq = ++entry.seq;
6101
+ if (entry.state.failure !== null) {
6102
+ entry.state = { ...entry.state, loading: true, failure: null };
6103
+ notify();
6104
+ }
6105
+ const request = read().then(
6106
+ (answer) => {
6107
+ if (seq !== entry.seq) return;
6108
+ entry.fresh = true;
6109
+ if (answer.kind === "parts") {
6110
+ entry.meeting = answer.meeting;
6111
+ entry.parts = answer.parts;
6112
+ assemble(entry);
6113
+ } else {
6114
+ entry.meeting = answer.bundle.meeting;
6115
+ entry.parts = null;
6116
+ entry.state = { loading: false, failure: null, record: answer.bundle };
6117
+ notify();
6118
+ }
6119
+ },
6120
+ (thrown) => {
6121
+ if (seq !== entry.seq) return;
6122
+ entry.fresh = false;
6123
+ entry.state = {
6124
+ loading: false,
6125
+ record: entry.state.record,
6126
+ failure: {
6127
+ message: thrown?.message ?? String(thrown),
6128
+ remedy: thrown?.remedy ?? "The meeting record could not be read. Reload the page; nothing was changed."
6129
+ }
6130
+ };
6131
+ notify();
6132
+ }
6133
+ );
6134
+ const tracked = request.finally(() => {
6135
+ if (entry.inflight === tracked) entry.inflight = null;
6136
+ });
6137
+ entry.inflight = tracked;
6138
+ return tracked;
6139
+ },
6140
+ setMeeting(key, meeting) {
6141
+ const entry = entries.get(key);
6142
+ if (!entry || entry.meeting === meeting) return;
6143
+ if (entry.parts !== null) {
6144
+ entry.meeting = meeting;
6145
+ assemble(entry);
6146
+ } else if (entry.state.record !== null) {
6147
+ entry.meeting = meeting;
6148
+ entry.state = { ...entry.state, record: { ...entry.state.record, meeting } };
6149
+ notify();
6150
+ }
6151
+ },
6152
+ apply(key, change) {
6153
+ const entry = entries.get(key);
6154
+ const parts = entry?.parts;
6155
+ if (!entry || !parts) return;
6156
+ const refused = parts.restrictions.some((r) => r.part === change.part);
6157
+ if (refused) return;
6158
+ switch (change.part) {
6159
+ case "transcript":
6160
+ entry.parts = {
6161
+ ...parts,
6162
+ transcript: upsert2(
6163
+ parts.transcript,
6164
+ change.row,
6165
+ change.removedId,
6166
+ (a, b) => ascending(a.startedAt, b.startedAt) || byId(a, b)
6167
+ )
6168
+ };
6169
+ break;
6170
+ case "notes":
6171
+ entry.parts = {
6172
+ ...parts,
6173
+ notes: upsert2(
6174
+ parts.notes,
6175
+ change.row,
6176
+ change.removedId,
6177
+ (a, b) => ascending(a.createdAt, b.createdAt) || byId(a, b)
6178
+ )
6179
+ };
6180
+ break;
6181
+ case "attendance":
6182
+ entry.parts = {
6183
+ ...parts,
6184
+ attendees: upsert2(
6185
+ parts.attendees,
6186
+ change.row,
6187
+ change.removedId,
6188
+ (a, b) => ascending(a.joinedAt, b.joinedAt) || byId(a, b)
6189
+ )
6190
+ };
6191
+ break;
6192
+ case "recording":
6193
+ entry.parts = {
6194
+ ...parts,
6195
+ // Newest first, as the read orders them.
6196
+ recordings: upsert2(
6197
+ parts.recordings,
6198
+ change.row,
6199
+ change.removedId,
6200
+ (a, b) => ascending(b.startedAt, a.startedAt) || byId(a, b)
6201
+ )
6202
+ };
6203
+ break;
6204
+ }
6205
+ assemble(entry);
6206
+ },
6207
+ hold(key, open, owner = null) {
6208
+ const entry = entryOf(key);
6209
+ if (entry.grace !== null) {
6210
+ clearTimeout(entry.grace);
6211
+ entry.grace = null;
6212
+ }
6213
+ if (entry.close !== null && entry.owner !== owner) {
6214
+ entry.close();
6215
+ entry.close = null;
6216
+ }
6217
+ if (entry.close === null) {
6218
+ entry.close = open();
6219
+ entry.owner = owner;
6220
+ }
6221
+ entry.holders += 1;
6222
+ let released = false;
6223
+ return () => {
6224
+ if (released) return;
6225
+ released = true;
6226
+ entry.holders -= 1;
6227
+ if (entry.holders > 0) return;
6228
+ entry.grace = setTimeout(() => {
6229
+ entry.grace = null;
6230
+ if (entry.holders > 0) return;
6231
+ entry.close?.();
6232
+ entry.close = null;
6233
+ entry.fresh = false;
6234
+ }, graceMs);
6235
+ };
6236
+ },
6237
+ reset() {
6238
+ for (const entry of entries.values()) {
6239
+ if (entry.grace !== null) clearTimeout(entry.grace);
6240
+ entry.close?.();
6241
+ }
6242
+ entries.clear();
6243
+ notify();
6244
+ }
6245
+ };
6246
+ }
6036
6247
  export {
6037
6248
  ATTENDANCE_CSV_HEADER,
6038
6249
  CALL_INVITE_KIND,
@@ -6054,6 +6265,7 @@ export {
6054
6265
  LISTEN_ONLY_NOTICE,
6055
6266
  MEETING_INVITE_KIND,
6056
6267
  MEETING_INVITE_VERSION,
6268
+ MEETING_RECORD_GRACE_MS,
6057
6269
  MEET_ANSWER_CONTEXT_EVENT,
6058
6270
  MEET_ANSWER_EVENTS,
6059
6271
  MEET_EVENTS,
@@ -6088,6 +6300,7 @@ export {
6088
6300
  asParticipantIdentity,
6089
6301
  asRoomName,
6090
6302
  asUserId,
6303
+ assembleMeetingRecord,
6091
6304
  assignedRoom,
6092
6305
  attendanceCsv,
6093
6306
  attendanceReport,
@@ -6117,6 +6330,7 @@ export {
6117
6330
  createMeetRepository,
6118
6331
  createMeetStore,
6119
6332
  createMeetingInviteHandler,
6333
+ createMeetingRecordCache,
6120
6334
  createMeetingSession,
6121
6335
  createMemoryDeviceStorage,
6122
6336
  createRecordingController,
@@ -6151,6 +6365,7 @@ export {
6151
6365
  loadMeetingActivities,
6152
6366
  loadMeetingChat,
6153
6367
  loadMeetingRecord,
6368
+ loadMeetingRecordParts,
6154
6369
  mapConnectionState,
6155
6370
  mapQuality,
6156
6371
  meetingInviteAction,