@agentshouse/mdruntime 0.1.0-c5fe3b2cddda7 → 0.1.0-c6197367d5fad

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.
Files changed (332) hide show
  1. package/README.md +21 -19
  2. package/dist/acceptance.d.ts +3 -0
  3. package/dist/acceptance.d.ts.map +1 -0
  4. package/dist/acceptance.js +299 -0
  5. package/dist/act-lanes.d.ts +30 -0
  6. package/dist/act-lanes.d.ts.map +1 -0
  7. package/dist/act-lanes.js +225 -0
  8. package/dist/actions.d.ts +26 -0
  9. package/dist/actions.d.ts.map +1 -0
  10. package/dist/actions.js +302 -0
  11. package/dist/adapter.d.ts +156 -89
  12. package/dist/adapter.d.ts.map +1 -1
  13. package/dist/attached-model.d.ts +3 -11
  14. package/dist/attached-model.d.ts.map +1 -1
  15. package/dist/attached-model.js +20 -60
  16. package/dist/authored.d.ts +88 -0
  17. package/dist/authored.d.ts.map +1 -0
  18. package/dist/authored.js +345 -0
  19. package/dist/bootstrap.d.ts +9 -5
  20. package/dist/bootstrap.d.ts.map +1 -1
  21. package/dist/bootstrap.js +204 -85
  22. package/dist/cell.d.ts +6 -0
  23. package/dist/cell.d.ts.map +1 -0
  24. package/dist/cell.js +9 -0
  25. package/dist/chain.d.ts +2 -1
  26. package/dist/chain.d.ts.map +1 -1
  27. package/dist/change-set.d.ts +4 -0
  28. package/dist/change-set.d.ts.map +1 -1
  29. package/dist/change-set.js +6 -0
  30. package/dist/change.d.ts +5 -0
  31. package/dist/change.d.ts.map +1 -1
  32. package/dist/change.js +7 -2
  33. package/dist/chat-types.d.ts +3 -0
  34. package/dist/chat-types.d.ts.map +1 -0
  35. package/dist/chat-types.js +11 -0
  36. package/dist/client.d.ts +33 -23
  37. package/dist/client.d.ts.map +1 -1
  38. package/dist/client.js +43 -56
  39. package/dist/collection.d.ts +26 -4
  40. package/dist/collection.d.ts.map +1 -1
  41. package/dist/collection.js +19 -2
  42. package/dist/column.d.ts +5 -0
  43. package/dist/column.d.ts.map +1 -0
  44. package/dist/column.js +9 -0
  45. package/dist/compile.d.ts +8 -6
  46. package/dist/compile.d.ts.map +1 -1
  47. package/dist/compile.js +286 -336
  48. package/dist/connection.d.ts +5 -142
  49. package/dist/connection.d.ts.map +1 -1
  50. package/dist/connection.js +111 -4993
  51. package/dist/context.d.ts +2 -0
  52. package/dist/context.d.ts.map +1 -0
  53. package/dist/context.js +7 -0
  54. package/dist/conversation-live.d.ts +150 -0
  55. package/dist/conversation-live.d.ts.map +1 -0
  56. package/dist/conversation-live.js +259 -0
  57. package/dist/conversation-row.d.ts +40 -0
  58. package/dist/conversation-row.d.ts.map +1 -0
  59. package/dist/conversation-row.js +66 -0
  60. package/dist/conversation.d.ts +72 -51
  61. package/dist/conversation.d.ts.map +1 -1
  62. package/dist/conversation.js +1001 -926
  63. package/dist/declaration.d.ts +10 -0
  64. package/dist/declaration.d.ts.map +1 -0
  65. package/dist/declaration.js +24 -0
  66. package/dist/discovery.d.ts +11 -0
  67. package/dist/discovery.d.ts.map +1 -0
  68. package/dist/discovery.js +157 -0
  69. package/dist/download.d.ts +6 -0
  70. package/dist/download.d.ts.map +1 -0
  71. package/dist/download.js +42 -0
  72. package/dist/durability.d.ts +20 -0
  73. package/dist/durability.d.ts.map +1 -1
  74. package/dist/durability.js +7 -0
  75. package/dist/entry.d.ts +2 -2
  76. package/dist/entry.d.ts.map +1 -1
  77. package/dist/entry.js +18 -8
  78. package/dist/expectation.d.ts +26 -13
  79. package/dist/expectation.d.ts.map +1 -1
  80. package/dist/expectation.js +72 -19
  81. package/dist/failure.d.ts +3 -0
  82. package/dist/failure.d.ts.map +1 -1
  83. package/dist/failure.js +6 -0
  84. package/dist/figures.d.ts +49 -0
  85. package/dist/figures.d.ts.map +1 -0
  86. package/dist/figures.js +199 -0
  87. package/dist/file.d.ts +1 -1
  88. package/dist/file.d.ts.map +1 -1
  89. package/dist/file.js +3 -2
  90. package/dist/holdings.d.ts +29 -0
  91. package/dist/holdings.d.ts.map +1 -0
  92. package/dist/holdings.js +95 -0
  93. package/dist/host.d.ts +21 -7
  94. package/dist/host.d.ts.map +1 -1
  95. package/dist/host.js +83 -41
  96. package/dist/identity.d.ts +0 -1
  97. package/dist/identity.d.ts.map +1 -1
  98. package/dist/identity.js +0 -3
  99. package/dist/index.d.ts +37 -14
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +17 -5
  102. package/dist/install.d.ts +60 -0
  103. package/dist/install.d.ts.map +1 -0
  104. package/dist/install.js +537 -0
  105. package/dist/lifecycle.d.ts +7 -0
  106. package/dist/lifecycle.d.ts.map +1 -0
  107. package/dist/lifecycle.js +282 -0
  108. package/dist/listing.d.ts +52 -0
  109. package/dist/listing.d.ts.map +1 -0
  110. package/dist/listing.js +152 -0
  111. package/dist/live.d.ts +9 -6
  112. package/dist/live.d.ts.map +1 -1
  113. package/dist/live.js +118 -36
  114. package/dist/loaded.d.ts +5 -0
  115. package/dist/loaded.d.ts.map +1 -0
  116. package/dist/loaded.js +40 -0
  117. package/dist/locks.d.ts +7 -0
  118. package/dist/locks.d.ts.map +1 -0
  119. package/dist/locks.js +32 -0
  120. package/dist/lost.d.ts +5 -0
  121. package/dist/lost.d.ts.map +1 -0
  122. package/dist/lost.js +49 -0
  123. package/dist/media.d.ts +19 -2
  124. package/dist/media.d.ts.map +1 -1
  125. package/dist/media.js +105 -27
  126. package/dist/memory.d.ts +18 -6
  127. package/dist/memory.d.ts.map +1 -1
  128. package/dist/memory.js +214 -91
  129. package/dist/messages.d.ts +35 -5
  130. package/dist/messages.d.ts.map +1 -1
  131. package/dist/messages.js +109 -39
  132. package/dist/model.d.ts +2 -10
  133. package/dist/model.d.ts.map +1 -1
  134. package/dist/model.js +0 -14
  135. package/dist/models.d.ts +10 -0
  136. package/dist/models.d.ts.map +1 -0
  137. package/dist/models.js +153 -0
  138. package/dist/observation.d.ts +28 -13
  139. package/dist/observation.d.ts.map +1 -1
  140. package/dist/observation.js +185 -81
  141. package/dist/opened.d.ts +1 -1
  142. package/dist/opened.d.ts.map +1 -1
  143. package/dist/opened.js +5 -5
  144. package/dist/opening.d.ts +4 -0
  145. package/dist/opening.d.ts.map +1 -0
  146. package/dist/opening.js +53 -0
  147. package/dist/outcome.d.ts +17 -2
  148. package/dist/outcome.d.ts.map +1 -1
  149. package/dist/outcome.js +35 -2
  150. package/dist/overlay.d.ts +7 -2
  151. package/dist/overlay.d.ts.map +1 -1
  152. package/dist/overlay.js +48 -24
  153. package/dist/page-metadata.d.ts +8 -0
  154. package/dist/page-metadata.d.ts.map +1 -0
  155. package/dist/page-metadata.js +19 -0
  156. package/dist/parts.d.ts +4 -0
  157. package/dist/parts.d.ts.map +1 -0
  158. package/dist/parts.js +13 -0
  159. package/dist/paths.d.ts +1 -0
  160. package/dist/paths.d.ts.map +1 -1
  161. package/dist/paths.js +22 -0
  162. package/dist/preparation.d.ts +41 -0
  163. package/dist/preparation.d.ts.map +1 -0
  164. package/dist/preparation.js +314 -0
  165. package/dist/presence.d.ts +58 -0
  166. package/dist/presence.d.ts.map +1 -0
  167. package/dist/presence.js +231 -0
  168. package/dist/queries.d.ts +16 -0
  169. package/dist/queries.d.ts.map +1 -0
  170. package/dist/queries.js +308 -0
  171. package/dist/query.d.ts +14 -210
  172. package/dist/query.d.ts.map +1 -1
  173. package/dist/query.js +3 -505
  174. package/dist/questions.d.ts +30 -0
  175. package/dist/questions.d.ts.map +1 -0
  176. package/dist/questions.js +116 -0
  177. package/dist/queue.d.ts +40 -0
  178. package/dist/queue.d.ts.map +1 -0
  179. package/dist/queue.js +480 -0
  180. package/dist/reach.d.ts +16 -15
  181. package/dist/reach.d.ts.map +1 -1
  182. package/dist/reach.js +257 -148
  183. package/dist/react.d.ts +19 -3
  184. package/dist/react.d.ts.map +1 -1
  185. package/dist/react.js +33 -1
  186. package/dist/read.d.ts +7 -0
  187. package/dist/read.d.ts.map +1 -0
  188. package/dist/read.js +17 -0
  189. package/dist/readiness.d.ts +9 -6
  190. package/dist/readiness.d.ts.map +1 -1
  191. package/dist/readiness.js +3 -5
  192. package/dist/reads.d.ts +39 -0
  193. package/dist/reads.d.ts.map +1 -0
  194. package/dist/reads.js +178 -0
  195. package/dist/recovery.d.ts +3 -1
  196. package/dist/recovery.d.ts.map +1 -1
  197. package/dist/recovery.js +38 -8
  198. package/dist/refresh.d.ts +2 -1
  199. package/dist/refresh.d.ts.map +1 -1
  200. package/dist/refresh.js +31 -3
  201. package/dist/refusal.d.ts +5 -0
  202. package/dist/refusal.d.ts.map +1 -0
  203. package/dist/refusal.js +55 -0
  204. package/dist/replica.d.ts +11 -16
  205. package/dist/replica.d.ts.map +1 -1
  206. package/dist/replica.js +66 -32
  207. package/dist/retried.d.ts +30 -0
  208. package/dist/retried.d.ts.map +1 -0
  209. package/dist/retried.js +72 -0
  210. package/dist/room-draft.d.ts +34 -10
  211. package/dist/room-draft.d.ts.map +1 -1
  212. package/dist/room-draft.js +331 -111
  213. package/dist/room-drafts.d.ts +24 -0
  214. package/dist/room-drafts.d.ts.map +1 -0
  215. package/dist/room-drafts.js +220 -0
  216. package/dist/room-list.d.ts +10 -0
  217. package/dist/room-list.d.ts.map +1 -0
  218. package/dist/room-list.js +45 -0
  219. package/dist/rows.d.ts +16 -22
  220. package/dist/rows.d.ts.map +1 -1
  221. package/dist/rows.js +49 -186
  222. package/dist/runtime.d.ts +34 -0
  223. package/dist/runtime.d.ts.map +1 -0
  224. package/dist/runtime.js +1 -0
  225. package/dist/save.d.ts +42 -86
  226. package/dist/save.d.ts.map +1 -1
  227. package/dist/save.js +332 -228
  228. package/dist/saves.d.ts +34 -0
  229. package/dist/saves.d.ts.map +1 -0
  230. package/dist/saves.js +437 -0
  231. package/dist/search.d.ts +12 -63
  232. package/dist/search.d.ts.map +1 -1
  233. package/dist/search.js +9 -52
  234. package/dist/seed.d.ts +11 -2
  235. package/dist/seed.d.ts.map +1 -1
  236. package/dist/seed.js +71 -1
  237. package/dist/shared.d.ts +1 -6
  238. package/dist/shared.d.ts.map +1 -1
  239. package/dist/shared.js +13 -95
  240. package/dist/shown.d.ts +35 -0
  241. package/dist/shown.d.ts.map +1 -0
  242. package/dist/shown.js +313 -0
  243. package/dist/socket/admission.d.ts +7 -0
  244. package/dist/socket/admission.d.ts.map +1 -0
  245. package/dist/socket/admission.js +39 -0
  246. package/dist/socket/answers.d.ts +6 -0
  247. package/dist/socket/answers.d.ts.map +1 -0
  248. package/dist/socket/answers.js +20 -0
  249. package/dist/socket/lanes.d.ts +28 -0
  250. package/dist/socket/lanes.d.ts.map +1 -0
  251. package/dist/socket/lanes.js +103 -0
  252. package/dist/socket/reads.d.ts +10 -0
  253. package/dist/socket/reads.d.ts.map +1 -0
  254. package/dist/socket/reads.js +31 -0
  255. package/dist/socket/session.d.ts +38 -0
  256. package/dist/socket/session.d.ts.map +1 -0
  257. package/dist/socket/session.js +23 -0
  258. package/dist/socket.d.ts +67 -0
  259. package/dist/socket.d.ts.map +1 -0
  260. package/dist/socket.js +164 -0
  261. package/dist/source.d.ts +59 -0
  262. package/dist/source.d.ts.map +1 -0
  263. package/dist/source.js +93 -0
  264. package/dist/status.d.ts +0 -2
  265. package/dist/status.d.ts.map +1 -1
  266. package/dist/status.js +0 -6
  267. package/dist/store/installs.d.ts +30 -0
  268. package/dist/store/installs.d.ts.map +1 -0
  269. package/dist/store/installs.js +40 -0
  270. package/dist/store/readiness.d.ts +11 -0
  271. package/dist/store/readiness.d.ts.map +1 -0
  272. package/dist/store/readiness.js +68 -0
  273. package/dist/store/records.d.ts +224 -0
  274. package/dist/store/records.d.ts.map +1 -0
  275. package/dist/store/records.js +466 -0
  276. package/dist/store/room-held.d.ts +8 -0
  277. package/dist/store/room-held.d.ts.map +1 -0
  278. package/dist/store/room-held.js +17 -0
  279. package/dist/store/rooms-shown.d.ts +8 -0
  280. package/dist/store/rooms-shown.d.ts.map +1 -0
  281. package/dist/store/rooms-shown.js +13 -0
  282. package/dist/store/space.d.ts +11 -0
  283. package/dist/store/space.d.ts.map +1 -0
  284. package/dist/store/space.js +41 -0
  285. package/dist/store/watchers.d.ts +17 -0
  286. package/dist/store/watchers.d.ts.map +1 -0
  287. package/dist/store/watchers.js +56 -0
  288. package/dist/store/writes.d.ts +24 -0
  289. package/dist/store/writes.d.ts.map +1 -0
  290. package/dist/store/writes.js +125 -0
  291. package/dist/store.d.ts +115 -143
  292. package/dist/store.d.ts.map +1 -1
  293. package/dist/store.js +171 -364
  294. package/dist/stored.d.ts +72 -35
  295. package/dist/stored.d.ts.map +1 -1
  296. package/dist/stored.js +337 -197
  297. package/dist/surface.d.ts +92 -0
  298. package/dist/surface.d.ts.map +1 -0
  299. package/dist/surface.js +251 -0
  300. package/dist/sync.d.ts +19 -0
  301. package/dist/sync.d.ts.map +1 -0
  302. package/dist/sync.js +538 -0
  303. package/dist/tables.d.ts +118 -67
  304. package/dist/tables.d.ts.map +1 -1
  305. package/dist/tables.js +199 -278
  306. package/dist/tallies.d.ts +18 -0
  307. package/dist/tallies.d.ts.map +1 -0
  308. package/dist/tallies.js +128 -0
  309. package/dist/validation.d.ts.map +1 -1
  310. package/dist/validation.js +9 -12
  311. package/dist/windows.d.ts +1 -12
  312. package/dist/windows.d.ts.map +1 -1
  313. package/dist/windows.js +2 -75
  314. package/dist/wire.d.ts +72 -9
  315. package/dist/wire.d.ts.map +1 -1
  316. package/dist/wire.js +26 -5
  317. package/package.json +3 -2
  318. package/dist/attempt.d.ts +0 -16
  319. package/dist/attempt.d.ts.map +0 -1
  320. package/dist/attempt.js +0 -70
  321. package/dist/fence.d.ts +0 -6
  322. package/dist/fence.d.ts.map +0 -1
  323. package/dist/fence.js +0 -10
  324. package/dist/fulltext.d.ts +0 -27
  325. package/dist/fulltext.d.ts.map +0 -1
  326. package/dist/fulltext.js +0 -370
  327. package/dist/log.d.ts +0 -5
  328. package/dist/log.d.ts.map +0 -1
  329. package/dist/log.js +0 -14
  330. package/dist/words.d.ts +0 -19
  331. package/dist/words.d.ts.map +0 -1
  332. package/dist/words.js +0 -156
