@ai-matrx/meet 0.7.77 → 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/CHANGELOG.md +31 -0
- package/README.md +4 -0
- package/dist/index.cjs +219 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -1
- package/dist/index.d.ts +120 -1
- package/dist/index.js +219 -4
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +478 -203
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +4 -3
- package/dist/react.d.ts +4 -3
- package/dist/react.js +373 -98
- package/dist/react.js.map +1 -1
- package/dist/styles.css +6 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog — `@ai-matrx/meet`
|
|
2
2
|
|
|
3
|
+
## 0.7.79
|
|
4
|
+
|
|
5
|
+
**A meeting's record is read once per tab, and kept live.** `useMeetingRecord` held the bundle
|
|
6
|
+
in its own state and read all four parts (`meet_participants`, `meet_notes`,
|
|
7
|
+
`meet_transcript_segments`, `meet_recordings`) on every mount — so a board tile that slept
|
|
8
|
+
and woke (React `<Activity>` re-runs every effect), a Remove + Undo, and every second view of
|
|
9
|
+
the same meeting read the whole record again. The record now lives in one per-tab cache
|
|
10
|
+
(`core/record-cache.ts`, keyed by reader + meeting): the first view reads, later views and
|
|
11
|
+
remounts render the kept record and read nothing; a read in flight is joined; Retry and a
|
|
12
|
+
reconnect read again, and an older answer never paints over a newer one. A member's record is
|
|
13
|
+
kept current by ONE change-feed channel per meeting (notes, transcript, attendance,
|
|
14
|
+
recordings — applied row by row, no re-read), held while any view is mounted and 60 s after;
|
|
15
|
+
past that the record is marked stale and the next view reads it once. A meeting rename
|
|
16
|
+
re-assembles the kept record instead of re-reading it. New exports: `loadMeetingRecordParts`,
|
|
17
|
+
`assembleMeetingRecord`, `createMeetingRecordCache`, `MEETING_RECORD_GRACE_MS`, and
|
|
18
|
+
`meetingRecordCache()` from `/react`. Guard: `react/meeting-record-reads-once.test.tsx`
|
|
19
|
+
(4/4 red on 0.7.78).
|
|
20
|
+
|
|
21
|
+
Consumer action: none. A host test that renders the record for the same meeting twice and
|
|
22
|
+
expects two reads resets the cache between cases (`meetingRecordCache().reset()`).
|
|
23
|
+
|
|
24
|
+
## 0.7.78
|
|
25
|
+
|
|
26
|
+
**A meeting can run inside a box.** `.mx-meet` was `height: 100dvh`, so a stage mounted in a
|
|
27
|
+
board tile or a pane was as tall as the window and its control bar fell below the box. The
|
|
28
|
+
height now reads `var(--mx-meet-height, 100dvh)`: unchanged by default; a host sets
|
|
29
|
+
`--mx-meet-height: 100%` on an ancestor with a definite height to contain every screen.
|
|
30
|
+
Guard: `styles.test.ts` "the stage's height".
|
|
31
|
+
|
|
32
|
+
Consumer action: none (opt-in).
|
|
33
|
+
|
|
3
34
|
## 0.7.77
|
|
4
35
|
|
|
5
36
|
The pre-join background choice spans its column (`SegmentedControl fullWidth`) instead of
|
package/README.md
CHANGED
|
@@ -192,6 +192,10 @@ export function App({ children }) {
|
|
|
192
192
|
<MeetingRoom roomName={roomName} meetingId={meetingId} />
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
+
The stage fills the viewport (`100dvh`). To run a meeting inside a box — a board tile, a split
|
|
196
|
+
pane — give an ancestor a definite height and set `--mx-meet-height: 100%` on it; every screen
|
|
197
|
+
(pre-join, lobby, room, record) then fills that box.
|
|
198
|
+
|
|
195
199
|
🚨 **You do not tell Meet who the user is, and there is no prop for it.** The acting user is read
|
|
196
200
|
from `client`'s own session — the same client that mints the JWT on every request — so the id in a
|
|
197
201
|
`p_user_id` argument and the id in the token cannot disagree. Through 0.5.x this WAS a prop, every
|
package/dist/index.cjs
CHANGED
|
@@ -50,6 +50,7 @@ __export(src_exports, {
|
|
|
50
50
|
LISTEN_ONLY_NOTICE: () => LISTEN_ONLY_NOTICE,
|
|
51
51
|
MEETING_INVITE_KIND: () => MEETING_INVITE_KIND,
|
|
52
52
|
MEETING_INVITE_VERSION: () => MEETING_INVITE_VERSION,
|
|
53
|
+
MEETING_RECORD_GRACE_MS: () => MEETING_RECORD_GRACE_MS,
|
|
53
54
|
MEET_ANSWER_CONTEXT_EVENT: () => MEET_ANSWER_CONTEXT_EVENT,
|
|
54
55
|
MEET_ANSWER_EVENTS: () => MEET_ANSWER_EVENTS,
|
|
55
56
|
MEET_EVENTS: () => MEET_EVENTS,
|
|
@@ -84,6 +85,7 @@ __export(src_exports, {
|
|
|
84
85
|
asParticipantIdentity: () => asParticipantIdentity,
|
|
85
86
|
asRoomName: () => asRoomName,
|
|
86
87
|
asUserId: () => asUserId,
|
|
88
|
+
assembleMeetingRecord: () => assembleMeetingRecord,
|
|
87
89
|
assignedRoom: () => assignedRoom,
|
|
88
90
|
attendanceCsv: () => attendanceCsv,
|
|
89
91
|
attendanceReport: () => attendanceReport,
|
|
@@ -113,6 +115,7 @@ __export(src_exports, {
|
|
|
113
115
|
createMeetRepository: () => createMeetRepository,
|
|
114
116
|
createMeetStore: () => createMeetStore,
|
|
115
117
|
createMeetingInviteHandler: () => createMeetingInviteHandler,
|
|
118
|
+
createMeetingRecordCache: () => createMeetingRecordCache,
|
|
116
119
|
createMeetingSession: () => createMeetingSession,
|
|
117
120
|
createMemoryDeviceStorage: () => createMemoryDeviceStorage,
|
|
118
121
|
createRecordingController: () => createRecordingController,
|
|
@@ -147,6 +150,7 @@ __export(src_exports, {
|
|
|
147
150
|
loadMeetingActivities: () => loadMeetingActivities,
|
|
148
151
|
loadMeetingChat: () => loadMeetingChat,
|
|
149
152
|
loadMeetingRecord: () => loadMeetingRecord,
|
|
153
|
+
loadMeetingRecordParts: () => loadMeetingRecordParts,
|
|
150
154
|
mapConnectionState: () => mapConnectionState,
|
|
151
155
|
mapQuality: () => mapQuality,
|
|
152
156
|
meetingInviteAction: () => meetingInviteAction,
|
|
@@ -1033,13 +1037,13 @@ function mergeCaption(list, caption) {
|
|
|
1033
1037
|
return list.map((entry, position) => position === index ? caption : entry);
|
|
1034
1038
|
}
|
|
1035
1039
|
function mergeRecord(list, record) {
|
|
1036
|
-
const
|
|
1037
|
-
if (
|
|
1038
|
-
const existing = list[
|
|
1040
|
+
const byId2 = list.findIndex((entry) => entry.source === "record" && entry.id === record.id);
|
|
1041
|
+
if (byId2 !== -1) {
|
|
1042
|
+
const existing = list[byId2];
|
|
1039
1043
|
if (existing !== void 0 && existing.text === record.text && existing.final === record.final) {
|
|
1040
1044
|
return list;
|
|
1041
1045
|
}
|
|
1042
|
-
return list.map((entry, position) => position ===
|
|
1046
|
+
return list.map((entry, position) => position === byId2 ? record : entry);
|
|
1043
1047
|
}
|
|
1044
1048
|
const captionIndex = list.findIndex((entry) => supersedes(record, entry));
|
|
1045
1049
|
if (captionIndex === -1) return [...list, record];
|
|
@@ -5869,6 +5873,9 @@ function restrictionFor(part, error) {
|
|
|
5869
5873
|
};
|
|
5870
5874
|
}
|
|
5871
5875
|
async function loadMeetingRecord(repository, meetingId, meeting, reader = { userId: null }) {
|
|
5876
|
+
return assembleMeetingRecord(meeting, await loadMeetingRecordParts(repository, meetingId, reader));
|
|
5877
|
+
}
|
|
5878
|
+
async function loadMeetingRecordParts(repository, meetingId, reader = { userId: null }) {
|
|
5872
5879
|
const restrictions = [];
|
|
5873
5880
|
async function attempt(part, run, empty) {
|
|
5874
5881
|
if (reader.userId === null) {
|
|
@@ -5896,6 +5903,10 @@ async function loadMeetingRecord(repository, meetingId, meeting, reader = { user
|
|
|
5896
5903
|
[]
|
|
5897
5904
|
)
|
|
5898
5905
|
]);
|
|
5906
|
+
return { attendees, notes, transcript, recordings, restrictions };
|
|
5907
|
+
}
|
|
5908
|
+
function assembleMeetingRecord(meeting, parts) {
|
|
5909
|
+
const { attendees, notes, transcript, recordings, restrictions } = parts;
|
|
5899
5910
|
return {
|
|
5900
5911
|
meeting,
|
|
5901
5912
|
attendees,
|
|
@@ -6240,4 +6251,208 @@ async function setGuestRecordAccess(args) {
|
|
|
6240
6251
|
};
|
|
6241
6252
|
}
|
|
6242
6253
|
var GUEST_RECORD_NOT_SHARED = NOT_SHARED;
|
|
6254
|
+
|
|
6255
|
+
// src/core/record-cache.ts
|
|
6256
|
+
var MEETING_RECORD_GRACE_MS = 6e4;
|
|
6257
|
+
var INITIAL = { loading: true, record: null, failure: null };
|
|
6258
|
+
function upsert2(rows, row, removedId, order) {
|
|
6259
|
+
const id = row?.id ?? removedId;
|
|
6260
|
+
if (id === void 0) return rows;
|
|
6261
|
+
const rest = rows.filter((existing) => existing.id !== id);
|
|
6262
|
+
return row === null ? rest : [...rest, row].sort(order);
|
|
6263
|
+
}
|
|
6264
|
+
var byId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
6265
|
+
var ascending = (a, b) => a === b ? 0 : a === null ? 1 : b === null ? -1 : a < b ? -1 : 1;
|
|
6266
|
+
function createMeetingRecordCache(options = {}) {
|
|
6267
|
+
const graceMs = options.graceMs ?? MEETING_RECORD_GRACE_MS;
|
|
6268
|
+
const entries = /* @__PURE__ */ new Map();
|
|
6269
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
6270
|
+
const notify = () => {
|
|
6271
|
+
for (const listener of listeners) listener();
|
|
6272
|
+
};
|
|
6273
|
+
const entryOf = (key) => {
|
|
6274
|
+
let entry = entries.get(key);
|
|
6275
|
+
if (!entry) {
|
|
6276
|
+
entry = {
|
|
6277
|
+
state: INITIAL,
|
|
6278
|
+
meeting: null,
|
|
6279
|
+
parts: null,
|
|
6280
|
+
fresh: false,
|
|
6281
|
+
seq: 0,
|
|
6282
|
+
inflight: null,
|
|
6283
|
+
holders: 0,
|
|
6284
|
+
close: null,
|
|
6285
|
+
owner: null,
|
|
6286
|
+
grace: null
|
|
6287
|
+
};
|
|
6288
|
+
entries.set(key, entry);
|
|
6289
|
+
}
|
|
6290
|
+
return entry;
|
|
6291
|
+
};
|
|
6292
|
+
const assemble = (entry) => {
|
|
6293
|
+
if (entry.meeting === null || entry.parts === null) return;
|
|
6294
|
+
entry.state = { loading: false, failure: null, record: assembleMeetingRecord(entry.meeting, entry.parts) };
|
|
6295
|
+
notify();
|
|
6296
|
+
};
|
|
6297
|
+
return {
|
|
6298
|
+
state: (key) => key === null ? INITIAL : entries.get(key)?.state ?? INITIAL,
|
|
6299
|
+
subscribe(listener) {
|
|
6300
|
+
listeners.add(listener);
|
|
6301
|
+
return () => {
|
|
6302
|
+
listeners.delete(listener);
|
|
6303
|
+
};
|
|
6304
|
+
},
|
|
6305
|
+
ensure(key, read, { force = false } = {}) {
|
|
6306
|
+
const entry = entryOf(key);
|
|
6307
|
+
if (!force) {
|
|
6308
|
+
if (entry.inflight !== null) return entry.inflight;
|
|
6309
|
+
if (entry.fresh) return Promise.resolve();
|
|
6310
|
+
}
|
|
6311
|
+
const seq = ++entry.seq;
|
|
6312
|
+
if (entry.state.failure !== null) {
|
|
6313
|
+
entry.state = { ...entry.state, loading: true, failure: null };
|
|
6314
|
+
notify();
|
|
6315
|
+
}
|
|
6316
|
+
const request = read().then(
|
|
6317
|
+
(answer) => {
|
|
6318
|
+
if (seq !== entry.seq) return;
|
|
6319
|
+
entry.fresh = true;
|
|
6320
|
+
if (answer.kind === "parts") {
|
|
6321
|
+
entry.meeting = answer.meeting;
|
|
6322
|
+
entry.parts = answer.parts;
|
|
6323
|
+
assemble(entry);
|
|
6324
|
+
} else {
|
|
6325
|
+
entry.meeting = answer.bundle.meeting;
|
|
6326
|
+
entry.parts = null;
|
|
6327
|
+
entry.state = { loading: false, failure: null, record: answer.bundle };
|
|
6328
|
+
notify();
|
|
6329
|
+
}
|
|
6330
|
+
},
|
|
6331
|
+
(thrown) => {
|
|
6332
|
+
if (seq !== entry.seq) return;
|
|
6333
|
+
entry.fresh = false;
|
|
6334
|
+
entry.state = {
|
|
6335
|
+
loading: false,
|
|
6336
|
+
record: entry.state.record,
|
|
6337
|
+
failure: {
|
|
6338
|
+
message: thrown?.message ?? String(thrown),
|
|
6339
|
+
remedy: thrown?.remedy ?? "The meeting record could not be read. Reload the page; nothing was changed."
|
|
6340
|
+
}
|
|
6341
|
+
};
|
|
6342
|
+
notify();
|
|
6343
|
+
}
|
|
6344
|
+
);
|
|
6345
|
+
const tracked = request.finally(() => {
|
|
6346
|
+
if (entry.inflight === tracked) entry.inflight = null;
|
|
6347
|
+
});
|
|
6348
|
+
entry.inflight = tracked;
|
|
6349
|
+
return tracked;
|
|
6350
|
+
},
|
|
6351
|
+
setMeeting(key, meeting) {
|
|
6352
|
+
const entry = entries.get(key);
|
|
6353
|
+
if (!entry || entry.meeting === meeting) return;
|
|
6354
|
+
if (entry.parts !== null) {
|
|
6355
|
+
entry.meeting = meeting;
|
|
6356
|
+
assemble(entry);
|
|
6357
|
+
} else if (entry.state.record !== null) {
|
|
6358
|
+
entry.meeting = meeting;
|
|
6359
|
+
entry.state = { ...entry.state, record: { ...entry.state.record, meeting } };
|
|
6360
|
+
notify();
|
|
6361
|
+
}
|
|
6362
|
+
},
|
|
6363
|
+
apply(key, change) {
|
|
6364
|
+
const entry = entries.get(key);
|
|
6365
|
+
const parts = entry?.parts;
|
|
6366
|
+
if (!entry || !parts) return;
|
|
6367
|
+
const refused = parts.restrictions.some((r) => r.part === change.part);
|
|
6368
|
+
if (refused) return;
|
|
6369
|
+
switch (change.part) {
|
|
6370
|
+
case "transcript":
|
|
6371
|
+
entry.parts = {
|
|
6372
|
+
...parts,
|
|
6373
|
+
transcript: upsert2(
|
|
6374
|
+
parts.transcript,
|
|
6375
|
+
change.row,
|
|
6376
|
+
change.removedId,
|
|
6377
|
+
(a, b) => ascending(a.startedAt, b.startedAt) || byId(a, b)
|
|
6378
|
+
)
|
|
6379
|
+
};
|
|
6380
|
+
break;
|
|
6381
|
+
case "notes":
|
|
6382
|
+
entry.parts = {
|
|
6383
|
+
...parts,
|
|
6384
|
+
notes: upsert2(
|
|
6385
|
+
parts.notes,
|
|
6386
|
+
change.row,
|
|
6387
|
+
change.removedId,
|
|
6388
|
+
(a, b) => ascending(a.createdAt, b.createdAt) || byId(a, b)
|
|
6389
|
+
)
|
|
6390
|
+
};
|
|
6391
|
+
break;
|
|
6392
|
+
case "attendance":
|
|
6393
|
+
entry.parts = {
|
|
6394
|
+
...parts,
|
|
6395
|
+
attendees: upsert2(
|
|
6396
|
+
parts.attendees,
|
|
6397
|
+
change.row,
|
|
6398
|
+
change.removedId,
|
|
6399
|
+
(a, b) => ascending(a.joinedAt, b.joinedAt) || byId(a, b)
|
|
6400
|
+
)
|
|
6401
|
+
};
|
|
6402
|
+
break;
|
|
6403
|
+
case "recording":
|
|
6404
|
+
entry.parts = {
|
|
6405
|
+
...parts,
|
|
6406
|
+
// Newest first, as the read orders them.
|
|
6407
|
+
recordings: upsert2(
|
|
6408
|
+
parts.recordings,
|
|
6409
|
+
change.row,
|
|
6410
|
+
change.removedId,
|
|
6411
|
+
(a, b) => ascending(b.startedAt, a.startedAt) || byId(a, b)
|
|
6412
|
+
)
|
|
6413
|
+
};
|
|
6414
|
+
break;
|
|
6415
|
+
}
|
|
6416
|
+
assemble(entry);
|
|
6417
|
+
},
|
|
6418
|
+
hold(key, open, owner = null) {
|
|
6419
|
+
const entry = entryOf(key);
|
|
6420
|
+
if (entry.grace !== null) {
|
|
6421
|
+
clearTimeout(entry.grace);
|
|
6422
|
+
entry.grace = null;
|
|
6423
|
+
}
|
|
6424
|
+
if (entry.close !== null && entry.owner !== owner) {
|
|
6425
|
+
entry.close();
|
|
6426
|
+
entry.close = null;
|
|
6427
|
+
}
|
|
6428
|
+
if (entry.close === null) {
|
|
6429
|
+
entry.close = open();
|
|
6430
|
+
entry.owner = owner;
|
|
6431
|
+
}
|
|
6432
|
+
entry.holders += 1;
|
|
6433
|
+
let released = false;
|
|
6434
|
+
return () => {
|
|
6435
|
+
if (released) return;
|
|
6436
|
+
released = true;
|
|
6437
|
+
entry.holders -= 1;
|
|
6438
|
+
if (entry.holders > 0) return;
|
|
6439
|
+
entry.grace = setTimeout(() => {
|
|
6440
|
+
entry.grace = null;
|
|
6441
|
+
if (entry.holders > 0) return;
|
|
6442
|
+
entry.close?.();
|
|
6443
|
+
entry.close = null;
|
|
6444
|
+
entry.fresh = false;
|
|
6445
|
+
}, graceMs);
|
|
6446
|
+
};
|
|
6447
|
+
},
|
|
6448
|
+
reset() {
|
|
6449
|
+
for (const entry of entries.values()) {
|
|
6450
|
+
if (entry.grace !== null) clearTimeout(entry.grace);
|
|
6451
|
+
entry.close?.();
|
|
6452
|
+
}
|
|
6453
|
+
entries.clear();
|
|
6454
|
+
notify();
|
|
6455
|
+
}
|
|
6456
|
+
};
|
|
6457
|
+
}
|
|
6243
6458
|
//# sourceMappingURL=index.cjs.map
|