@ifc-lite/collab 0.2.0

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 (219) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +92 -0
  3. package/dist/awareness/agent.d.ts +36 -0
  4. package/dist/awareness/agent.d.ts.map +1 -0
  5. package/dist/awareness/agent.js +39 -0
  6. package/dist/awareness/agent.js.map +1 -0
  7. package/dist/awareness/color.d.ts +31 -0
  8. package/dist/awareness/color.d.ts.map +1 -0
  9. package/dist/awareness/color.js +61 -0
  10. package/dist/awareness/color.js.map +1 -0
  11. package/dist/awareness/index.d.ts +6 -0
  12. package/dist/awareness/index.d.ts.map +1 -0
  13. package/dist/awareness/index.js +9 -0
  14. package/dist/awareness/index.js.map +1 -0
  15. package/dist/awareness/overlay.d.ts +59 -0
  16. package/dist/awareness/overlay.d.ts.map +1 -0
  17. package/dist/awareness/overlay.js +110 -0
  18. package/dist/awareness/overlay.js.map +1 -0
  19. package/dist/awareness/presence.d.ts +95 -0
  20. package/dist/awareness/presence.d.ts.map +1 -0
  21. package/dist/awareness/presence.js +114 -0
  22. package/dist/awareness/presence.js.map +1 -0
  23. package/dist/awareness/render.d.ts +58 -0
  24. package/dist/awareness/render.d.ts.map +1 -0
  25. package/dist/awareness/render.js +66 -0
  26. package/dist/awareness/render.js.map +1 -0
  27. package/dist/branch/branch-tree.d.ts +40 -0
  28. package/dist/branch/branch-tree.d.ts.map +1 -0
  29. package/dist/branch/branch-tree.js +66 -0
  30. package/dist/branch/branch-tree.js.map +1 -0
  31. package/dist/branch/branch.d.ts +44 -0
  32. package/dist/branch/branch.d.ts.map +1 -0
  33. package/dist/branch/branch.js +109 -0
  34. package/dist/branch/branch.js.map +1 -0
  35. package/dist/branch/history-automerge.d.ts +31 -0
  36. package/dist/branch/history-automerge.d.ts.map +1 -0
  37. package/dist/branch/history-automerge.js +237 -0
  38. package/dist/branch/history-automerge.js.map +1 -0
  39. package/dist/branch/history.d.ts +147 -0
  40. package/dist/branch/history.d.ts.map +1 -0
  41. package/dist/branch/history.js +223 -0
  42. package/dist/branch/history.js.map +1 -0
  43. package/dist/branch/index.d.ts +5 -0
  44. package/dist/branch/index.d.ts.map +1 -0
  45. package/dist/branch/index.js +8 -0
  46. package/dist/branch/index.js.map +1 -0
  47. package/dist/conflicts/detector.d.ts +52 -0
  48. package/dist/conflicts/detector.d.ts.map +1 -0
  49. package/dist/conflicts/detector.js +226 -0
  50. package/dist/conflicts/detector.js.map +1 -0
  51. package/dist/conflicts/index.d.ts +3 -0
  52. package/dist/conflicts/index.d.ts.map +1 -0
  53. package/dist/conflicts/index.js +6 -0
  54. package/dist/conflicts/index.js.map +1 -0
  55. package/dist/conflicts/ui-bridge.d.ts +80 -0
  56. package/dist/conflicts/ui-bridge.d.ts.map +1 -0
  57. package/dist/conflicts/ui-bridge.js +126 -0
  58. package/dist/conflicts/ui-bridge.js.map +1 -0
  59. package/dist/doc/entity.d.ts +84 -0
  60. package/dist/doc/entity.d.ts.map +1 -0
  61. package/dist/doc/entity.js +345 -0
  62. package/dist/doc/entity.js.map +1 -0
  63. package/dist/doc/geometry.d.ts +39 -0
  64. package/dist/doc/geometry.d.ts.map +1 -0
  65. package/dist/doc/geometry.js +99 -0
  66. package/dist/doc/geometry.js.map +1 -0
  67. package/dist/doc/index.d.ts +8 -0
  68. package/dist/doc/index.d.ts.map +1 -0
  69. package/dist/doc/index.js +11 -0
  70. package/dist/doc/index.js.map +1 -0
  71. package/dist/doc/migration-ifc4-to-ifc4x3.d.ts +21 -0
  72. package/dist/doc/migration-ifc4-to-ifc4x3.d.ts.map +1 -0
  73. package/dist/doc/migration-ifc4-to-ifc4x3.js +55 -0
  74. package/dist/doc/migration-ifc4-to-ifc4x3.js.map +1 -0
  75. package/dist/doc/relationship.d.ts +27 -0
  76. package/dist/doc/relationship.d.ts.map +1 -0
  77. package/dist/doc/relationship.js +110 -0
  78. package/dist/doc/relationship.js.map +1 -0
  79. package/dist/doc/schema-version.d.ts +47 -0
  80. package/dist/doc/schema-version.d.ts.map +1 -0
  81. package/dist/doc/schema-version.js +41 -0
  82. package/dist/doc/schema-version.js.map +1 -0
  83. package/dist/doc/schema.d.ts +131 -0
  84. package/dist/doc/schema.d.ts.map +1 -0
  85. package/dist/doc/schema.js +117 -0
  86. package/dist/doc/schema.js.map +1 -0
  87. package/dist/doc/units.d.ts +45 -0
  88. package/dist/doc/units.d.ts.map +1 -0
  89. package/dist/doc/units.js +120 -0
  90. package/dist/doc/units.js.map +1 -0
  91. package/dist/federation/bridge.d.ts +34 -0
  92. package/dist/federation/bridge.d.ts.map +1 -0
  93. package/dist/federation/bridge.js +52 -0
  94. package/dist/federation/bridge.js.map +1 -0
  95. package/dist/federation/index.d.ts +4 -0
  96. package/dist/federation/index.d.ts.map +1 -0
  97. package/dist/federation/index.js +7 -0
  98. package/dist/federation/index.js.map +1 -0
  99. package/dist/federation/resolver.d.ts +58 -0
  100. package/dist/federation/resolver.d.ts.map +1 -0
  101. package/dist/federation/resolver.js +43 -0
  102. package/dist/federation/resolver.js.map +1 -0
  103. package/dist/federation/session.d.ts +79 -0
  104. package/dist/federation/session.d.ts.map +1 -0
  105. package/dist/federation/session.js +126 -0
  106. package/dist/federation/session.js.map +1 -0
  107. package/dist/geometry/blob-store.d.ts +106 -0
  108. package/dist/geometry/blob-store.d.ts.map +1 -0
  109. package/dist/geometry/blob-store.js +266 -0
  110. package/dist/geometry/blob-store.js.map +1 -0
  111. package/dist/geometry/csg.d.ts +63 -0
  112. package/dist/geometry/csg.d.ts.map +1 -0
  113. package/dist/geometry/csg.js +101 -0
  114. package/dist/geometry/csg.js.map +1 -0
  115. package/dist/geometry/determinism.d.ts +45 -0
  116. package/dist/geometry/determinism.d.ts.map +1 -0
  117. package/dist/geometry/determinism.js +92 -0
  118. package/dist/geometry/determinism.js.map +1 -0
  119. package/dist/geometry/gc.d.ts +63 -0
  120. package/dist/geometry/gc.d.ts.map +1 -0
  121. package/dist/geometry/gc.js +109 -0
  122. package/dist/geometry/gc.js.map +1 -0
  123. package/dist/geometry/index.d.ts +6 -0
  124. package/dist/geometry/index.d.ts.map +1 -0
  125. package/dist/geometry/index.js +9 -0
  126. package/dist/geometry/index.js.map +1 -0
  127. package/dist/geometry/parametric.d.ts +92 -0
  128. package/dist/geometry/parametric.d.ts.map +1 -0
  129. package/dist/geometry/parametric.js +200 -0
  130. package/dist/geometry/parametric.js.map +1 -0
  131. package/dist/index.d.ts +27 -0
  132. package/dist/index.d.ts.map +1 -0
  133. package/dist/index.js +46 -0
  134. package/dist/index.js.map +1 -0
  135. package/dist/mutations/bind.d.ts +56 -0
  136. package/dist/mutations/bind.d.ts.map +1 -0
  137. package/dist/mutations/bind.js +68 -0
  138. package/dist/mutations/bind.js.map +1 -0
  139. package/dist/mutations/index.d.ts +2 -0
  140. package/dist/mutations/index.d.ts.map +1 -0
  141. package/dist/mutations/index.js +5 -0
  142. package/dist/mutations/index.js.map +1 -0
  143. package/dist/perf/benchmark.d.ts +25 -0
  144. package/dist/perf/benchmark.d.ts.map +1 -0
  145. package/dist/perf/benchmark.js +97 -0
  146. package/dist/perf/benchmark.js.map +1 -0
  147. package/dist/perf/index.d.ts +3 -0
  148. package/dist/perf/index.d.ts.map +1 -0
  149. package/dist/perf/index.js +6 -0
  150. package/dist/perf/index.js.map +1 -0
  151. package/dist/perf/latency.d.ts +39 -0
  152. package/dist/perf/latency.d.ts.map +1 -0
  153. package/dist/perf/latency.js +79 -0
  154. package/dist/perf/latency.js.map +1 -0
  155. package/dist/privacy.d.ts +45 -0
  156. package/dist/privacy.d.ts.map +1 -0
  157. package/dist/privacy.js +66 -0
  158. package/dist/privacy.js.map +1 -0
  159. package/dist/providers/indexeddb.d.ts +37 -0
  160. package/dist/providers/indexeddb.d.ts.map +1 -0
  161. package/dist/providers/indexeddb.js +45 -0
  162. package/dist/providers/indexeddb.js.map +1 -0
  163. package/dist/providers/webrtc.d.ts +40 -0
  164. package/dist/providers/webrtc.d.ts.map +1 -0
  165. package/dist/providers/webrtc.js +81 -0
  166. package/dist/providers/webrtc.js.map +1 -0
  167. package/dist/providers/websocket.d.ts +45 -0
  168. package/dist/providers/websocket.d.ts.map +1 -0
  169. package/dist/providers/websocket.js +56 -0
  170. package/dist/providers/websocket.js.map +1 -0
  171. package/dist/security/e2e.d.ts +54 -0
  172. package/dist/security/e2e.d.ts.map +1 -0
  173. package/dist/security/e2e.js +147 -0
  174. package/dist/security/e2e.js.map +1 -0
  175. package/dist/security/index.d.ts +2 -0
  176. package/dist/security/index.d.ts.map +1 -0
  177. package/dist/security/index.js +5 -0
  178. package/dist/security/index.js.map +1 -0
  179. package/dist/session.d.ts +79 -0
  180. package/dist/session.d.ts.map +1 -0
  181. package/dist/session.js +112 -0
  182. package/dist/session.js.map +1 -0
  183. package/dist/snapshot/from-ifcx.d.ts +24 -0
  184. package/dist/snapshot/from-ifcx.d.ts.map +1 -0
  185. package/dist/snapshot/from-ifcx.js +93 -0
  186. package/dist/snapshot/from-ifcx.js.map +1 -0
  187. package/dist/snapshot/index.d.ts +6 -0
  188. package/dist/snapshot/index.d.ts.map +1 -0
  189. package/dist/snapshot/index.js +9 -0
  190. package/dist/snapshot/index.js.map +1 -0
  191. package/dist/snapshot/layers.d.ts +56 -0
  192. package/dist/snapshot/layers.d.ts.map +1 -0
  193. package/dist/snapshot/layers.js +82 -0
  194. package/dist/snapshot/layers.js.map +1 -0
  195. package/dist/snapshot/minimal-layer.d.ts +40 -0
  196. package/dist/snapshot/minimal-layer.d.ts.map +1 -0
  197. package/dist/snapshot/minimal-layer.js +123 -0
  198. package/dist/snapshot/minimal-layer.js.map +1 -0
  199. package/dist/snapshot/to-ifcx.d.ts +26 -0
  200. package/dist/snapshot/to-ifcx.d.ts.map +1 -0
  201. package/dist/snapshot/to-ifcx.js +54 -0
  202. package/dist/snapshot/to-ifcx.js.map +1 -0
  203. package/dist/snapshot/worker.d.ts +45 -0
  204. package/dist/snapshot/worker.d.ts.map +1 -0
  205. package/dist/snapshot/worker.js +73 -0
  206. package/dist/snapshot/worker.js.map +1 -0
  207. package/dist/sync/room.d.ts +15 -0
  208. package/dist/sync/room.d.ts.map +1 -0
  209. package/dist/sync/room.js +20 -0
  210. package/dist/sync/room.js.map +1 -0
  211. package/dist/undo.d.ts +25 -0
  212. package/dist/undo.d.ts.map +1 -0
  213. package/dist/undo.js +37 -0
  214. package/dist/undo.js.map +1 -0
  215. package/dist/viewer-bridge.d.ts +27 -0
  216. package/dist/viewer-bridge.d.ts.map +1 -0
  217. package/dist/viewer-bridge.js +82 -0
  218. package/dist/viewer-bridge.js.map +1 -0
  219. package/package.json +83 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/awareness/index.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Renderer-agnostic presence overlay (spec §7).
