@1agh/maude 0.30.0 → 0.31.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 (90) hide show
  1. package/README.md +5 -5
  2. package/apps/studio/acp/bridge.ts +285 -0
  3. package/apps/studio/acp/env.ts +48 -0
  4. package/apps/studio/acp/index.ts +132 -0
  5. package/apps/studio/acp/probe.ts +112 -0
  6. package/apps/studio/acp/transcript.ts +149 -0
  7. package/apps/studio/annotations-layer.tsx +6 -1
  8. package/apps/studio/api.ts +225 -66
  9. package/apps/studio/bin/chat-open.sh +44 -0
  10. package/apps/studio/canvas-lib.tsx +112 -19
  11. package/apps/studio/canvas-list-watch.ts +177 -0
  12. package/apps/studio/canvas-shell.tsx +22 -2
  13. package/apps/studio/client/app.jsx +781 -26
  14. package/apps/studio/client/canvas-url.js +5 -0
  15. package/apps/studio/client/github.js +99 -0
  16. package/apps/studio/client/panels/ChatPanel.jsx +770 -0
  17. package/apps/studio/client/panels/CollabModelInfographic.jsx +71 -0
  18. package/apps/studio/client/panels/CreateProject.jsx +334 -0
  19. package/apps/studio/client/panels/DiffView.jsx +590 -0
  20. package/apps/studio/client/panels/GitPanel.jsx +767 -0
  21. package/apps/studio/client/panels/IdentityBar.jsx +294 -0
  22. package/apps/studio/client/panels/OnboardingWizard.jsx +563 -0
  23. package/apps/studio/client/panels/RepoBranchSwitcher.jsx +349 -0
  24. package/apps/studio/client/panels/acp-runtime.js +286 -0
  25. package/apps/studio/client/panels/chat-markdown.jsx +138 -0
  26. package/apps/studio/client/panels/git-grouping.js +86 -0
  27. package/apps/studio/client/styles/0-reset.css +4 -0
  28. package/apps/studio/client/styles/3-shell-maude.css +613 -3
  29. package/apps/studio/client/styles/5-maude-overrides.css +25 -0
  30. package/apps/studio/client/styles/6-acp-chat.css +771 -0
  31. package/apps/studio/client/styles/_index.css +2 -0
  32. package/apps/studio/client/tour/collab-tour.js +61 -0
  33. package/apps/studio/client/tour/overlay.jsx +11 -1
  34. package/apps/studio/client/whats-new.jsx +25 -10
  35. package/apps/studio/collab/registry.ts +13 -0
  36. package/apps/studio/collab/room.ts +36 -0
  37. package/apps/studio/cursors-overlay.tsx +17 -1
  38. package/apps/studio/dist/client.bundle.js +28780 -1534
  39. package/apps/studio/dist/comment-mount.js +4 -2
  40. package/apps/studio/dist/styles.css +5633 -1617
  41. package/apps/studio/git/endpoints.ts +338 -0
  42. package/apps/studio/git/service.ts +1334 -0
  43. package/apps/studio/git/watch.ts +97 -0
  44. package/apps/studio/github/endpoints.ts +358 -0
  45. package/apps/studio/github/service.ts +231 -0
  46. package/apps/studio/github/token.ts +53 -0
  47. package/apps/studio/hmr-broadcast.ts +9 -2
  48. package/apps/studio/http.ts +384 -1
  49. package/apps/studio/participants-chrome.tsx +69 -9
  50. package/apps/studio/paths.ts +12 -0
  51. package/apps/studio/scaffold-design.ts +57 -0
  52. package/apps/studio/server.ts +65 -2
  53. package/apps/studio/sync/agent.ts +81 -1
  54. package/apps/studio/sync/codec.ts +24 -0
  55. package/apps/studio/sync/cold-start.ts +40 -0
  56. package/apps/studio/sync/hub-link.ts +137 -0
  57. package/apps/studio/test/acp-bridge.test.ts +127 -0
  58. package/apps/studio/test/acp-env.test.ts +65 -0
  59. package/apps/studio/test/acp-origin-gate.test.ts +78 -0
  60. package/apps/studio/test/acp-transcript.test.ts +112 -0
  61. package/apps/studio/test/canvas-create-api.test.ts +72 -0
  62. package/apps/studio/test/canvas-list-watch.test.ts +322 -0
  63. package/apps/studio/test/canvas-meta-api.test.ts +161 -27
  64. package/apps/studio/test/canvas-origin-gate.test.ts +35 -0
  65. package/apps/studio/test/chat-markdown.test.tsx +58 -0
  66. package/apps/studio/test/collab-session-survival.test.tsx +176 -0
  67. package/apps/studio/test/csrf-write-guard.test.ts +26 -0
  68. package/apps/studio/test/editing-presence.test.ts +103 -0
  69. package/apps/studio/test/fixtures/mock-acp-agent.mjs +45 -0
  70. package/apps/studio/test/git-api.test.ts +0 -0
  71. package/apps/studio/test/git-branches.test.ts +106 -0
  72. package/apps/studio/test/git-grouping.test.ts +106 -0
  73. package/apps/studio/test/git-watch.test.ts +97 -0
  74. package/apps/studio/test/github-api.test.ts +465 -0
  75. package/apps/studio/test/hub-link.test.ts +69 -0
  76. package/apps/studio/test/participants-chrome.test.ts +36 -1
  77. package/apps/studio/test/sync-cold-seed-dedup.test.ts +187 -0
  78. package/apps/studio/test/sync-cold-start.test.ts +61 -1
  79. package/apps/studio/test/tour-overlay.test.tsx +18 -0
  80. package/apps/studio/tool-palette.tsx +18 -9
  81. package/apps/studio/use-chrome-visibility.tsx +66 -0
  82. package/apps/studio/use-collab.tsx +414 -187
  83. package/apps/studio/whats-new.json +73 -0
  84. package/apps/studio/ws.ts +44 -1
  85. package/cli/commands/design.mjs +1 -0
  86. package/cli/lib/gitignore-block.mjs +16 -3
  87. package/cli/lib/gitignore-block.test.mjs +13 -1
  88. package/package.json +11 -9
  89. package/plugins/design/dependencies.json +17 -0
  90. package/plugins/design/templates/_shell.html +30 -8
