@1agh/maude 1.3.0 → 1.3.1
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/apps/studio/api.ts +43 -2
- package/apps/studio/canvas-build.ts +49 -8
- package/apps/studio/canvas-edit.ts +77 -0
- package/apps/studio/canvas-list-watch.ts +33 -2
- package/apps/studio/client/app.jsx +97 -4
- package/apps/studio/client/canvas-url.js +39 -1
- package/apps/studio/client/styles/3-shell-maude.css +1 -0
- package/apps/studio/collab/awareness-bridge.ts +65 -14
- package/apps/studio/dist/client.bundle.js +449 -449
- package/apps/studio/dist/comment-mount.js +2 -2
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/git/accepted-guard.ts +36 -0
- package/apps/studio/hmr-broadcast.ts +34 -1
- package/apps/studio/http.ts +11 -0
- package/apps/studio/managed-projects.ts +22 -0
- package/apps/studio/sync/index.ts +106 -12
- package/apps/studio/sync/presentation.ts +30 -0
- package/apps/studio/sync/status.ts +5 -1
- package/apps/studio/sync/transaction-client.ts +26 -0
- package/apps/studio/use-tool-mode.tsx +44 -0
- package/apps/studio/whats-new.json +36 -0
- package/package.json +8 -8
- package/plugins/design/templates/_shell.html +48 -7
|
@@ -27,8 +27,45 @@
|
|
|
27
27
|
// originating clientID — the random 32-bit ids make local↔remote collisions
|
|
28
28
|
// negligible.
|
|
29
29
|
|
|
30
|
+
import * as encoding from 'lib0/encoding';
|
|
30
31
|
import type { Awareness } from 'y-protocols/awareness';
|
|
31
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
applyAwarenessUpdate,
|
|
34
|
+
encodeAwarenessUpdate,
|
|
35
|
+
removeAwarenessStates,
|
|
36
|
+
} from 'y-protocols/awareness';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A departed relayed client, as the hub can carry it.
|
|
40
|
+
*
|
|
41
|
+
* A peer leaving is a REMOVAL — a null awareness state. The hub (Hocuspocus 4)
|
|
42
|
+
* runs every incoming awareness frame through a scratch Awareness for its
|
|
43
|
+
* `beforeHandleAwareness` hook and re-encodes only the states that scratch
|
|
44
|
+
* still HOLDS, so a null state is dropped on the way in: a tab closing behind
|
|
45
|
+
* this studio never left anyone else's presence until the 30 s awareness
|
|
46
|
+
* timeout (plan T31/L19). The one thing it forwards is a state, so a departure
|
|
47
|
+
* crosses as this marker with a newer clock; every bridge turns it back into a
|
|
48
|
+
* real removal on its room. It carries no `name`, so a client that predates
|
|
49
|
+
* the marker drops it at the trust boundary (`sanitizeForeignState`) instead
|
|
50
|
+
* of drawing a nameless avatar.
|
|
51
|
+
*/
|
|
52
|
+
export const DEPARTED = Object.freeze({ __left: true });
|
|
53
|
+
|
|
54
|
+
function isDeparted(state: unknown): boolean {
|
|
55
|
+
return !!state && typeof state === 'object' && (state as { __left?: unknown }).__left === true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Encode a departure for each of `clients`, one clock past what `from` knows. */
|
|
59
|
+
function encodeDepartures(from: Awareness, clients: readonly number[]): Uint8Array {
|
|
60
|
+
const enc = encoding.createEncoder();
|
|
61
|
+
encoding.writeVarUint(enc, clients.length);
|
|
62
|
+
for (const clientID of clients) {
|
|
63
|
+
encoding.writeVarUint(enc, clientID);
|
|
64
|
+
encoding.writeVarUint(enc, (from.meta.get(clientID)?.clock ?? 0) + 1);
|
|
65
|
+
encoding.writeVarString(enc, JSON.stringify(DEPARTED));
|
|
66
|
+
}
|
|
67
|
+
return encoding.toUint8Array(enc);
|
|
68
|
+
}
|
|
32
69
|
|
|
33
70
|
interface AwarenessChange {
|
|
34
71
|
added: number[];
|
|
@@ -49,22 +86,36 @@ export function bridgeAwareness(a: Awareness, b: Awareness): () => void {
|
|
|
49
86
|
// so it relays; a change this bridge applied carries BRIDGE, so it stops.
|
|
50
87
|
const BRIDGE = { awarenessBridge: true };
|
|
51
88
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
89
|
+
// Room → hub. A client that left the room leaves as a DEPARTED marker (see
|
|
90
|
+
// above) — never as the null state the hub would drop. Everything else
|
|
91
|
+
// relays as it is.
|
|
92
|
+
const aToB = ({ added, updated, removed }: AwarenessChange, origin: unknown) => {
|
|
93
|
+
if (origin === BRIDGE) return;
|
|
94
|
+
const present = added.concat(updated);
|
|
95
|
+
if (present.length > 0) applyAwarenessUpdate(b, encodeAwarenessUpdate(a, present), BRIDGE);
|
|
96
|
+
const gone = removed.filter((id) => id !== b.clientID && b.getStates().has(id));
|
|
97
|
+
if (gone.length > 0) applyAwarenessUpdate(b, encodeDepartures(b, gone), BRIDGE);
|
|
98
|
+
};
|
|
99
|
+
// Hub → room. A DEPARTED marker is a removal here; a real removal (the hub's
|
|
100
|
+
// own timeout, a peer's socket closing) relays as ever.
|
|
101
|
+
const bToA = ({ added, updated, removed }: AwarenessChange, origin: unknown) => {
|
|
102
|
+
if (origin === BRIDGE) return;
|
|
103
|
+
const states = b.getStates();
|
|
104
|
+
const departed = added.concat(updated).filter((id) => isDeparted(states.get(id)));
|
|
105
|
+
const live = added.concat(updated).filter((id) => !isDeparted(states.get(id)));
|
|
106
|
+
const changed = live.concat(removed);
|
|
107
|
+
if (changed.length > 0) applyAwarenessUpdate(a, encodeAwarenessUpdate(b, changed), BRIDGE);
|
|
108
|
+
const leaving = departed.filter((id) => a.getStates().has(id));
|
|
109
|
+
if (leaving.length > 0) removeAwarenessStates(a, leaving, BRIDGE);
|
|
110
|
+
};
|
|
63
111
|
|
|
64
|
-
// Initial state exchange — push each side's current states to the other
|
|
112
|
+
// Initial state exchange — push each side's current states to the other
|
|
113
|
+
// (departed markers are not a presence).
|
|
65
114
|
const aClients = Array.from(a.getStates().keys());
|
|
66
115
|
if (aClients.length > 0) applyAwarenessUpdate(b, encodeAwarenessUpdate(a, aClients), BRIDGE);
|
|
67
|
-
const bClients = Array.from(b.getStates().
|
|
116
|
+
const bClients = Array.from(b.getStates().entries())
|
|
117
|
+
.filter(([, state]) => !isDeparted(state))
|
|
118
|
+
.map(([id]) => id);
|
|
68
119
|
if (bClients.length > 0) applyAwarenessUpdate(a, encodeAwarenessUpdate(b, bClients), BRIDGE);
|
|
69
120
|
|
|
70
121
|
a.on('update', aToB);
|