3
+ *
4
+ * Drop this into any DOM-based viewer (`packages/viewer`, custom apps,
5
+ * embedded iframes) and it draws other peers' 2D cursors and
6
+ * selection-color labels onto a transparent overlay <canvas>. Pure
7
+ * 2D — apps that need 3D cursors layer them via Three.js / WebGPU on
8
+ * top of the same `peerVisuals` data.
9
+ *
10
+ * Wiring (browser):
11
+ *
12
+ * const overlay = createPresenceOverlay({
13
+ * container: document.getElementById('viewport'),
14
+ * viewport: 'plan',
15
+ * });
16
+ * session.presence.onUpdate((peers) => overlay.update(peers));
17
+ *
18
+ * // Then on dispose:
19
+ * overlay.destroy();
20
+ */
21
+ import type { PresenceMap, Vec3 } from './presence.js';
22
+ import { type PeerVisualOptions } from './render.js';
23
+ export interface PresenceOverlayOptions extends PeerVisualOptions {
24
+ /** Element to mount the overlay <canvas> over. Must be position:relative. */
25
+ container: HTMLElement;
26
+ /** Viewport name to filter cursor2d to. */
27
+ viewport: string;
28
+ /** Optional override for the cursor arrow size in CSS pixels. Default 14. */
29
+ cursorSize?: number;
30
+ /** Optional override for label font in CSS. Default '12px sans-serif'. */
31
+ font?: string;
32
+ /**
33
+ * Host-supplied world→screen projector. When provided, peers' `cursor3d`
34
+ * values (published by `mountPresenceInViewer` with `raycastToWorld`)
35
+ * are projected through THIS viewer's camera, so every peer sees
36
+ * cursors anchored to the same world point regardless of their own
37
+ * perspective.
38
+ *
39
+ * Returning `null` (point is behind the camera, off-screen, etc.)
40
+ * hides the peer's cursor that frame. `cursor2d` continues to be used
41
+ * as a fallback when a peer hasn't published a 3D cursor.
42
+ */
43
+ worldToScreen?: (worldPos: Vec3) => {
44
+ x: number;
45
+ y: number;
46
+ } | null;
47
+ }
48
+ export interface PresenceOverlay {
49
+ update(peers: PresenceMap): void;
50
+ /** Resize the canvas to match the container (call on container resize). */
51
+ resize(): void;
52
+ destroy(): void;
53
+ }
54
+ /**
55
+ * Mount a 2D presence overlay. Returns a controller that the app can
56
+ * call `update(peers)` on whenever presence changes.
57
+ */
58
+ export declare function createPresenceOverlay(opts: PresenceOverlayOptions): PresenceOverlay;
59
+ //# sourceMappingURL=overlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/awareness/overlay.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAsD,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEzG,MAAM,WAAW,sBAAuB,SAAQ,iBAAiB;IAC/D,6EAA6E;IAC7E,SAAS,EAAE,WAAW,CAAC;IACvB,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,KAAK;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACrE;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,2EAA2E;IAC3E,MAAM,IAAI,IAAI,CAAC;IACf,OAAO,IAAI,IAAI,CAAC;CACjB;AAMD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,sBAAsB,GAAG,eAAe,CAgEnF"}
@@ -0,0 +1,110 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ import { peerVisuals, cursorScreenPosition } from './render.js';
5
+ const DPR = () => (typeof globalThis !== 'undefined' && typeof window !== 'undefined'
6
+ ? window.devicePixelRatio || 1
7
+ : 1);
8
+ /**
9
+ * Mount a 2D presence overlay. Returns a controller that the app can
10
+ * call `update(peers)` on whenever presence changes.
11
+ */
12
+ export function createPresenceOverlay(opts) {
13
+ if (typeof document === 'undefined') {
14
+ throw new Error('@ifc-lite/collab: createPresenceOverlay requires a browser DOM');
15
+ }
16
+ const canvas = document.createElement('canvas');
17
+ canvas.style.position = 'absolute';
18
+ canvas.style.inset = '0';
19
+ canvas.style.pointerEvents = 'none';
20
+ canvas.style.zIndex = '10';
21
+ opts.container.appendChild(canvas);
22
+ const ctx = canvas.getContext('2d');
23
+ if (!ctx)
24
+ throw new Error('@ifc-lite/collab: 2D canvas unavailable');
25
+ const cursorSize = opts.cursorSize ?? 14;
26
+ const font = opts.font ?? '12px sans-serif';
27
+ // Cache the last drawn peers so resize can redraw without waiting for
28
+ // the next presence update — otherwise the overlay goes blank between
29
+ // resize and the next `update(peers)` call.
30
+ let lastPeers = null;
31
+ const draw = (peers) => {
32
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
33
+ if (!peers)
34
+ return;
35
+ const visuals = peerVisuals(peers, opts);
36
+ for (const v of visuals) {
37
+ drawPeer(ctx, v, opts.viewport, cursorSize, font, opts.worldToScreen);
38
+ }
39
+ };
40
+ const resize = () => {
41
+ const r = opts.container.getBoundingClientRect();
42
+ const dpr = DPR();
43
+ canvas.width = Math.max(1, Math.floor(r.width * dpr));
44
+ canvas.height = Math.max(1, Math.floor(r.height * dpr));
45
+ canvas.style.width = `${r.width}px`;
46
+ canvas.style.height = `${r.height}px`;
47
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
48
+ draw(lastPeers);
49
+ };
50
+ resize();
51
+ const update = (peers) => {
52
+ lastPeers = peers;
53
+ draw(peers);
54
+ };
55
+ // Auto-resize via ResizeObserver if available.
56
+ let ro = null;
57
+ if (typeof ResizeObserver !== 'undefined') {
58
+ ro = new ResizeObserver(() => resize());
59
+ ro.observe(opts.container);
60
+ }
61
+ return {
62
+ update,
63
+ resize,
64
+ destroy() {
65
+ ro?.disconnect();
66
+ canvas.remove();
67
+ },
68
+ };
69
+ }
70
+ function drawPeer(ctx, v, viewport, cursorSize, font, worldToScreen) {
71
+ // Prefer the 3D cursor when both the peer published one AND this
72
+ // overlay was given a projector — that path is camera-aware and stays
73
+ // correct across different viewer perspectives. Fall back to cursor2d
74
+ // (same-viewport pixel coordinates) otherwise.
75
+ let pos = null;
76
+ if (v.cursor3d && worldToScreen) {
77
+ pos = worldToScreen(v.cursor3d);
78
+ }
79
+ if (!pos) {
80
+ pos = cursorScreenPosition(v, viewport);
81
+ }
82
+ if (!pos)
83
+ return;
84
+ ctx.globalAlpha = v.opacity;
85
+ ctx.fillStyle = v.color;
86
+ ctx.strokeStyle = '#ffffff';
87
+ ctx.lineWidth = 1;
88
+ // Cursor arrow.
89
+ ctx.beginPath();
90
+ ctx.moveTo(pos.x, pos.y);
91
+ ctx.lineTo(pos.x, pos.y + cursorSize);
92
+ ctx.lineTo(pos.x + cursorSize * 0.3, pos.y + cursorSize * 0.7);
93
+ ctx.lineTo(pos.x + cursorSize * 0.7, pos.y + cursorSize * 0.7);
94
+ ctx.closePath();
95
+ ctx.fill();
96
+ ctx.stroke();
97
+ // Label.
98
+ ctx.font = font;
99
+ const padX = 6;
100
+ const padY = 4;
101
+ const textW = ctx.measureText(v.label).width;
102
+ const labelX = pos.x + cursorSize + 4;
103
+ const labelY = pos.y + cursorSize - 6;
104
+ ctx.fillStyle = v.color;
105
+ ctx.fillRect(labelX, labelY, textW + padX * 2, 16 + padY);
106
+ ctx.fillStyle = '#ffffff';
107
+ ctx.fillText(v.label, labelX + padX, labelY + 14);
108
+ ctx.globalAlpha = 1;
109
+ }
110
+ //# sourceMappingURL=overlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay.js","sourceRoot":"","sources":["../../src/awareness/overlay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Presence: a typed wrapper over `y-protocols/awareness`.
3
+ *
4
+ * Spec §5.4 / §7. Awareness is last-write-wins-by-clock and never
5
+ * persisted. Updates are bursty — we cap them at 30 Hz with delta-only
6
+ * payloads.
7
+ */
8
+ import { Awareness, applyAwarenessUpdate, encodeAwarenessUpdate } from 'y-protocols/awareness';
9
+ import * as Y from 'yjs';
10
+ export interface UserIdentity {
11
+ id: string;
12
+ name: string;
13
+ /** Optional. If omitted, a deterministic color is derived from `id`. */
14
+ color?: string;
15
+ avatar?: string;
16
+ }
17
+ export interface Vec3 {
18
+ x: number;
19
+ y: number;
20
+ z: number;
21
+ }
22
+ export interface Vec2 {
23
+ x: number;
24
+ y: number;
25
+ }
26
+ export interface Vec4 {
27
+ x: number;
28
+ y: number;
29
+ z: number;
30
+ w: number;
31
+ }
32
+ export interface CameraState {
33
+ position: Vec3;
34
+ target: Vec3;
35
+ fov: number;
36
+ }
37
+ export interface PresenceState {
38
+ user: UserIdentity;
39
+ cursor3d?: Vec3;
40
+ cursor2d?: {
41
+ viewport: string;
42
+ pos: Vec2;
43
+ };
44
+ selection: string[];
45
+ camera?: CameraState;
46
+ activeView?: string;
47
+ activeSection?: {
48
+ plane: Vec4;
49
+ };
50
+ isolated?: string[];
51
+ tool?: 'select' | 'measure' | 'comment' | 'edit' | 'create' | string;
52
+ status: 'active' | 'idle' | 'offline';
53
+ /** Wall-clock ms; used by stale-presence eviction. */
54
+ lastUpdate: number;
55
+ /** Optional model the cursor is currently in (for federated sessions). */
56
+ modelId?: string;
57
+ }
58
+ export interface PresenceMap {
59
+ /** clientID → state */
60
+ [clientId: number]: PresenceState;
61
+ }
62
+ export type PresenceUpdateListener = (peers: PresenceMap, self: PresenceState | null) => void;
63
+ export interface PresenceOptions {
64
+ /** Awareness update rate cap in Hz (default 30). */
65
+ updateRateHz?: number;
66
+ /** Stale eviction window in ms (default 10_000). */
67
+ staleAfterMs?: number;
68
+ }
69
+ export interface Presence {
70
+ readonly awareness: Awareness;
71
+ readonly clientId: number;
72
+ setUser(user: UserIdentity): void;
73
+ setSelection(paths: string[]): void;
74
+ setCursor3d(pos: Vec3 | null): void;
75
+ setCursor2d(viewport: string, pos: Vec2 | null): void;
76
+ setCamera(camera: CameraState | null): void;
77
+ setActiveView(viewId: string | null): void;
78
+ setActiveSection(plane: Vec4 | null): void;
79
+ setIsolated(paths: string[] | null): void;
80
+ setTool(tool: PresenceState['tool'] | null): void;
81
+ setStatus(status: PresenceState['status']): void;
82
+ setModelId(modelId: string | null): void;
83
+ /** Patch arbitrary fields in one go. Use for camera+cursor on the same frame. */
84
+ patch(partial: Partial<PresenceState>): void;
85
+ getSelf(): PresenceState | null;
86
+ getPeers(): PresenceMap;
87
+ onUpdate(listener: PresenceUpdateListener): () => void;
88
+ /** Force a stale sweep; normally automatic. */
89
+ evictStale(): void;
90
+ dispose(): void;
91
+ }
92
+ /** Create a presence object bound to a Y.Doc. */
93
+ export declare function createPresence(doc: Y.Doc, opts?: PresenceOptions): Presence;
94
+ export { applyAwarenessUpdate, encodeAwarenessUpdate, Awareness };
95
+ //# sourceMappingURL=presence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/awareness/presence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,114 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ /**
5
+ * Presence: a typed wrapper over `y-protocols/awareness`.
6
+ *
7
+ * Spec §5.4 / §7. Awareness is last-write-wins-by-clock and never
8
+ * persisted. Updates are bursty — we cap them at 30 Hz with delta-only
9
+ * payloads.
10
+ */
11
+ import { Awareness, applyAwarenessUpdate, encodeAwarenessUpdate, removeAwarenessStates } from 'y-protocols/awareness';
12
+ import { colorForUser } from './color.js';
13
+ /** Create a presence object bound to a Y.Doc. */
14
+ export function createPresence(doc, opts = {}) {
15
+ const awareness = new Awareness(doc);
16
+ const updateRateMs = 1000 / (opts.updateRateHz ?? 30);
17
+ const staleAfterMs = opts.staleAfterMs ?? 10_000;
18
+ let pendingPatch = null;
19
+ let flushHandle = null;
20
+ const listeners = new Set();
21
+ const flush = () => {
22
+ if (!pendingPatch)
23
+ return;
24
+ const current = awareness.getLocalState() ?? null;
25
+ const merged = {
26
+ ...(current ?? {
27
+ user: { id: 'anon', name: 'Anonymous', color: '#888888' },
28
+ selection: [],
29
+ status: 'active',
30
+ lastUpdate: Date.now(),
31
+ }),
32
+ ...pendingPatch,
33
+ lastUpdate: Date.now(),
34
+ };
35
+ awareness.setLocalState(merged);
36
+ pendingPatch = null;
37
+ flushHandle = null;
38
+ };
39
+ const enqueue = (patch) => {
40
+ pendingPatch = { ...(pendingPatch ?? {}), ...patch };
41
+ if (flushHandle == null) {
42
+ flushHandle = setTimeout(flush, updateRateMs);
43
+ }
44
+ };
45
+ const onAwareness = () => {
46
+ const peers = {};
47
+ awareness.getStates().forEach((value, clientId) => {
48
+ peers[clientId] = value;
49
+ });
50
+ const self = awareness.getLocalState() ?? null;
51
+ listeners.forEach((l) => l(peers, self));
52
+ };
53
+ awareness.on('change', onAwareness);
54
+ const evictTimer = setInterval(() => evictStale(), Math.max(staleAfterMs / 2, 2_000));
55
+ const evictStale = () => {
56
+ const now = Date.now();
57
+ const toRemove = [];
58
+ awareness.getStates().forEach((value, clientId) => {
59
+ const state = value;
60
+ if (clientId === awareness.clientID)
61
+ return;
62
+ if (state?.lastUpdate && now - state.lastUpdate > staleAfterMs) {
63
+ toRemove.push(clientId);
64
+ }
65
+ });
66
+ if (toRemove.length > 0) {
67
+ removeAwarenessStates(awareness, toRemove, 'stale-eviction');
68
+ }
69
+ };
70
+ return {
71
+ awareness,
72
+ clientId: doc.clientID,
73
+ setUser(user) {
74
+ const resolved = { ...user, color: user.color ?? colorForUser(user.id) };
75
+ enqueue({ user: resolved });
76
+ },
77
+ setSelection(paths) { enqueue({ selection: paths }); },
78
+ setCursor3d(pos) { enqueue({ cursor3d: pos ?? undefined }); },
79
+ setCursor2d(viewport, pos) {
80
+ enqueue({ cursor2d: pos ? { viewport, pos } : undefined });
81
+ },
82
+ setCamera(camera) { enqueue({ camera: camera ?? undefined }); },
83
+ setActiveView(viewId) { enqueue({ activeView: viewId ?? undefined }); },
84
+ setActiveSection(plane) { enqueue({ activeSection: plane ? { plane } : undefined }); },
85
+ setIsolated(paths) { enqueue({ isolated: paths ?? undefined }); },
86
+ setTool(tool) { enqueue({ tool: tool ?? undefined }); },
87
+ setStatus(status) { enqueue({ status }); },
88
+ setModelId(modelId) { enqueue({ modelId: modelId ?? undefined }); },
89
+ patch(partial) { enqueue(partial); },
90
+ getSelf() { return awareness.getLocalState() ?? null; },
91
+ getPeers() {
92
+ const peers = {};
93
+ awareness.getStates().forEach((value, clientId) => {
94
+ peers[clientId] = value;
95
+ });
96
+ return peers;
97
+ },
98
+ onUpdate(listener) {
99
+ listeners.add(listener);
100
+ return () => { listeners.delete(listener); };
101
+ },
102
+ evictStale,
103
+ dispose() {
104
+ clearInterval(evictTimer);
105
+ if (flushHandle)
106
+ clearTimeout(flushHandle);
107
+ awareness.off('change', onAwareness);
108
+ awareness.destroy();
109
+ listeners.clear();
110
+ },
111
+ };
112
+ }
113
+ export { applyAwarenessUpdate, encodeAwarenessUpdate, Awareness };
114
+ //# sourceMappingURL=presence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presence.js","sourceRoot":"","sources":["../../src/awareness/presence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Presence-renderer math helpers (spec §7).
3
+ *
4
+ * The actual viewer rendering lives in `packages/viewer` (Three.js,
5
+ * WebGPU, …) — but the math that decides "where does this peer's
6
+ * cursor draw, what color, what label, is it stale" is the same
7
+ * regardless of the rendering engine. Pulling it out here keeps the
8
+ * viewer thin and gives every rendering target the same look.
9
+ *
10
+ * Apps consume:
11
+ * - `peerVisuals(peers, opts)` → list of `{ clientId, color,
12
+ * label, opacity, cursor3d, cursor2d, selection, isStale }`.
13
+ * - `cursorScreenPosition(peer, viewport)` → `{ x, y } | null` for
14
+ * 2D cursor projection (3D peers project externally).
15
+ */
16
+ import type { PresenceMap, PresenceState } from './presence.js';
17
+ export interface PeerVisual {
18
+ clientId: number;
19
+ user: PresenceState['user'];
20
+ /** Resolved hex color — palette pick if user.color was absent. */
21
+ color: string;
22
+ /** "Anna (agent)", "Mark — measuring", "Louis"… */
23
+ label: string;
24
+ /** 0..1; 1 for fresh, fades to ~0.4 as the peer goes idle / stale. */
25
+ opacity: number;
26
+ /** True when no update has arrived for `staleAfterMs`. */
27
+ isStale: boolean;
28
+ cursor3d?: PresenceState['cursor3d'];
29
+ cursor2d?: PresenceState['cursor2d'];
30
+ selection: string[];
31
+ modelId?: string;
32
+ }
33
+ export interface PeerVisualOptions {
34
+ /** Drop the local peer from the result. Default true. */
35
+ excludeClientId?: number;
36
+ /** Wall-clock ms after which a peer is considered stale. Default 10_000. */
37
+ staleAfterMs?: number;
38
+ /** Override the random color palette. */
39
+ palette?: readonly string[];
40
+ /** Override the agent palette. */
41
+ agentPalette?: readonly string[];
42
+ /** Override `Date.now`. */
43
+ now?: () => number;
44
+ }
45
+ /**
46
+ * Convert a `PresenceMap` into render-ready visuals. Pure function —
47
+ * the renderer just iterates the result and draws.
48
+ */
49
+ export declare function peerVisuals(peers: PresenceMap, opts?: PeerVisualOptions): PeerVisual[];
50
+ /**
51
+ * Project a peer's `cursor2d` into a viewport. Returns null when the
52
+ * peer is in a different viewport or has no 2D cursor reported.
53
+ */
54
+ export declare function cursorScreenPosition(peer: PeerVisual, viewportName: string): {
55
+ x: number;
56
+ y: number;
57
+ } | null;
58
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/awareness/render.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAIhE,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5B,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACrC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,yDAAyD;IACzD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yCAAyC;IACzC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,kCAAkC;IAClC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,2BAA2B;IAC3B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,GAAE,iBAAsB,GAAG,UAAU,EAAE,CAsC1F;AAQD;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,UAAU,EAChB,YAAY,EAAE,MAAM,GACnB;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAIjC"}
@@ -0,0 +1,66 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ import { colorForUser, DEFAULT_USER_PALETTE } from './color.js';
5
+ import { AGENT_PALETTE } from './agent.js';
6
+ /**
7
+ * Convert a `PresenceMap` into render-ready visuals. Pure function —
8
+ * the renderer just iterates the result and draws.
9
+ */
10
+ export function peerVisuals(peers, opts = {}) {
11
+ const exclude = opts.excludeClientId;
12
+ const staleAfterMs = opts.staleAfterMs ?? 10_000;
13
+ const now = opts.now ? opts.now() : Date.now();
14
+ const palette = opts.palette ?? DEFAULT_USER_PALETTE;
15
+ const agentPalette = opts.agentPalette ?? AGENT_PALETTE;
16
+ const out = [];
17
+ for (const [idStr, state] of Object.entries(peers)) {
18
+ const clientId = Number(idStr);
19
+ if (clientId === exclude)
20
+ continue;
21
+ if (!state || !state.user)
22
+ continue;
23
+ const isAgent = typeof state.user.name === 'string' && state.user.name.endsWith('(agent)');
24
+ const color = state.user.color ?? colorForUser(state.user.id, isAgent ? agentPalette : palette);
25
+ const idleMs = state.lastUpdate ? now - state.lastUpdate : 0;
26
+ const isStale = idleMs >= staleAfterMs;
27
+ const fadeStart = staleAfterMs / 2;
28
+ const opacity = isStale
29
+ ? 0.4
30
+ : idleMs <= fadeStart
31
+ ? 1
32
+ : 1 - (0.6 * (idleMs - fadeStart)) / fadeStart;
33
+ const label = renderLabel(state);
34
+ out.push({
35
+ clientId,
36
+ user: state.user,
37
+ color,
38
+ label,
39
+ opacity: Math.max(0.4, Math.min(1, opacity)),
40
+ isStale,
41
+ cursor3d: state.cursor3d,
42
+ cursor2d: state.cursor2d,
43
+ selection: state.selection ?? [],
44
+ modelId: state.modelId,
45
+ });
46
+ }
47
+ return out.sort((a, b) => a.clientId - b.clientId);
48
+ }
49
+ function renderLabel(state) {
50
+ const base = state.user.name;
51
+ if (state.tool && state.tool !== 'select')
52
+ return `${base} — ${state.tool}`;
53
+ return base;
54
+ }
55
+ /**
56
+ * Project a peer's `cursor2d` into a viewport. Returns null when the
57
+ * peer is in a different viewport or has no 2D cursor reported.
58
+ */
59
+ export function cursorScreenPosition(peer, viewportName) {
60
+ if (!peer.cursor2d)
61
+ return null;
62
+ if (peer.cursor2d.viewport !== viewportName)
63
+ return null;
64
+ return { x: peer.cursor2d.pos.x, y: peer.cursor2d.pos.y };
65
+ }
66
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/awareness/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Branch-tree visualization data (spec §16.2 / §19 v0.7).
3
+ *
4
+ * Pure data helper that turns a `HistorySidecar` into a (nodes, edges)
5
+ * tree the UI layer can render. We deliberately produce nothing
6
+ * pixel-related — apps choose between a force-directed layout, a git
7
+ * log columnar layout, or a simple list. The tree is just the
8
+ * branching structure.
9
+ */
10
+ import type { BranchInfo, HistorySidecar } from './history.js';
11
+ export interface BranchTreeNode {
12
+ /** Entry id (or branch-anchor `'branch:<name>'` for the empty branch root). */
13
+ id: string;
14
+ kind: 'entry' | 'branch-anchor' | 'merge';
15
+ branch: string;
16
+ at: string;
17
+ label?: string;
18
+ /** Parent node id (predecessor on the same branch, or anchor). */
19
+ parentId?: string;
20
+ /** For merge nodes, the second parent (the branch being merged in). */
21
+ mergedFromBranch?: string;
22
+ }
23
+ export interface BranchTreeEdge {
24
+ from: string;
25
+ to: string;
26
+ kind: 'history' | 'fork' | 'merge';
27
+ }
28
+ export interface BranchTree {
29
+ nodes: BranchTreeNode[];
30
+ edges: BranchTreeEdge[];
31
+ branches: BranchInfo[];
32
+ }
33
+ /**
34
+ * Build a branch-tree view from a sidecar. Single pass: collect
35
+ * branches + entries, link entries to their predecessor on the same
36
+ * branch, and emit fork edges from the parent branch's
37
+ * `forkedFromEntryId` to the new branch's first entry (or anchor).
38
+ */
39
+ export declare function buildBranchTree(sidecar: HistorySidecar): Promise<BranchTree>;
40
+ //# sourceMappingURL=branch-tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branch-tree.d.ts","sourceRoot":"","sources":["../../src/branch/branch-tree.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAgB,cAAc,EAAE,MAAM,cAAc,CAAC;AAE7E,MAAM,WAAW,cAAc;IAC7B,+EAA+E;IAC/E,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,GAAG,eAAe,GAAG,OAAO,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;CACpC;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,QAAQ,EAAE,UAAU,EAAE,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CA4DlF"}
@@ -0,0 +1,66 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ /**
5
+ * Build a branch-tree view from a sidecar. Single pass: collect
6
+ * branches + entries, link entries to their predecessor on the same
7
+ * branch, and emit fork edges from the parent branch's
8
+ * `forkedFromEntryId` to the new branch's first entry (or anchor).
9
+ */
10
+ export async function buildBranchTree(sidecar) {
11
+ const branches = await sidecar.branches();
12
+ const all = await sidecar.entries();
13
+ const byBranch = new Map();
14
+ for (const e of all) {
15
+ const arr = byBranch.get(e.branch) ?? [];
16
+ arr.push(e);
17
+ byBranch.set(e.branch, arr);
18
+ }
19
+ for (const arr of byBranch.values())
20
+ arr.sort((a, b) => a.at.localeCompare(b.at));
21
+ const nodes = [];
22
+ const edges = [];
23
+ for (const branch of branches) {
24
+ const entries = byBranch.get(branch.name) ?? [];
25
+ const anchorId = `branch:${branch.name}`;
26
+ nodes.push({
27
+ id: anchorId,
28
+ kind: 'branch-anchor',
29
+ branch: branch.name,
30
+ at: branch.createdAt,
31
+ });
32
+ // Fork edge from the parent entry to the anchor.
33
+ if (branch.forkedFromEntryId) {
34
+ edges.push({ from: branch.forkedFromEntryId, to: anchorId, kind: 'fork' });
35
+ }
36
+ let prevId = anchorId;
37
+ for (const e of entries) {
38
+ // Trust the structural metadata set by `merge()`, not the label.
39
+ // A user can authorin a regular commit with `label: "merge bar"`
40
+ // and we don't want that to render as a merge node.
41
+ const isMerge = typeof e.mergedFromBranch === 'string';
42
+ const node = {
43
+ id: e.entryId,
44
+ kind: isMerge ? 'merge' : 'entry',
45
+ branch: e.branch,
46
+ at: e.at,
47
+ label: e.label,
48
+ parentId: prevId,
49
+ };
50
+ if (isMerge) {
51
+ node.mergedFromBranch = e.mergedFromBranch;
52
+ }
53
+ nodes.push(node);
54
+ edges.push({ from: prevId, to: e.entryId, kind: 'history' });
55
+ // Use the immutable `mergedFromEntryId` captured at merge time —
56
+ // pointing at the source branch's CURRENT tip would drift the
57
+ // merge edge whenever that branch advances later.
58
+ if (isMerge && e.mergedFromEntryId) {
59
+ edges.push({ from: e.mergedFromEntryId, to: e.entryId, kind: 'merge' });
60
+ }
61
+ prevId = e.entryId;
62
+ }
63
+ }
64
+ return { nodes, edges, branches };
65
+ }
66
+ //# sourceMappingURL=branch-tree.js.map