@1agh/maude 0.23.0 → 0.24.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 (101) hide show
  1. package/README.md +34 -1
  2. package/cli/bin/maude.mjs +3 -0
  3. package/cli/commands/cache.mjs +181 -0
  4. package/cli/commands/cache.test.mjs +166 -0
  5. package/cli/commands/design-link.test.mjs +32 -2
  6. package/cli/commands/design.mjs +95 -4
  7. package/cli/commands/design.test.mjs +56 -0
  8. package/cli/commands/help.mjs +34 -0
  9. package/cli/commands/hub.mjs +255 -30
  10. package/cli/commands/hub.test.mjs +126 -2
  11. package/cli/commands/init.mjs +3 -0
  12. package/cli/commands/preflight.mjs +11 -0
  13. package/cli/commands/preflight.test.mjs +46 -0
  14. package/cli/commands/scenario-report.mjs +45 -0
  15. package/cli/lib/cache.mjs +407 -0
  16. package/cli/lib/cache.test.mjs +303 -0
  17. package/cli/lib/design-link.mjs +74 -10
  18. package/cli/lib/flow-design-integration.test.mjs +165 -0
  19. package/cli/lib/gitignore-block.mjs +90 -0
  20. package/cli/lib/gitignore-block.test.mjs +123 -0
  21. package/cli/lib/plugin-cli-reachability.test.mjs +56 -0
  22. package/cli/lib/preflight.mjs +41 -10
  23. package/package.json +8 -8
  24. package/plugins/design/dev-server/ai-banner.tsx +2 -2
  25. package/plugins/design/dev-server/annotations-context-toolbar.tsx +349 -112
  26. package/plugins/design/dev-server/annotations-layer.tsx +906 -137
  27. package/plugins/design/dev-server/api.ts +109 -4
  28. package/plugins/design/dev-server/bin/preflight.sh +21 -10
  29. package/plugins/design/dev-server/bin/prep.sh +211 -0
  30. package/plugins/design/dev-server/bin/scenario-report.mjs +209 -0
  31. package/plugins/design/dev-server/bin/screenshot.sh +18 -1
  32. package/plugins/design/dev-server/bin/smoke.sh +94 -11
  33. package/plugins/design/dev-server/canvas-cursors.ts +125 -0
  34. package/plugins/design/dev-server/canvas-icons.tsx +130 -0
  35. package/plugins/design/dev-server/canvas-lib.tsx +25 -21
  36. package/plugins/design/dev-server/canvas-meta.schema.json +20 -0
  37. package/plugins/design/dev-server/canvas-shell.tsx +333 -19
  38. package/plugins/design/dev-server/client/app.jsx +155 -7
  39. package/plugins/design/dev-server/client/styles/3-shell.css +10 -0
  40. package/plugins/design/dev-server/collab/index.ts +87 -9
  41. package/plugins/design/dev-server/collab/persistence.ts +34 -3
  42. package/plugins/design/dev-server/collab/registry.ts +80 -2
  43. package/plugins/design/dev-server/collab/room.ts +21 -8
  44. package/plugins/design/dev-server/context-menu.tsx +167 -23
  45. package/plugins/design/dev-server/context.ts +24 -0
  46. package/plugins/design/dev-server/contextual-toolbar.tsx +7 -7
  47. package/plugins/design/dev-server/dist/client.bundle.js +126 -13
  48. package/plugins/design/dev-server/dist/comment-mount.js +78 -11
  49. package/plugins/design/dev-server/dist/styles.css +16 -0
  50. package/plugins/design/dev-server/equal-spacing-handles.tsx +1 -1
  51. package/plugins/design/dev-server/export-dialog.tsx +19 -17
  52. package/plugins/design/dev-server/fs-watch.ts +1 -0
  53. package/plugins/design/dev-server/http.ts +260 -20
  54. package/plugins/design/dev-server/input-router.tsx +26 -3
  55. package/plugins/design/dev-server/participants-chrome.tsx +10 -10
  56. package/plugins/design/dev-server/server.ts +123 -1
  57. package/plugins/design/dev-server/sync/agent.ts +95 -0
  58. package/plugins/design/dev-server/sync/codec.ts +155 -0
  59. package/plugins/design/dev-server/sync/connection-state.ts +203 -0
  60. package/plugins/design/dev-server/sync/index.ts +479 -35
  61. package/plugins/design/dev-server/sync/materialize.ts +62 -0
  62. package/plugins/design/dev-server/sync/migrate-seed.ts +163 -0
  63. package/plugins/design/dev-server/sync/origins.ts +57 -0
  64. package/plugins/design/dev-server/sync/projection.ts +368 -0
  65. package/plugins/design/dev-server/sync/status.ts +115 -0
  66. package/plugins/design/dev-server/sync/untrusted.ts +153 -0
  67. package/plugins/design/dev-server/test/_helpers.ts +6 -2
  68. package/plugins/design/dev-server/test/annotations-layer.test.ts +231 -0
  69. package/plugins/design/dev-server/test/annotations-roundtrip.test.ts +276 -0
  70. package/plugins/design/dev-server/test/canvas-cursors.test.ts +73 -0
  71. package/plugins/design/dev-server/test/canvas-origin-gate.test.ts +76 -0
  72. package/plugins/design/dev-server/test/collab-reseed-guard.test.ts +78 -0
  73. package/plugins/design/dev-server/test/collab-room.test.ts +21 -10
  74. package/plugins/design/dev-server/test/csp-canvas-shell.test.ts +46 -0
  75. package/plugins/design/dev-server/test/fixtures/phase-20-annotations.svg +1 -0
  76. package/plugins/design/dev-server/test/input-router.test.ts +21 -0
  77. package/plugins/design/dev-server/test/sanitize-annotation-svg.test.ts +100 -0
  78. package/plugins/design/dev-server/test/shared-doc-convergence.test.ts +265 -0
  79. package/plugins/design/dev-server/test/shared-doc-foundation.test.ts +63 -0
  80. package/plugins/design/dev-server/test/shared-doc-migrate.test.ts +160 -0
  81. package/plugins/design/dev-server/test/shared-doc-projection.test.ts +238 -0
  82. package/plugins/design/dev-server/test/sync-connection-state.test.ts +146 -0
  83. package/plugins/design/dev-server/test/sync-meta-codec.test.ts +123 -0
  84. package/plugins/design/dev-server/test/sync-runtime.test.ts +531 -4
  85. package/plugins/design/dev-server/test/sync-status.test.ts +80 -0
  86. package/plugins/design/dev-server/test/sync-untrusted.test.ts +104 -0
  87. package/plugins/design/dev-server/test/use-tool-mode.test.tsx +38 -10
  88. package/plugins/design/dev-server/tool-palette.tsx +18 -16
  89. package/plugins/design/dev-server/undo-hud.tsx +4 -4
  90. package/plugins/design/dev-server/undo-stack.ts +20 -4
  91. package/plugins/design/dev-server/use-annotation-resize.tsx +16 -5
  92. package/plugins/design/dev-server/use-tool-mode.tsx +15 -12
  93. package/plugins/design/dev-server/ws.ts +40 -1
  94. package/plugins/design/templates/_shell.html +49 -1
  95. package/plugins/design/templates/canvas.tsx.template +13 -0
  96. package/plugins/design/templates/design-system-inspiration/SUB-AGENT-PROMPTS.md +14 -7
  97. package/plugins/flow/.claude-plugin/config.schema.json +5 -0
  98. package/plugins/flow/templates/ai-skeleton/README.md +1 -1
  99. package/plugins/flow/templates/ai-skeleton/gitignore +10 -0
  100. package/plugins/flow/templates/ai-skeleton/scenarios/README.md +3 -1
  101. package/plugins/flow/templates/ai-skeleton/workflows.config.json +2 -1