@@ -30,4 +30,6 @@
30
30
  @import url('./4-components-maude.css');
31
31
  /* Plan B — maude redesign of remaining legacy shell surfaces (HelpModal, tour, what's-new, SystemView, CommentBar). */
32
32
  @import url('./5-maude-overrides.css');
33
+ /* Phase 31 (DDR-123) — ACP chat sidepanel (chat-* inside the Assistant st-rpanel). */
34
+ @import url('./6-acp-chat.css');
33
35
  @import url('./5-utilities.css');
@@ -0,0 +1,61 @@
1
+ // collab-tour.js — the version-control "rychlý kurz" (Phase 29, epic E4).
2
+ //
3
+ // Teaches the two-layer collab model on the EXISTING tour engine (overlay.jsx) —
4
+ // never a fork. Per the teaching-model DDR (this phase REVERSES the "hide the
5
+ // duality" recommendation in collab-model-design.md § Part 2): we surface a visible
6
+ // three-verb action-cycle and teach the live-vs-async split honestly, because the
7
+ // cycle has to match real buttons in the app and pure-hiding is leaky.
8
+ //
9
+ // Step 1 is a centered, target-less step whose graphic is the two-layer infographic
10
+ // (the engine already centers target-less steps; the `render` field is the only
11
+ // addition). Steps 2-4 spotlight the real action controls via stable [data-tour]
12
+ // anchors (save-local / publish / pull) — `changes: true` opens the Changes panel
13
+ // first so the buttons exist. Step 5 points at the live presence layer; step 6 is
14
+ // the one honest hard thing, centered.
15
+ //
16
+ // Vocabulary is the canonical set ONLY (teaching-model table) — no raw git terms:
17
+ // Save changes locally · Publish for everyone · Pull changes · Draft · Shared version.
18
+
19
+ import CollabModelInfographic from '../panels/CollabModelInfographic.jsx';
20
+
21
+ export const COLLAB_TOUR = [
22
+ {
23
+ // Centered, no target — the engine renders target-less steps centered, and the
24
+ // `render` field draws the infographic above the copy.
25
+ render: CollabModelInfographic,
26
+ title: 'Working together, in one picture',
27
+ body: 'There are two layers. Up top, being together just happens. Below, your work moves through three simple steps. Let’s walk them.',
28
+ },
29
+ {
30
+ target: "[data-tour='save-local']",
31
+ changes: true,
32
+ title: 'Save changes locally',
33
+ body: 'When something looks right, save it. That keeps a version on your computer you can always come back to — like a checkpoint, just for you.',
34
+ placement: 'left',
35
+ },
36
+ {
37
+ target: "[data-tour='publish']",
38
+ changes: true,
39
+ title: 'Publish for everyone',
40
+ body: 'Ready to share? Publish sends your saved work to the whole team. Think of it as putting your version on the shared shelf.',
41
+ placement: 'left',
42
+ },
43
+ {
44
+ target: "[data-tour='pull']",
45
+ changes: true,
46
+ title: 'Pull changes',
47
+ body: 'When teammates publish, Pull brings their work onto your computer so you’re both looking at the same thing.',
48
+ placement: 'left',
49
+ },
50
+ {
51
+ target: "[data-tour='status']",
52
+ title: 'Being together is automatic',
53
+ body: 'Cursors, who’s here, and comments need no buttons at all. When you’re both in a canvas, you see each other live — that’s the top layer, always on.',
54
+ placement: 'bottom',
55
+ },
56
+ {
57
+ // Centered — the one honest hard thing.
58
+ title: 'The one thing worth knowing',
59
+ body: 'When you’re here together, publishing already covers your teammate — they’ve seen it live, so Publish is just dropping a bookmark. Only when you work apart can two versions drift. If that happens, Maude shows you both and lets you pick — keep mine, keep theirs, or keep both. Never a confusing merge.',
60
+ },
61
+ ];
@@ -225,6 +225,11 @@ export function TourOverlay({ steps, open, onClose, onComplete, bus, hasSelectio
225
225
  const needSelHint = !!step.requireSelection && !hasSelection;
226
226
  const needCanvasHint = (!!step.canvas || !!step.requireSelection) && hasCanvas === false;
227
227
 
228
+ // Optional per-step graphic (Phase 29 / E4): a component drawn above the copy —
229
+ // the collab tour uses it for the centered two-layer infographic step. Widens the
230
+ // card so the diagram has room.
231
+ const Graphic = step.render || null;
232
+
228
233
  return (
229
234
  <div className="mdcc-tour" role="presentation">
230
235
  {spot ? (
@@ -233,7 +238,7 @@ export function TourOverlay({ steps, open, onClose, onComplete, bus, hasSelectio
233
238
  <div className="mdcc-tour__scrim" aria-hidden="true" />
234
239
  )}
235
240
  <div
236
- className="mdcc-tour__card"
241
+ className={'mdcc-tour__card' + (Graphic ? ' mdcc-tour__card--graphic' : '')}
237
242
  ref={cardRef}
238
243
  role="dialog"
239
244
  aria-modal="true"
@@ -244,6 +249,11 @@ export function TourOverlay({ steps, open, onClose, onComplete, bus, hasSelectio
244
249
  <div className="mdcc-tour__step">
245
250
  {i + 1} / {steps.length}
246
251
  </div>
252
+ {Graphic && (
253
+ <div className="mdcc-tour__graphic">
254
+ <Graphic />
255
+ </div>
256
+ )}
247
257
  <div className="mdcc-tour__title" id="mdcc-tour-title">
248
258
  {step.title}
249
259
  </div>
@@ -48,20 +48,35 @@ export function useWhatsNew(currentVersion) {
48
48
  const [toastDismissed, setToastDismissed] = useState(() => readToastDismissed());
49
49
  const [panelOpen, setPanelOpen] = useState(false);
50
50
 
51
+ // Fetch the feed on mount AND whenever the window regains focus (Phase 32 /
52
+ // Task 5). The native shell can sit backgrounded for a long time; an auto-update
53
+ // (or a deploy of the web studio) bumps the feed while we're not looking, so we
54
+ // re-evaluate on focus to surface the ✦ badge without a full reload.
51
55
  useEffect(() => {
52
56
  let cancelled = false;
53
- fetch('/_api/whats-new')
54
- .then((r) => r.json())
55
- .then((j) => {
56
- if (cancelled) return;
57
- setEntries(Array.isArray(j?.entries) ? j.entries : []);
58
- if (typeof j?.version === 'string') setFeedVersion(j.version);
59
- })
60
- .catch(() => {
61
- /* offline / restart — no banner */
62
- });
57
+ const refresh = () => {
58
+ fetch('/_api/whats-new')
59
+ .then((r) => r.json())
60
+ .then((j) => {
61
+ if (cancelled) return;
62
+ setEntries(Array.isArray(j?.entries) ? j.entries : []);
63
+ if (typeof j?.version === 'string') setFeedVersion(j.version);
64
+ })
65
+ .catch(() => {
66
+ /* offline / restart — no banner */
67
+ });
68
+ };
69
+ refresh();
70
+ const onFocus = () => refresh();
71
+ const onVisible = () => {
72
+ if (document.visibilityState === 'visible') refresh();
73
+ };
74
+ window.addEventListener('focus', onFocus);
75
+ document.addEventListener('visibilitychange', onVisible);
63
76
  return () => {
64
77
  cancelled = true;
78
+ window.removeEventListener('focus', onFocus);
79
+ document.removeEventListener('visibilitychange', onVisible);
65
80
  };
66
81
  }, []);
67
82
 
@@ -58,6 +58,14 @@ export interface Registry {
58
58
  * updated stroke set without waiting for a cold-open re-seed.
59
59
  */
60
60
  syncRoomFromAnnotations(slug: string, svg: string): void;
61
+ /**
62
+ * Phase 30 — project agent editing-presence onto a slug's room awareness so
63
+ * it crosses the hub (the loopback `ai-activity` bus event does not). `null`
64
+ * clears it. No-op when no room is live for the slug — a peer that joins
65
+ * later will see the agent on the next ai-activity heartbeat. Soft heads-up;
66
+ * never a lock.
67
+ */
68
+ setAgentEditing(slug: string, state: { name: string; since: number } | null): void;
61
69
  /**
62
70
  * Phase 9 Task 5 — attach the hub-side Awareness (from a sync provider) for
63
71
  * a slug so the Room's Awareness (browser peers) is bridged bidirectionally
@@ -192,6 +200,10 @@ export function createRegistry(callbacks: RoomCallbacks): Registry {
192
200
  }, 'inspector-write');
193
201
  }
194
202
 
203
+ function setAgentEditing(slug: string, state: { name: string; since: number } | null): void {
204
+ rooms.get(slug)?.setAgentEditing(state);
205
+ }
206
+
195
207
  async function flushAll(): Promise<void> {
196
208
  await Promise.all(Array.from(rooms.values(), (r) => r.flush()));
197
209
  }
@@ -227,6 +239,7 @@ export function createRegistry(callbacks: RoomCallbacks): Registry {
227
239
  getDoc,
228
240
  syncRoomFromComments,
229
241
  syncRoomFromAnnotations,
242
+ setAgentEditing,
230
243
  attachHubAwareness,
231
244
  pin,
232
245
  unpin,
@@ -57,6 +57,15 @@ export interface Room {
57
57
  connect(conn: RoomConn): Promise<void>;
58
58
  disconnect(conn: RoomConn): void;
59
59
  receive(conn: RoomConn, payload: Uint8Array): void;
60
+ /**
61
+ * Phase 30 — project an agent's "editing" activity onto this room's awareness
62
+ * using the room's own (otherwise-unused) local awareness slot, so the soft
63
+ * editing-presence crosses the hub to remote peers (the loopback `ai-activity`
64
+ * bus event does not). `null` clears it. Idempotent — a re-set of the same
65
+ * `{name, since}` is a no-op (ai-activity heartbeats re-emit every 10 s).
66
+ * NOT a lock; never blocks a peer.
67
+ */
68
+ setAgentEditing(state: { name: string; since: number } | null): void;
60
69
  /** Force the debounced flush to fire now. DDR-051 §3 (branch-switch). */
61
70
  flush(): Promise<void>;
62
71
  /** Tear down — clears timers + removes awareness. */
@@ -192,6 +201,32 @@ export function createRoom(slug: string, callbacks: RoomCallbacks): Room {
192
201
  if (reply) conn.send(reply);
193
202
  }
194
203
 
204
+ // Phase 30 — agent editing-presence projected onto the room's own awareness
205
+ // slot. The key short-circuits redundant re-sets (ai-activity heartbeats
206
+ // re-emit the same entry every 10 s). The awareness `update` handler above
207
+ // broadcasts the change to local conns AND relays it to the hub via
208
+ // bridgeAwareness, so a remote peer's overlay attributes the edit.
209
+ let agentEditingKey: string | null = null;
210
+ function setAgentEditing(state: { name: string; since: number } | null): void {
211
+ if (destroyed) return;
212
+ const key = state ? `${state.name}${state.since}` : null;
213
+ if (key === agentEditingKey) return;
214
+ agentEditingKey = key;
215
+ if (state === null) {
216
+ awareness.setLocalState(null);
217
+ return;
218
+ }
219
+ awareness.setLocalState({
220
+ name: state.name,
221
+ color: '', // re-derived client-side from name; wire value is discarded
222
+ cursor: null,
223
+ selection: null,
224
+ annotationSelection: [],
225
+ viewport: { x: 0, y: 0, zoom: 1 },
226
+ editing: { since: state.since },
227
+ });
228
+ }
229
+
195
230
  async function destroy(): Promise<void> {
196
231
  destroyed = true;
197
232
  if (flushTimer) {
@@ -211,6 +246,7 @@ export function createRoom(slug: string, callbacks: RoomCallbacks): Room {
211
246
  connect,
212
247
  disconnect,
213
248
  receive,
249
+ setAgentEditing,
214
250
  flush,
215
251
  destroy,
216
252
  size: () => conns.size,
@@ -89,8 +89,15 @@ const CURSOR_CSS = `
89
89
  border-radius: var(--radius-sm, 2px) var(--radius-sm, 2px) 0 0;
90
90
  white-space: nowrap;
91
91
  }
92
+ /* Phase 30 / DDR-120 — soft editing-presence. When a peer (human or a bridged
93
+ * agent) is actively editing this canvas, their cursor pulses gently and the
94
+ * label carries a ✎ mark. A heads-up, never a lock. */
95
+ .dc-cursor--editing .dc-cursor-arrow { animation: dc-cursor-edit-pulse 1.6s ease-in-out infinite; }
96
+ .dc-cursor-edit-mark { margin-left: 3px; opacity: 0.85; }
97
+ @keyframes dc-cursor-edit-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
92
98
  @media (prefers-reduced-motion: reduce) {
93
99
  .dc-cursor { transition: none !important; }
100
+ .dc-cursor--editing .dc-cursor-arrow { animation: none; }
94
101
  }
95
102
  `.trim();
96
103
 
@@ -119,8 +126,12 @@ const Cursor = memo(function Cursor({ peer, viewport }: CursorProps): JSX.Elemen
119
126
  // world → screen: screen = world * zoom + viewport.{x,y}
120
127
  const screenX = peer.cursor.x * viewport.zoom + viewport.x;
121
128
  const screenY = peer.cursor.y * viewport.zoom + viewport.y;
129
+ const editing = !!peer.editing;
122
130
  return (
123
- <div className="dc-cursor" style={{ transform: `translate(${screenX}px, ${screenY}px)` }}>
131
+ <div
132
+ className={`dc-cursor${editing ? ' dc-cursor--editing' : ''}`}
133
+ style={{ transform: `translate(${screenX}px, ${screenY}px)` }}
134
+ >
124
135
  {/* DS colors-presence Pointer — one plain triangle glyph, tinted by its
125
136
  owner (the specimen's exact 24-grid path, no tail/notch). */}
126
137
  <svg
@@ -134,6 +145,11 @@ const Cursor = memo(function Cursor({ peer, viewport }: CursorProps): JSX.Elemen
134
145
  </svg>
135
146
  <div className="dc-cursor-label" style={{ background: peer.color }}>
136
147
  {peer.name}
148
+ {editing && (
149
+ <span className="dc-cursor-edit-mark" aria-hidden="true">
150
+
151
+ </span>
152
+ )}
137
153
  </div>
138
154
  </div>
139
155
  );