package/dist/live.js CHANGED
@@ -1,50 +1,59 @@
1
- import { changeOf, digestOf, } from './observation.js';
2
- import { roomOfKey } from './rows.js';
1
+ import { changeOf, digestOf, keyedAt, keyedInto, samePending, unchanged, } from './observation.js';
2
+ import { uncaught } from './failure.js';
3
+ import { roomsOfReach } from './rows.js';
3
4
  function narrowed(plan) {
4
5
  return (plan.paths !== null &&
5
6
  plan.hop === null &&
6
7
  plan.backlinks === null &&
7
8
  !(plan.shape.kind === 'documents' && plan.shape.backlinks));
8
9
  }
9
- function reads(plan, locator) {
10
- if (plan.locked.includes(locator))
11
- return false;
12
- if (plan.rooms !== null && !plan.rooms.includes(locator))
13
- return false;
14
- if (plan.reach === null)
15
- return true;
16
- return (plan.reach.rooms.includes(locator) ||
17
- plan.reach.documents.some((document) => roomOfKey(document) === locator) ||
18
- plan.reach.areas.some((area) => area.room === locator && area.types.length > 0));
10
+ export function readerOf(plan) {
11
+ const locked = new Set(plan.locked);
12
+ const named = plan.rooms === null ? null : new Set(plan.rooms);
13
+ const reached = plan.reach === null ? null : roomsOfReach(plan.reach);
14
+ return (locator) => !locked.has(locator) &&
15
+ (named === null || named.has(locator)) &&
16
+ (reached === null || reached.has(locator));
19
17
  }
20
- function affected(plan, set) {
21
- if (set.rooms.some((locator) => reads(plan, locator)))
18
+ function affected({ plan, reads }, set) {
19
+ if (set.rooms.some(reads))
22
20
  return true;
23
21
  const paths = narrowed(plan) ? new Set(plan.paths) : null;
24
- return set.documents.some(({ locator, path }) => reads(plan, locator) && (paths === null || path === null || paths.has(path)));
22
+ return set.documents.some(({ locator, path }) => reads(locator) && (paths === null || path === null || paths.has(path)));
25
23
  }
26
- function shown(entry) {
27
- const cover = entry.cover();
24
+ function shown(entry, held) {
25
+ const cover = entry.cover(held);
28
26
  return JSON.stringify('pending' in cover ? cover : cover.coverage);
29
27
  }
30
- export function createLiveQueries(run) {
28
+ export function createLiveQueries(run, holding) {
31
29
  const entries = new Map();
32
30
  let closed = false;
31
+ let recoveringNow = false;
32
+ let recoveringAll = false;
33
+ const recovering = new Set();
33
34
  function deliver(entry, next) {
34
35
  const previous = entry.digest;
35
36
  entry.digest = digestOf(next);
37
+ entry.pending = 'pending' in entry.digest ? entry.digest.pending : null;
36
38
  entry.covered = JSON.stringify('pending' in next.result ? next.result : next.result.coverage);
37
39
  const change = previous === undefined ? null : changeOf(previous, next);
38
40
  if (change !== null) {
39
41
  for (const listener of entry.ready)
40
42
  listener(change);
41
43
  }
42
- for (const listener of entry.fresh) {
44
+ for (const listener of entry.fresh.keys()) {
43
45
  entry.ready.add(listener);
44
46
  listener(next);
45
47
  }
46
48
  entry.fresh.clear();
47
49
  }
50
+ async function delivered(entry) {
51
+ const { current, ...next } = await run(entry.plan, async () => entry.cover(await holding()));
52
+ if (closed || entry.listeners.size === 0)
53
+ return null;
54
+ deliver(entry, next);
55
+ return current;
56
+ }
48
57
  function schedule(entry) {
49
58
  if (closed || entry.listeners.size === 0)
50
59
  return;
@@ -57,47 +66,120 @@ export function createLiveQueries(run) {
57
66
  try {
58
67
  for (;;) {
59
68
  entry.dirty = false;
60
- const { current, ...next } = await run(entry.plan, entry.cover);
61
- if (closed || entry.listeners.size === 0)
69
+ const current = await delivered(entry);
70
+ if (current === null)
62
71
  return;
63
- deliver(entry, next);
64
- if (!(entry.dirty && !current()) && shown(entry) === entry.covered)
72
+ const held = await holding();
73
+ if (entry.fresh.size > 0 || (entry.dirty && !current()))
74
+ continue;
75
+ if (shown(entry, held) === entry.covered)
65
76
  break;
66
77
  }
67
78
  }
68
- catch {
69
- entry.dirty = false;
79
+ catch (error) {
80
+ const unanswered = [...entry.fresh.values()];
81
+ entry.fresh.clear();
82
+ for (const failed of unanswered)
83
+ failed(error);
70
84
  }
71
85
  finally {
72
86
  entry.running = false;
73
87
  }
74
88
  })();
75
89
  }
90
+ function pendingMoved(entry, held, rooms) {
91
+ const { pending } = entry;
92
+ if (pending === null || pending.size === 0 || entry.running || entry.fresh.size > 0) {
93
+ return false;
94
+ }
95
+ const at = entry.pendingAt(held, rooms);
96
+ const change = at === null ? null : keyedAt(pending, at, samePending);
97
+ if (change === null || change.removed.length === pending.size)
98
+ return false;
99
+ keyedInto(pending, change);
100
+ entry.covered = '';
101
+ if (unchanged(change))
102
+ return true;
103
+ for (const listener of entry.ready)
104
+ listener({ pending: change });
105
+ return true;
106
+ }
107
+ function recovered(locators) {
108
+ if (locators === undefined)
109
+ recoveringAll = true;
110
+ else
111
+ for (const locator of locators)
112
+ recovering.add(locator);
113
+ if (recoveringNow)
114
+ return;
115
+ recoveringNow = true;
116
+ void (async () => {
117
+ try {
118
+ while ((recoveringAll || recovering.size > 0) && !closed) {
119
+ const all = recoveringAll;
120
+ const touched = [...recovering];
121
+ recoveringAll = false;
122
+ recovering.clear();
123
+ const reached = [...entries.values()].filter((entry) => all || touched.some(entry.reads));
124
+ for (const entry of reached) {
125
+ if (entry.running)
126
+ entry.dirty = true;
127
+ }
128
+ const waiting = reached.filter((entry) => !entry.running);
129
+ if (waiting.length === 0)
130
+ continue;
131
+ const held = await holding();
132
+ for (const entry of waiting) {
133
+ if (entry.running)
134
+ continue;
135
+ const read = all ? [] : touched.filter(entry.reads);
136
+ if (read.length > 0 && pendingMoved(entry, held, read))
137
+ continue;
138
+ if (entry.digest === undefined || shown(entry, held) !== entry.covered)
139
+ schedule(entry);
140
+ }
141
+ }
142
+ }
143
+ catch (error) {
144
+ uncaught(error);
145
+ }
146
+ finally {
147
+ recoveringNow = false;
148
+ }
149
+ })();
150
+ }
76
151
  return {
77
152
  touched(set) {
153
+ const pending = new Set();
78
154
  for (const entry of entries.values()) {
79
- if (affected(entry.plan, set))
80
- schedule(entry);
81
- }
82
- },
83
- recover() {
84
- for (const entry of entries.values()) {
85
- if (entry.running)
155
+ if (!affected(entry, set))
86
156
  continue;
87
- if (entry.digest === undefined || shown(entry) !== entry.covered)
157
+ if (entry.pending === null || entry.running) {
88
158
  schedule(entry);
159
+ continue;
160
+ }
161
+ for (const locator of set.rooms)
162
+ pending.add(locator);
163
+ for (const { locator } of set.documents)
164
+ pending.add(locator);
89
165
  }
166
+ if (pending.size > 0)
167
+ recovered([...pending]);
90
168
  },
91
- watch(plan, identity, cover, listener) {
169
+ recover: recovered,
170
+ watch(plan, identity, cover, pendingAt, listener, failed) {
92
171
  let entry = entries.get(identity);
93
172
  if (entry === undefined) {
94
173
  entry = {
95
174
  plan,
175
+ reads: readerOf(plan),
96
176
  cover,
177
+ pendingAt,
97
178
  listeners: new Set(),
98
- fresh: new Set(),
179
+ fresh: new Map(),
99
180
  ready: new Set(),
100
181
  digest: undefined,
182
+ pending: null,
101
183
  covered: '',
102
184
  dirty: false,
103
185
  running: false,
@@ -106,7 +188,7 @@ export function createLiveQueries(run) {
106
188
  }
107
189
  const held = entry;
108
190
  held.listeners.add(listener);
109
- held.fresh.add(listener);
191
+ held.fresh.set(listener, failed);
110
192
  schedule(held);
111
193
  return () => {
112
194
  held.listeners.delete(listener);
@@ -0,0 +1,5 @@
1
+ import type { BuildFile } from './client.js';
2
+ export declare function parsed(file: Blob | null): Promise<unknown>;
3
+ export declare function perBuild<T>(buildFile: BuildFile, key: string, make: () => Promise<T>): Promise<T>;
4
+ export declare function loaded<T>(buildFile: BuildFile, name: string, read?: (file: Blob | null) => Promise<unknown>): Promise<T | null>;
5
+ //# sourceMappingURL=loaded.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loaded.d.ts","sourceRoot":"","sources":["../src/loaded.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAc7C,wBAAsB,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAEhE;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAYjG;AAED,wBAAgB,MAAM,CAAC,CAAC,EACtB,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,OAAO,CAAC,OAAO,CAAY,GACvD,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAEnB"}
package/dist/loaded.js ADDED
@@ -0,0 +1,40 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ const holdings = new WeakMap();
10
+ async function imported(file) {
11
+ if (file === null)
12
+ return null;
13
+ const address = URL.createObjectURL(file);
14
+ try {
15
+ return await import(__rewriteRelativeImportExtension(address));
16
+ }
17
+ finally {
18
+ URL.revokeObjectURL(address);
19
+ }
20
+ }
21
+ export async function parsed(file) {
22
+ return file === null ? null : JSON.parse(await file.text());
23
+ }
24
+ export function perBuild(buildFile, key, make) {
25
+ let named = holdings.get(buildFile);
26
+ if (named === undefined) {
27
+ named = new Map();
28
+ holdings.set(buildFile, named);
29
+ }
30
+ const holding = named.get(key);
31
+ if (holding !== undefined)
32
+ return holding;
33
+ const making = make();
34
+ named.set(key, making);
35
+ making.catch(() => named.delete(key));
36
+ return making;
37
+ }
38
+ export function loaded(buildFile, name, read = imported) {
39
+ return perBuild(buildFile, name, () => buildFile(name).then(read));
40
+ }
@@ -0,0 +1,7 @@
1
+ import type { Runtime } from './runtime.js';
2
+ export declare function locked({ store, presence }: Runtime, locator: string): Promise<boolean>;
3
+ export declare function lockedRooms({ store, presence }: Runtime): Promise<readonly string[]>;
4
+ export declare function relocked(runtime: Runtime, before: readonly string[]): Promise<void>;
5
+ export declare function connectionLost(runtime: Runtime): void;
6
+ export declare function watchLocks({ store }: Runtime, notify: () => void): () => void;
7
+ //# sourceMappingURL=locks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locks.d.ts","sourceRoot":"","sources":["../src/locks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG5C,wBAAsB,MAAM,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAE5F;AAED,wBAAsB,WAAW,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAG1F;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,iBAKzE;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,QAU9C;AAED,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAE7E"}
package/dist/locks.js ADDED
@@ -0,0 +1,32 @@
1
+ import { uncaught } from './failure.js';
2
+ import { emitRooms, LOCKS_TOPIC } from './shown.js';
3
+ export async function locked({ store, presence }, locator) {
4
+ return presence.locked(locator, async () => (await store.open()).offline(locator));
5
+ }
6
+ export async function lockedRooms({ store, presence }) {
7
+ const { rooms, locking } = await store.covering();
8
+ return presence.lockedAmong(rooms, locking);
9
+ }
10
+ export async function relocked(runtime, before) {
11
+ const after = await lockedRooms(runtime);
12
+ if (JSON.stringify(after) === JSON.stringify(before))
13
+ return;
14
+ emitRooms(runtime);
15
+ runtime.store.tell(LOCKS_TOPIC, undefined);
16
+ }
17
+ export function connectionLost(runtime) {
18
+ const { socket, presence } = runtime;
19
+ if (socket.closed())
20
+ return;
21
+ presence.connectionLost(() => {
22
+ if (socket.isOpen() || socket.closed())
23
+ return;
24
+ void lockedRooms(runtime).then((before) => {
25
+ presence.lapse();
26
+ return relocked(runtime, before);
27
+ }, uncaught);
28
+ });
29
+ }
30
+ export function watchLocks({ store }, notify) {
31
+ return store.watch(LOCKS_TOPIC, notify).stop;
32
+ }
package/dist/lost.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { Runtime } from './runtime.js';
2
+ export declare function eraseFenced({ store }: Runtime, locators: readonly string[]): Promise<void>;
3
+ export declare function fenceLive(runtime: Runtime, locators: readonly string[]): void;
4
+ export declare function loseRooms(runtime: Runtime, locators: readonly string[]): Promise<void>;
5
+ //# sourceMappingURL=lost.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lost.d.ts","sourceRoot":"","sources":["../src/lost.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAK5C,wBAAsB,WAAW,CAAC,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,iBAIhF;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,QAMtE;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,iBA+B5E"}
package/dist/lost.js ADDED
@@ -0,0 +1,49 @@
1
+ import { changeSet, mergeChangeSets } from './change-set.js';
2
+ import { ERASE_BATCH } from './install.js';
3
+ import { unpair } from './queue.js';
4
+ import { recoverReads } from './shown.js';
5
+ import { yielded } from './turn.js';
6
+ export async function eraseFenced({ store }, locators) {
7
+ for (const locator of locators) {
8
+ while (await store.durably((local) => local.erase(locator, ERASE_BATCH)))
9
+ await yielded();
10
+ }
11
+ }
12
+ export function fenceLive(runtime, locators) {
13
+ const { store, socket, presence } = runtime;
14
+ for (const locator of new Set(locators)) {
15
+ if (!presence.lose(locator, socket.session.now().discoveries))
16
+ continue;
17
+ store.forget(locator);
18
+ }
19
+ }
20
+ export async function loseRooms(runtime, locators) {
21
+ const { store, presence } = runtime;
22
+ fenceLive(runtime, locators);
23
+ const fresh = [...new Set(locators)].filter((locator) => presence.writing(locator));
24
+ if (fresh.length === 0)
25
+ return;
26
+ let held = [];
27
+ try {
28
+ await store.durably(async (local) => {
29
+ held = await local.roomDrafts();
30
+ await local.fence(fresh);
31
+ });
32
+ }
33
+ catch (error) {
34
+ for (const locator of fresh)
35
+ presence.unwritten(locator);
36
+ throw error;
37
+ }
38
+ finally {
39
+ store.changed(mergeChangeSets(fresh.map((locator) => changeSet(locator, {
40
+ documents: null,
41
+ room: true,
42
+ queue: true,
43
+ drafts: held.filter((draft) => draft.locator === locator).map(({ path }) => path),
44
+ }))));
45
+ recoverReads(runtime, fresh);
46
+ }
47
+ await unpair(runtime);
48
+ await eraseFenced(runtime, fresh);
49
+ }
package/dist/media.d.ts CHANGED
@@ -1,8 +1,25 @@
1
1
  import type { MdRuntimeClient } from './client.js';
2
- import { type ShownAnswer } from './outcome.js';
2
+ import { type DownloadAnswer, type ShownAnswer } from './outcome.js';
3
3
  export declare const ROOM_MEDIA = "room-media";
4
+ export declare const KEYED_MEDIA = "ahc-attachment";
5
+ export declare const MEDIA = "room-media, ahc-attachment";
6
+ export declare const DIAGRAM = "md-diagram";
7
+ export declare const VIEWING = "ahc-viewer";
4
8
  type Show = (room: string, path: string, revision?: string) => Promise<ShownAnswer>;
9
+ export type MediaAt = {
10
+ readonly link: string;
11
+ } | {
12
+ readonly room: string;
13
+ readonly path: string;
14
+ readonly revision: string | undefined;
15
+ };
5
16
  export declare function shownBy(show: Show, client: MdRuntimeClient): MdRuntimeClient;
6
- export declare function defineRoomMedia(client: Promise<MdRuntimeClient> | MdRuntimeClient, registry?: CustomElementRegistry): void;
17
+ export declare function mediaAt(media: Element): MediaAt | null;
18
+ export declare function mediaTag(at: MediaAt): string;
19
+ export declare function downloadAt(client: MdRuntimeClient, at: MediaAt): Promise<DownloadAnswer>;
20
+ export declare function rendered(element: Element): boolean;
21
+ export declare function viewable(element: Element): boolean;
22
+ export declare function viewFile(client: MdRuntimeClient, media: Element): void;
23
+ export declare function defineRoomMedia(client: Promise<MdRuntimeClient> | MdRuntimeClient): void;
7
24
  export {};
8
25
  //# sourceMappingURL=media.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3D,eAAO,MAAM,UAAU,eAAe,CAAC;AAwBvC,KAAK,IAAI,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;AAIpF,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,GAAG,eAAe,CAG5E;AAsBD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe,EAClD,QAAQ,GAAE,qBAAsC,GAC/C,IAAI,CA0LN"}
1
+ {"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAqB,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAExF,eAAO,MAAM,UAAU,eAAe,CAAC;AAEvC,eAAO,MAAM,WAAW,mBAAmB,CAAC;AAE5C,eAAO,MAAM,KAAK,+BAAkC,CAAC;AAMrD,eAAO,MAAM,OAAO,eAAe,CAAC;AAEpC,eAAO,MAAM,OAAO,eAAe,CAAC;AAgCpC,KAAK,IAAI,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;AAEpF,MAAM,MAAM,OAAO,GACf;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAI5F,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,GAAG,eAAe,CAG5E;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAUtD;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,CAE5C;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,eAAe,EAAE,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAIxF;AA6BD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAElD;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAMlD;AA4BD,wBAAgB,QAAQ,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAEtE;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe,GAAG,IAAI,CAgMxF"}
package/dist/media.js CHANGED
@@ -1,7 +1,18 @@
1
- import { UNCERTAIN } from './outcome.js';
1
+ import { isAudioType, isImageType, isVideoType } from '@agentshouse/mdmodel';
2
+ import { loaded } from './loaded.js';
3
+ import { downloadAttachment } from './download.js';
4
+ import { blobOf, UNCERTAIN } from './outcome.js';
2
5
  export const ROOM_MEDIA = 'room-media';
6
+ export const KEYED_MEDIA = 'ahc-attachment';
7
+ export const MEDIA = `${ROOM_MEDIA}, ${KEYED_MEDIA}`;
8
+ const VIEWER = 'viewer';
9
+ const RENDERED = '.ahc, .ahc-markdown';
10
+ export const DIAGRAM = 'md-diagram';
11
+ export const VIEWING = 'ahc-viewer';
12
+ const PRESSED_ELSEWHERE = 'a[href], button, input, select, textarea, summary, label, [role="button"], [role="link"]';
3
13
  const ATTRIBUTES = ['room', 'path', 'revision', 'width', 'height', 'alt'];
4
- const LOADING = new Set(['room', 'path', 'revision']);
14
+ const KEYED_ATTRIBUTES = ['link', 'alt'];
15
+ const LOADING = new Set(['link', 'room', 'path', 'revision']);
5
16
  const NEARING = '600px 0px';
6
17
  const RELINKS = 2;
7
18
  // The Room's stream brings a file seconds after the text that links it, minutes after on a first open, and a page's reach of its Rooms settles seconds after it opens, so a file not shown yet is asked for again every three seconds.
@@ -10,12 +21,42 @@ const SHAPE = '16 / 9';
10
21
  const STYLE = `:host{display:block}
11
22
  [part=box]{position:relative;overflow:hidden}
12
23
  [part=box]>img,[part=box]>video,[part=box]>audio{display:block;width:100%;height:100%;object-fit:contain}
13
- [part=box]>slot{display:flex;align-items:center;justify-content:center;width:100%;height:100%;text-align:center}`;
24
+ [part=box]>video:not([controls]){cursor:pointer}
25
+ [part=box]>slot{display:flex;align-items:center;justify-content:center;width:100%;height:100%;text-align:center}
26
+ [part=play]{position:absolute;top:50%;left:50%;width:56px;height:56px;margin:-28px 0 0 -28px;border-radius:50%;background:rgb(0 0 0/.6);pointer-events:none}
27
+ [part=play]::before{content:"";position:absolute;top:18px;left:22px;border-style:solid;border-width:10px 0 10px 16px;border-color:transparent transparent transparent #fff}`;
14
28
  const shows = new WeakMap();
15
29
  export function shownBy(show, client) {
16
30
  shows.set(client, show);
17
31
  return client;
18
32
  }
33
+ export function mediaAt(media) {
34
+ if (media.localName === KEYED_MEDIA) {
35
+ const link = media.getAttribute('link');
36
+ return link === null ? null : { link };
37
+ }
38
+ const room = media.getAttribute('room');
39
+ const path = media.getAttribute('path');
40
+ return room === null || path === null
41
+ ? null
42
+ : { room, path, revision: media.getAttribute('revision') ?? undefined };
43
+ }
44
+ export function mediaTag(at) {
45
+ return 'link' in at ? KEYED_MEDIA : ROOM_MEDIA;
46
+ }
47
+ export function downloadAt(client, at) {
48
+ return 'link' in at
49
+ ? downloadAttachment(client, at.link)
50
+ : client.binaryFile.download(at.room, at.path, at.revision);
51
+ }
52
+ function shownAt(client, at) {
53
+ if (at === null)
54
+ return null;
55
+ if ('link' in at)
56
+ return client.conversation.attachment(at.link);
57
+ const show = shows.get(client);
58
+ return show === undefined ? null : show(at.room, at.path, at.revision);
59
+ }
19
60
  function ratioOf(width, height) {
20
61
  const across = Number(width);
21
62
  const down = Number(height);
@@ -29,23 +70,62 @@ function refusedForGood(shown) {
29
70
  return (shown.kind === 'rejected' || shown.kind === 'scoped-refusal') && !shown.retryable;
30
71
  }
31
72
  function drawnFor(mediaType) {
32
- if (mediaType.startsWith('image/'))
73
+ if (isImageType(mediaType))
33
74
  return 'img';
34
- if (mediaType.startsWith('video/'))
75
+ if (isVideoType(mediaType))
35
76
  return 'video';
36
- if (mediaType.startsWith('audio/'))
77
+ if (isAudioType(mediaType))
37
78
  return 'audio';
38
79
  return null;
39
80
  }
40
- export function defineRoomMedia(client, registry = customElements) {
41
- if (registry.get(ROOM_MEDIA) !== undefined)
81
+ export function rendered(element) {
82
+ return element.closest(RENDERED) !== null;
83
+ }
84
+ export function viewable(element) {
85
+ if (element.localName === 'figure')
86
+ return element.classList.contains(DIAGRAM);
87
+ if (!element.matches(MEDIA) || element.closest(`.${VIEWING}`) !== null)
88
+ return false;
89
+ return (rendered(element) || (element.shadowRoot?.querySelector('[part=box] > video') ?? null) !== null);
90
+ }
91
+ function viewed(client, opener, alone) {
92
+ void loaded(client.buildFile, VIEWER).then((file) => file?.openViewer({
93
+ opener,
94
+ alone,
95
+ download: (at) => void downloadAt(client, at),
96
+ }));
97
+ }
98
+ function pressed(client) {
99
+ return (event) => {
100
+ if (event.defaultPrevented || event.detail > 1)
101
+ return;
102
+ for (const node of event.composedPath()) {
103
+ if (!(node instanceof Element))
104
+ continue;
105
+ if (viewable(node)) {
106
+ event.preventDefault();
107
+ void client.then((one) => viewed(one, node, !rendered(node)));
108
+ return;
109
+ }
110
+ if (node.matches(PRESSED_ELSEWHERE))
111
+ return;
112
+ }
113
+ };
114
+ }
115
+ export function viewFile(client, media) {
116
+ viewed(client, media, true);
117
+ }
118
+ export function defineRoomMedia(client) {
119
+ if (customElements.get(ROOM_MEDIA) !== undefined)
42
120
  return;
43
121
  const connected = Promise.resolve(client);
122
+ document.addEventListener('click', pressed(connected));
44
123
  class RoomMedia extends HTMLElement {
45
124
  static observedAttributes = ATTRIBUTES;
46
125
  #root = this.attachShadow({ mode: 'open' });
47
126
  #box = document.createElement('div');
48
127
  #fallback = document.createElement('slot');
128
+ #play = document.createElement('span');
49
129
  #watching = null;
50
130
  #near = false;
51
131
  #asked = 0;
@@ -56,6 +136,7 @@ export function defineRoomMedia(client, registry = customElements) {
56
136
  constructor() {
57
137
  super();
58
138
  this.#box.setAttribute('part', 'box');
139
+ this.#play.setAttribute('part', 'play');
59
140
  this.#root.append(Object.assign(document.createElement('style'), { textContent: STYLE }), this.#box);
60
141
  this.#shape();
61
142
  }
@@ -107,15 +188,8 @@ export function defineRoomMedia(client, registry = customElements) {
107
188
  async #load() {
108
189
  const asked = (this.#asked += 1);
109
190
  this.#loaded = false;
110
- const room = this.getAttribute('room');
111
- const path = this.getAttribute('path');
112
- const revision = this.getAttribute('revision') ?? undefined;
113
- if (room === null || path === null) {
114
- this.#unreadable();
115
- return;
116
- }
117
- const show = shows.get(await connected);
118
- const shown = show === undefined ? null : await show(room, path, revision).catch(() => UNCERTAIN);
191
+ const showing = shownAt(await connected, mediaAt(this));
192
+ const shown = showing === null ? null : await showing.catch(() => UNCERTAIN);
119
193
  if (asked !== this.#asked)
120
194
  return;
121
195
  if (shown?.kind === 'link') {
@@ -129,11 +203,7 @@ export function defineRoomMedia(client, registry = customElements) {
129
203
  this.#unshown(asked);
130
204
  return;
131
205
  }
132
- const copy = await new Response(shown.stream, {
133
- headers: { 'content-type': shown.mediaType },
134
- })
135
- .blob()
136
- .catch(() => null);
206
+ const copy = await blobOf(shown).catch(() => null);
137
207
  if (asked !== this.#asked)
138
208
  return;
139
209
  if (copy === null)
@@ -157,7 +227,9 @@ export function defineRoomMedia(client, registry = customElements) {
157
227
  if (content === null)
158
228
  this.#box.replaceChildren();
159
229
  else if (content.parentNode !== this.#box)
160
- this.#box.replaceChildren(content);
230
+ this.#box.replaceChildren(...(content instanceof HTMLVideoElement && !content.controls
231
+ ? [content, this.#play]
232
+ : [content]));
161
233
  this.#label();
162
234
  }
163
235
  #unreadable() {
@@ -174,13 +246,16 @@ export function defineRoomMedia(client, registry = customElements) {
174
246
  const kept = this.#drawn?.localName === tag ? this.#drawn : null;
175
247
  const drawn = kept ?? document.createElement(tag);
176
248
  if (kept === null) {
249
+ const plays = !(drawn instanceof HTMLVideoElement) || this.closest(`.${VIEWING}`) !== null;
177
250
  if (drawn instanceof HTMLImageElement)
178
251
  drawn.decoding = 'async';
179
252
  if (drawn instanceof HTMLMediaElement) {
180
- drawn.controls = true;
181
253
  drawn.preload = 'metadata';
182
- if (drawn instanceof HTMLVideoElement)
183
- drawn.playsInline = true;
254
+ drawn.controls = plays;
255
+ }
256
+ if (drawn instanceof HTMLVideoElement && plays) {
257
+ drawn.autoplay = true;
258
+ drawn.setAttribute('playsinline', '');
184
259
  }
185
260
  drawn.addEventListener(drawn instanceof HTMLImageElement ? 'load' : 'loadedmetadata', () => {
186
261
  if (drawn !== this.#drawn)
@@ -214,5 +289,8 @@ export function defineRoomMedia(client, registry = customElements) {
214
289
  void this.#load();
215
290
  }
216
291
  }
217
- registry.define(ROOM_MEDIA, RoomMedia);
292
+ customElements.define(ROOM_MEDIA, RoomMedia);
293
+ customElements.define(KEYED_MEDIA, class extends RoomMedia {
294
+ static observedAttributes = KEYED_ATTRIBUTES;
295
+ });
218
296
  }