@@ -0,0 +1,203 @@
1
+ // Hub connection state machine — Phase 9 Task 8 (hub-down offline mode).
2
+ //
3
+ // The sync runtime owns one monitor for the whole linked session. Each
4
+ // HocuspocusProvider reports its WS status ('connected' | 'connecting' |
5
+ // 'disconnected'); the monitor aggregates them into a single hub-reachability
6
+ // view and drives the user-facing offline UX:
7
+ //
8
+ // online — hub reachable, edits flow live.
9
+ // connecting — WS dropped, inside the reconnect grace window. No banner yet
10
+ // (transient blips shouldn't flash UI).
11
+ // offline — still not reconnected after graceMs (default 30s). Local
12
+ // edits keep working + queue; yellow banner.
13
+ // offline-long — offline > escalateMs (default 24h). Red banner: "consider
14
+ // git commit && push as backup".
15
+ //
16
+ // On reconnect from any offline state the monitor emits a transient `flash:
17
+ // 'synced'` (green, flashMs default 3s) then settles back to online with
18
+ // queuedOps reset (the provider replays the buffered ops to the hub).
19
+ //
20
+ // Timers + clock are injectable so the state machine is fully unit-testable
21
+ // without real wall-clock waits.
22
+
23
+ export type ProviderStatus = 'connected' | 'connecting' | 'disconnected';
24
+ export type SyncState = 'online' | 'connecting' | 'offline' | 'offline-long';
25
+
26
+ export interface SyncStatusSnapshot {
27
+ state: SyncState;
28
+ /** Local edits made since the hub went unreachable (replayed on reconnect). */
29
+ queuedOps: number;
30
+ /** ms epoch of the last successful hub sync, or null if never synced. */
31
+ lastSyncAt: number | null;
32
+ /** ms epoch the current offline streak began, or null when online. */
33
+ offlineSince: number | null;
34
+ /** Transient 'synced' signal (green flash) right after a reconnect. */
35
+ flash: 'synced' | null;
36
+ /** ms epoch this snapshot was produced. */
37
+ updatedAt: number;
38
+ }
39
+
40
+ type TimerHandle = ReturnType<typeof setTimeout>;
41
+
42
+ export interface ConnectionMonitorOptions {
43
+ /** ms disconnected before declaring offline. Default 30_000. */
44
+ graceMs?: number;
45
+ /** ms offline before escalating to offline-long. Default 24h. */
46
+ escalateMs?: number;
47
+ /** ms the green "synced" flash stays up after reconnect. Default 3_000. */
48
+ flashMs?: number;
49
+ now?: () => number;
50
+ setTimer?: (cb: () => void, ms: number) => TimerHandle;
51
+ clearTimer?: (h: TimerHandle) => void;
52
+ /** Called on every observable state change (including flash set/clear). */
53
+ onChange?: (snapshot: SyncStatusSnapshot) => void;
54
+ }
55
+
56
+ export interface ConnectionMonitor {
57
+ /** Aggregate a single provider's status into the session view. */
58
+ noteProviderStatus(providerId: string, status: ProviderStatus): void;
59
+ /** A local edit happened — counts toward queuedOps while not online. */
60
+ noteLocalEdit(): void;
61
+ /** Current snapshot (defensive copy). */
62
+ snapshot(): SyncStatusSnapshot;
63
+ /** Tear down timers. */
64
+ stop(): void;
65
+ }
66
+
67
+ const DEFAULT_GRACE_MS = 30_000;
68
+ const DEFAULT_ESCALATE_MS = 24 * 60 * 60 * 1000;
69
+ const DEFAULT_FLASH_MS = 3_000;
70
+
71
+ export function createConnectionMonitor(opts: ConnectionMonitorOptions = {}): ConnectionMonitor {
72
+ const graceMs = opts.graceMs ?? DEFAULT_GRACE_MS;
73
+ const escalateMs = opts.escalateMs ?? DEFAULT_ESCALATE_MS;
74
+ const flashMs = opts.flashMs ?? DEFAULT_FLASH_MS;
75
+ const now = opts.now ?? Date.now;
76
+ const setTimer = opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
77
+ const clearTimer = opts.clearTimer ?? ((h) => clearTimeout(h));
78
+ const onChange = opts.onChange;
79
+
80
+ const providerStatuses = new Map<string, ProviderStatus>();
81
+
82
+ let state: SyncState = 'online';
83
+ let queuedOps = 0;
84
+ let lastSyncAt: number | null = null;
85
+ let offlineSince: number | null = null;
86
+ let flash: 'synced' | null = null;
87
+
88
+ let graceTimer: TimerHandle | null = null;
89
+ let escalateTimer: TimerHandle | null = null;
90
+ let flashTimer: TimerHandle | null = null;
91
+ let stopped = false;
92
+
93
+ function snapshot(): SyncStatusSnapshot {
94
+ return { state, queuedOps, lastSyncAt, offlineSince, flash, updatedAt: now() };
95
+ }
96
+
97
+ function emit(): void {
98
+ onChange?.(snapshot());
99
+ }
100
+
101
+ function clearAllTimers(): void {
102
+ if (graceTimer !== null) {
103
+ clearTimer(graceTimer);
104
+ graceTimer = null;
105
+ }
106
+ if (escalateTimer !== null) {
107
+ clearTimer(escalateTimer);
108
+ escalateTimer = null;
109
+ }
110
+ }
111
+
112
+ /** Aggregate provider statuses: connected if ANY is connected, else
113
+ * connecting if ANY is connecting, else disconnected. An empty map (no
114
+ * providers yet) is treated as connecting (boot, pre-handshake). */
115
+ function aggregate(): ProviderStatus {
116
+ if (providerStatuses.size === 0) return 'connecting';
117
+ let anyConnecting = false;
118
+ for (const s of providerStatuses.values()) {
119
+ if (s === 'connected') return 'connected';
120
+ if (s === 'connecting') anyConnecting = true;
121
+ }
122
+ return anyConnecting ? 'connecting' : 'disconnected';
123
+ }
124
+
125
+ function goOnline(): void {
126
+ const wasOffline = state === 'offline' || state === 'offline-long';
127
+ clearAllTimers();
128
+ state = 'online';
129
+ lastSyncAt = now();
130
+ offlineSince = null;
131
+ queuedOps = 0;
132
+ if (wasOffline) {
133
+ flash = 'synced';
134
+ if (flashTimer !== null) clearTimer(flashTimer);
135
+ flashTimer = setTimer(() => {
136
+ flashTimer = null;
137
+ flash = null;
138
+ if (!stopped) emit();
139
+ }, flashMs);
140
+ }
141
+ emit();
142
+ }
143
+
144
+ function enterGrace(): void {
145
+ // Already counting down or already offline — don't restart the clock.
146
+ if (state !== 'online') return;
147
+ state = 'connecting';
148
+ if (graceTimer !== null) clearTimer(graceTimer);
149
+ graceTimer = setTimer(() => {
150
+ graceTimer = null;
151
+ goOffline();
152
+ }, graceMs);
153
+ emit();
154
+ }
155
+
156
+ function goOffline(): void {
157
+ state = 'offline';
158
+ offlineSince = now();
159
+ if (escalateTimer !== null) clearTimer(escalateTimer);
160
+ escalateTimer = setTimer(() => {
161
+ escalateTimer = null;
162
+ state = 'offline-long';
163
+ emit();
164
+ }, escalateMs);
165
+ emit();
166
+ }
167
+
168
+ return {
169
+ noteProviderStatus(providerId, status) {
170
+ if (stopped) return;
171
+ providerStatuses.set(providerId, status);
172
+ const agg = aggregate();
173
+ if (agg === 'connected') {
174
+ if (state !== 'online') goOnline();
175
+ return;
176
+ }
177
+ // Aggregate is connecting or disconnected → start (or continue) the
178
+ // grace countdown. Once offline/offline-long we stay there until a
179
+ // provider reports 'connected' again.
180
+ if (state === 'online') enterGrace();
181
+ },
182
+
183
+ noteLocalEdit() {
184
+ if (stopped) return;
185
+ // Only count edits made while the hub is unreachable — those are the
186
+ // ones queued for replay. Edits while online flush immediately.
187
+ if (state === 'online') return;
188
+ queuedOps += 1;
189
+ emit();
190
+ },
191
+
192
+ snapshot,
193
+
194
+ stop() {
195
+ stopped = true;
196
+ clearAllTimers();
197
+ if (flashTimer !== null) {
198
+ clearTimer(flashTimer);
199
+ flashTimer = null;
200
+ }
201
+ },
202
+ };
203
+ }