@1agh/maude 0.29.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.
- package/README.md +6 -6
- package/apps/studio/acp/bridge.ts +285 -0
- package/apps/studio/acp/env.ts +48 -0
- package/apps/studio/acp/index.ts +132 -0
- package/apps/studio/acp/probe.ts +112 -0
- package/apps/studio/acp/transcript.ts +149 -0
- package/apps/studio/ai-banner.tsx +0 -1
- package/apps/studio/annotations-align.ts +149 -0
- package/apps/studio/annotations-bindings.ts +197 -0
- package/apps/studio/annotations-context-toolbar.tsx +436 -186
- package/apps/studio/annotations-groups.ts +270 -0
- package/apps/studio/annotations-layer.tsx +1714 -1661
- package/apps/studio/annotations-model.ts +2077 -0
- package/apps/studio/annotations-snap.ts +125 -0
- package/apps/studio/api.ts +433 -182
- package/apps/studio/bin/_png-playwright.mjs +1 -1
- package/apps/studio/bin/annotate.mjs +732 -0
- package/apps/studio/bin/annotate.sh +17 -0
- package/apps/studio/bin/chat-open.sh +44 -0
- package/apps/studio/bin/read-annotations.mjs +152 -17
- package/apps/studio/build.ts +1 -1
- package/apps/studio/canvas-arrowheads.ts +78 -9
- package/apps/studio/canvas-cursors.ts +2 -0
- package/apps/studio/canvas-edit.ts +257 -7
- package/apps/studio/canvas-icons.tsx +105 -0
- package/apps/studio/canvas-lib.tsx +112 -19
- package/apps/studio/canvas-list-watch.ts +177 -0
- package/apps/studio/canvas-shell.tsx +326 -9
- package/apps/studio/client/app.jsx +3579 -250
- package/apps/studio/client/canvas-url.js +5 -0
- package/apps/studio/client/github.js +99 -0
- package/apps/studio/client/index.html +1 -1
- package/apps/studio/client/panels/ChatPanel.jsx +770 -0
- package/apps/studio/client/panels/CollabModelInfographic.jsx +71 -0
- package/apps/studio/client/panels/CreateProject.jsx +334 -0
- package/apps/studio/client/panels/DiffView.jsx +590 -0
- package/apps/studio/client/panels/GitPanel.jsx +767 -0
- package/apps/studio/client/panels/IdentityBar.jsx +294 -0
- package/apps/studio/client/panels/OnboardingWizard.jsx +563 -0
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +349 -0
- package/apps/studio/client/panels/acp-runtime.js +286 -0
- package/apps/studio/client/panels/chat-markdown.jsx +138 -0
- package/apps/studio/client/panels/git-grouping.js +86 -0
- package/apps/studio/client/styles/0-reset.css +4 -0
- package/apps/studio/client/styles/3-shell-maude.css +1326 -14
- package/apps/studio/client/styles/4-components.css +39 -0
- package/apps/studio/client/styles/5-maude-overrides.css +41 -2
- package/apps/studio/client/styles/6-acp-chat.css +771 -0
- package/apps/studio/client/styles/_index.css +2 -0
- package/apps/studio/client/tour/collab-tour.js +61 -0
- package/apps/studio/client/tour/overlay.jsx +116 -18
- package/apps/studio/client/tour/usage-tour.js +23 -10
- package/apps/studio/client/whats-new.jsx +25 -10
- package/apps/studio/collab/registry.ts +13 -0
- package/apps/studio/collab/room.ts +36 -0
- package/apps/studio/commands/annotation-strokes-command.ts +1 -1
- package/apps/studio/commands/move-artboards-command.ts +1 -1
- package/apps/studio/comments-overlay.tsx +7 -5
- package/apps/studio/context-menu.tsx +4 -3
- package/apps/studio/contextual-toolbar.tsx +14 -0
- package/apps/studio/cursors-overlay.tsx +37 -21
- package/apps/studio/dist/client.bundle.js +31795 -1829
- package/apps/studio/dist/comment-mount.js +97 -9
- package/apps/studio/dist/styles.css +7938 -1599
- package/apps/studio/dom-selection.ts +115 -0
- package/apps/studio/export-dialog.tsx +3 -3
- package/apps/studio/git/endpoints.ts +338 -0
- package/apps/studio/git/service.ts +1334 -0
- package/apps/studio/git/watch.ts +97 -0
- package/apps/studio/github/endpoints.ts +358 -0
- package/apps/studio/github/service.ts +231 -0
- package/apps/studio/github/token.ts +53 -0
- package/apps/studio/history.ts +20 -3
- package/apps/studio/hmr-broadcast.ts +9 -2
- package/apps/studio/http.ts +508 -6
- package/apps/studio/input-router.tsx +7 -0
- package/apps/studio/inspect.ts +37 -8
- package/apps/studio/participants-chrome.tsx +70 -16
- package/apps/studio/paths.ts +12 -0
- package/apps/studio/scaffold-design.ts +57 -0
- package/apps/studio/server.ts +70 -4
- package/apps/studio/sync/agent.ts +311 -57
- package/apps/studio/sync/codec.ts +69 -0
- package/apps/studio/sync/cold-start.ts +198 -0
- package/apps/studio/sync/connection-state.ts +58 -2
- package/apps/studio/sync/hub-link.ts +137 -0
- package/apps/studio/sync/index.ts +563 -221
- package/apps/studio/sync/journal.ts +190 -0
- package/apps/studio/sync/migrate-seed.ts +202 -44
- package/apps/studio/sync/projection.ts +22 -3
- package/apps/studio/sync/status.ts +15 -4
- package/apps/studio/test/acp-bridge.test.ts +127 -0
- package/apps/studio/test/acp-env.test.ts +65 -0
- package/apps/studio/test/acp-origin-gate.test.ts +78 -0
- package/apps/studio/test/acp-transcript.test.ts +112 -0
- package/apps/studio/test/activity.test.ts +1 -7
- package/apps/studio/test/annotate-write.test.ts +184 -0
- package/apps/studio/test/annotations-align.test.ts +88 -0
- package/apps/studio/test/annotations-bindings.test.ts +124 -0
- package/apps/studio/test/annotations-groups.test.ts +231 -0
- package/apps/studio/test/annotations-snap.test.ts +79 -0
- package/apps/studio/test/canvas-create-api.test.ts +72 -0
- package/apps/studio/test/canvas-edit.test.ts +181 -1
- package/apps/studio/test/canvas-list-watch.test.ts +322 -0
- package/apps/studio/test/canvas-meta-api.test.ts +161 -27
- package/apps/studio/test/canvas-origin-gate.test.ts +43 -0
- package/apps/studio/test/chat-markdown.test.tsx +58 -0
- package/apps/studio/test/collab-bridge.test.ts +0 -2
- package/apps/studio/test/collab-room.test.ts +2 -7
- package/apps/studio/test/collab-session-survival.test.tsx +176 -0
- package/apps/studio/test/csp-canvas-shell.test.ts +5 -0
- package/apps/studio/test/csrf-write-guard.test.ts +78 -0
- package/apps/studio/test/editing-presence.test.ts +103 -0
- package/apps/studio/test/figjam-v3-model.test.ts +342 -0
- package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +1 -0
- package/apps/studio/test/fixtures/mock-acp-agent.mjs +45 -0
- package/apps/studio/test/git-api.test.ts +0 -0
- package/apps/studio/test/git-branches.test.ts +106 -0
- package/apps/studio/test/git-grouping.test.ts +106 -0
- package/apps/studio/test/git-watch.test.ts +97 -0
- package/apps/studio/test/github-api.test.ts +465 -0
- package/apps/studio/test/hub-link.test.ts +69 -0
- package/apps/studio/test/participants-chrome.test.ts +36 -1
- package/apps/studio/test/shared-doc-migrate.test.ts +224 -14
- package/apps/studio/test/sync-agent.test.ts +272 -2
- package/apps/studio/test/sync-codec.test.ts +65 -0
- package/apps/studio/test/sync-cold-seed-dedup.test.ts +187 -0
- package/apps/studio/test/sync-cold-start.test.ts +304 -0
- package/apps/studio/test/sync-connection-state.test.ts +68 -0
- package/apps/studio/test/sync-hardening.test.ts +0 -8
- package/apps/studio/test/sync-hubs-config.test.ts +0 -1
- package/apps/studio/test/sync-incident-replay.test.ts +211 -0
- package/apps/studio/test/sync-journal.test.ts +176 -0
- package/apps/studio/test/sync-runtime.test.ts +360 -5
- package/apps/studio/test/sync-status.test.ts +66 -0
- package/apps/studio/test/tour-overlay.test.tsx +18 -0
- package/apps/studio/test/use-annotation-resize.test.ts +159 -0
- package/apps/studio/test/use-artboard-drag.test.ts +0 -1
- package/apps/studio/test/use-collab.test.ts +0 -0
- package/apps/studio/test/use-tool-mode.test.tsx +5 -2
- package/apps/studio/tool-palette.tsx +30 -11
- package/apps/studio/undo-hud.tsx +0 -1
- package/apps/studio/use-agent-presence.tsx +4 -2
- package/apps/studio/use-annotation-resize.tsx +484 -54
- package/apps/studio/use-annotation-selection.tsx +16 -4
- package/apps/studio/use-canvas-activity.tsx +2 -2
- package/apps/studio/use-chrome-visibility.tsx +66 -0
- package/apps/studio/use-collab.tsx +434 -202
- package/apps/studio/use-selection-set.tsx +9 -0
- package/apps/studio/use-tool-mode.tsx +5 -1
- package/apps/studio/whats-new.json +208 -10
- package/apps/studio/whats-new.schema.json +18 -2
- package/apps/studio/ws.ts +44 -1
- package/cli/commands/design-link.test.mjs +84 -0
- package/cli/commands/design.mjs +3 -1
- package/cli/commands/design.test.mjs +5 -1
- package/cli/lib/design-link.mjs +51 -1
- package/cli/lib/gitignore-block.mjs +16 -3
- package/cli/lib/gitignore-block.test.mjs +13 -1
- package/package.json +11 -9
- package/plugins/design/dependencies.json +17 -0
- package/plugins/design/templates/_shell.html +58 -12
|
@@ -61,6 +61,15 @@ export interface Selection {
|
|
|
61
61
|
dom_path?: string[];
|
|
62
62
|
bounds?: { x: number; y: number; w: number; h: number } | null;
|
|
63
63
|
html?: string;
|
|
64
|
+
/** Phase 12.2 — authored inline-style values (knob pre-fill) + resolved computed (placeholder hint). */
|
|
65
|
+
authored?: Record<string, string>;
|
|
66
|
+
computed?: Record<string, string>;
|
|
67
|
+
/** Phase 12.3 — authored inline-style props OUTSIDE the curated knob set, so the
|
|
68
|
+
* panel can surface a custom CSS property the user added (e.g. `letter-spacing`). */
|
|
69
|
+
customStyles?: Record<string, string>;
|
|
70
|
+
/** Phase 12.3 — custom HTML attributes on the element (data-, aria-, role, title…),
|
|
71
|
+
* so the panel reflects a custom attribute the user added via the escape hatch. */
|
|
72
|
+
attrs?: Record<string, string>;
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
interface SelectionSetValue {
|
|
@@ -58,6 +58,8 @@ export const DEFAULT_TOOLS: readonly ToolDescriptor[] = Object.freeze([
|
|
|
58
58
|
// buttons; the primitive is chosen from the palette popover.
|
|
59
59
|
{ id: 'shape', label: 'Shape', shortcut: 'R', cursor: TOOL_CURSORS.shape },
|
|
60
60
|
{ id: 'sticky', label: 'Sticky', shortcut: 'N', cursor: TOOL_CURSORS.sticky },
|
|
61
|
+
// FigJam v3 — labelled organizing container.
|
|
62
|
+
{ id: 'section', label: 'Section', shortcut: '⇧S', cursor: TOOL_CURSORS.shape },
|
|
61
63
|
{ id: 'arrow', label: 'Arrow', shortcut: 'A', cursor: TOOL_CURSORS.arrow },
|
|
62
64
|
{ id: 'text', label: 'Text', shortcut: 'T', cursor: TOOL_CURSORS.text },
|
|
63
65
|
{ id: 'eraser', label: 'Eraser', shortcut: 'E', cursor: TOOL_CURSORS.eraser },
|
|
@@ -114,7 +116,9 @@ export function ToolProvider({
|
|
|
114
116
|
const clearSticky = useCallback(() => {
|
|
115
117
|
setSticky({ tool: null, locked: false });
|
|
116
118
|
}, []);
|
|
117
|
-
|
|
119
|
+
// FigJam v3 — soft default: a fresh Shape tool draws ROUNDED squares (the
|
|
120
|
+
// FigJam look); sharp squares stay one popover click away.
|
|
121
|
+
const [shapeKind, setShapeKind] = useState<ShapeKind>('rounded');
|
|
118
122
|
|
|
119
123
|
// Cursor sync — applied inside the canvas (this hook runs in the canvas
|
|
120
124
|
// context). The active tool's cursor is set on <body> AND forced across the
|
|
@@ -1,6 +1,197 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./whats-new.schema.json",
|
|
3
3
|
"entries": [
|
|
4
|
+
{
|
|
5
|
+
"id": "native-desktop-app",
|
|
6
|
+
"version": "0.31.0",
|
|
7
|
+
"date": "2026-06-23",
|
|
8
|
+
"kind": "feature",
|
|
9
|
+
"title": "Maude is now a desktop app",
|
|
10
|
+
"summary": "Install Maude for macOS or Windows — download, open, sign in with GitHub, and start designing, with no terminal at any step. It keeps itself up to date in the background: when a new version is ready you'll see “Maude updated · restart to apply,” and one click puts you on the latest. No npm, no command line.",
|
|
11
|
+
"learnMore": "https://maude.sh/desktop",
|
|
12
|
+
"surface": "design-ui"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "native-acp-chat-sidepanel",
|
|
16
|
+
"version": "0.31.0",
|
|
17
|
+
"date": "2026-06-23",
|
|
18
|
+
"kind": "feature",
|
|
19
|
+
"title": "Chat with Claude inside Maude",
|
|
20
|
+
"summary": "Open the Assistant panel (the ✦ in the menubar, or ⌘⇧A) and ask Claude for an edit, a critique, or a new screen — it changes the canvas while you watch, no terminal needed. It runs on your own installed Claude on your Pro/Max subscription: no login inside Maude, never metered API billing. Open several chats and work on different things at once — each runs in the background, so switching never interrupts a running one, and a switcher shows which chats are running, open, or saved (start a new one or delete an old one). Chats are saved per project and reopen with their history. Pick the model and effort, watch what Claude is doing live, and get a notification when a turn finishes while you're looking elsewhere. Native app only.",
|
|
21
|
+
"surface": "design-ui"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "branch-scoped-live-multiplayer",
|
|
25
|
+
"version": "0.31.0",
|
|
26
|
+
"date": "2026-06-23",
|
|
27
|
+
"kind": "feature",
|
|
28
|
+
"title": "Work together, live, in the same version",
|
|
29
|
+
"summary": "When you and a teammate are in the same shared version, you now collaborate live — you see each other's cursors, annotations, comments, and each other's canvas changes as they happen, like Figma. Your canvas list shows only the version you're in, so there's no clutter; switch to a teammate's draft to join them. When someone — or the AI agent — is editing a canvas, a soft badge in their colour gives you a heads-up so you don't step on each other (it's never a lock, and you can always keep working). The AI's \"is editing\" cue now reaches everyone on the team, new canvases show up instantly in a second tab and via Get latest for teammates, and live updates now slot in seamlessly without the presence avatars blinking.",
|
|
30
|
+
"surface": "design-ui"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "native-onboarding-switcher-collab-tour",
|
|
34
|
+
"version": "0.31.0",
|
|
35
|
+
"date": "2026-06-23",
|
|
36
|
+
"kind": "feature",
|
|
37
|
+
"title": "Get started & switch projects — no terminal",
|
|
38
|
+
"summary": "Open Maude for the first time and a friendly setup walks you in: sign in with GitHub to open or create a shared project, pick a local folder, or connect to a team hub — no terminal, ever. A new switcher at the bottom of the sidebar lets you jump between projects and between the Shared version and your own drafts, all in plain words. Happy with a draft? \"Add this draft to the Shared version\" folds it in, publishes, and tidies up in one click. And a re-openable \"How sharing works\" tour teaches the simple Save changes locally → Publish for everyone → Pull changes cycle — with a picture showing that being in a canvas together just happens, automatically.",
|
|
39
|
+
"surface": "design-ui"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "present-mode-and-chrome-toggles",
|
|
43
|
+
"version": "0.31.0",
|
|
44
|
+
"date": "2026-06-23",
|
|
45
|
+
"kind": "feature",
|
|
46
|
+
"title": "Presentation Mode — just your artboards",
|
|
47
|
+
"summary": "View ▸ Presentation Mode now clears everything but your work: the menubar, sidebar, and panels disappear along with the mini-map, zoom controls, tool palette, annotations, and comment pins — leaving a clean canvas perfect for a screenshot or a demo. Press Esc or the floating Exit button to bring it all back. The presentation button in the canvas toolbar enters it too. Prefer to hide just the floating extras? Two new View toggles — Minimap and Zoom controls — switch those off on their own. Nothing is lost: leaving Presentation Mode restores exactly what you had.",
|
|
48
|
+
"surface": "design-ui"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "sign-in-with-github",
|
|
52
|
+
"version": "0.31.0",
|
|
53
|
+
"date": "2026-06-23",
|
|
54
|
+
"kind": "feature",
|
|
55
|
+
"title": "Sign in with GitHub — projects without the terminal",
|
|
56
|
+
"summary": "Sign in with GitHub right from the account bar (just enter a short code — your access stays in your computer's keychain). Start a New project and Maude creates a private GitHub project and opens it; Pull a local copy brings down one of your projects to a folder you choose; Share invites a teammate by username. Publish and Get latest now use your sign-in directly, and you'll get a nudge when a teammate has shared new work. If you both changed the same canvas, Get latest opens a visual resolver — Keep mine, theirs, or both (both saves your version as a copy, so nothing is ever lost) — and finishes the merge for you.",
|
|
57
|
+
"surface": "design-ui"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "changes-panel-canvas-grouping",
|
|
61
|
+
"version": "0.31.0",
|
|
62
|
+
"date": "2026-06-23",
|
|
63
|
+
"kind": "improvement",
|
|
64
|
+
"title": "A cleaner Changes panel, grouped by canvas",
|
|
65
|
+
"summary": "Panning or zooming a canvas no longer counts as a change to save — the Changes panel now reflects your actual work, not your mouse. And changes are grouped by canvas: each one lists its supporting files (Layout & settings, Annotations) tucked underneath, so a single checkbox saves a canvas and everything that travels with it as one unit.",
|
|
66
|
+
"surface": "design-ui"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": "in-ui-git-layer",
|
|
70
|
+
"version": "0.31.0",
|
|
71
|
+
"date": "2026-06-23",
|
|
72
|
+
"kind": "feature",
|
|
73
|
+
"title": "See, save & publish — no terminal",
|
|
74
|
+
"summary": "A new Changes panel (View ▸ Changes, or ⌘⇧G) shows exactly what you've changed, with live colour-coded badges right in the file tree. Save a version with a message, Publish it to share with your team, or Get the latest they published — all in plain language, never a command line. And when a canvas changed, compare a real rendered before & after: both sides live with locked zoom & pan, an overlay wipe, and a simple Keep mine / theirs / both picker so nothing is ever lost. Open History to browse every saved version of a canvas and preview any one of them — and pick which past version to compare against, right in the diff.",
|
|
75
|
+
"surface": "design-ui"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "in-canvas-token-editor",
|
|
79
|
+
"version": "0.30.0",
|
|
80
|
+
"date": "2026-06-12",
|
|
81
|
+
"kind": "feature",
|
|
82
|
+
"title": "A real token editor, right in the canvas",
|
|
83
|
+
"summary": "The Inspector's CSS tab grew into a Figma-style design-token editor. Numeric fields carry a property glyph and scrub left/right to change; spacing fields take Webflow-style modifiers. Any value can bind to a design-system Variable through a searchable popover that shows where the token is defined — and it resolves the right system in multi-DS projects. One unified colour picker now drives every colour field, fill and border alike, with Custom and Variables tabs. Values you've set stand out while untouched rows recede, so the diff is scannable at a glance.",
|
|
84
|
+
"surface": "design-ui",
|
|
85
|
+
"tour": [
|
|
86
|
+
{
|
|
87
|
+
"target": "[data-tour='inspector-tabs']",
|
|
88
|
+
"inspector": true,
|
|
89
|
+
"tab": "css",
|
|
90
|
+
"title": "Inspect · Layers · CSS",
|
|
91
|
+
"body": "Here's the Inspector — opened to its CSS tab. It reads and writes your maude design system itself, not a generic :root. Three tabs: Inspect, Layers, CSS.",
|
|
92
|
+
"placement": "left"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"target": "[data-tour='css-panel']",
|
|
96
|
+
"inspector": true,
|
|
97
|
+
"tab": "css",
|
|
98
|
+
"requireSelection": true,
|
|
99
|
+
"title": "Your real element, live",
|
|
100
|
+
"body": "Hold ⌘ and click any element in the canvas. The CSS panel fills with its real, editable properties — and every change writes back to the source on blur, no AI round-trip.",
|
|
101
|
+
"placement": "left"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"target": ".st-cp-row",
|
|
105
|
+
"inspector": true,
|
|
106
|
+
"tab": "css",
|
|
107
|
+
"requireSelection": true,
|
|
108
|
+
"title": "Figma-style property rows",
|
|
109
|
+
"body": "Each row leads with a property glyph. Drag a numeric field left or right to scrub the value, or just type it; spacing fields accept Webflow-style modifiers.",
|
|
110
|
+
"placement": "left"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"target": ".st-cp-tokbtn",
|
|
114
|
+
"inspector": true,
|
|
115
|
+
"tab": "css",
|
|
116
|
+
"requireSelection": true,
|
|
117
|
+
"title": "Bind to a design token",
|
|
118
|
+
"body": "Click a field's token chip for the Variables popover — Custom for a literal, Variables for the design system's tokens. Search the list; each Variable shows its provenance. One unified colour picker drives every colour field, fill and border alike.",
|
|
119
|
+
"placement": "left"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"target": ".st-cp-sechd-row",
|
|
123
|
+
"inspector": true,
|
|
124
|
+
"tab": "css",
|
|
125
|
+
"requireSelection": true,
|
|
126
|
+
"title": "Scannable, resettable",
|
|
127
|
+
"body": "Values you've set stand out; untouched rows recede so the difference from the default reads at a glance. The leading dot on each row carries both its status and its reset.",
|
|
128
|
+
"placement": "left"
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"id": "in-canvas-direct-edit",
|
|
134
|
+
"version": "0.30.0",
|
|
135
|
+
"date": "2026-06-12",
|
|
136
|
+
"kind": "feature",
|
|
137
|
+
"title": "Edit the canvas directly — text & layers",
|
|
138
|
+
"summary": "Double-click any text to retype it in place — the new copy is written straight back to the source, no AI round-trip. The Layers tab is a browsable tree with type icons: click a layer to select it in the canvas, hover to highlight, and toggle any layer's visibility from its row. Together with the CSS token editor, small tweaks no longer need a command.",
|
|
139
|
+
"surface": "design-ui",
|
|
140
|
+
"tour": [
|
|
141
|
+
{
|
|
142
|
+
"target": "[data-tour='inspector-tabs']",
|
|
143
|
+
"inspector": true,
|
|
144
|
+
"tab": "layers",
|
|
145
|
+
"requireSelection": true,
|
|
146
|
+
"title": "Browsable layers",
|
|
147
|
+
"body": "⌘-click an element, then this Layers tab shows the artboard's elements as a tree with type icons — click a row to select it in the canvas, hover to highlight.",
|
|
148
|
+
"placement": "left"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"target": ".st-layer-eye",
|
|
152
|
+
"inspector": true,
|
|
153
|
+
"tab": "layers",
|
|
154
|
+
"requireSelection": true,
|
|
155
|
+
"title": "Toggle visibility",
|
|
156
|
+
"body": "The eye on each layer row hides or shows that element while you work — handy for seeing what sits underneath. Double-click any text in the canvas to retype it in place too.",
|
|
157
|
+
"placement": "left"
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"id": "studio-chrome-polished-end-to-end",
|
|
163
|
+
"version": "0.30.0",
|
|
164
|
+
"date": "2026-06-12",
|
|
165
|
+
"kind": "feature",
|
|
166
|
+
"title": "Studio chrome, polished end to end",
|
|
167
|
+
"summary": "The whole browser feels hand-finished now. Drag the file and inspector panels to resize them (or nudge with the arrow keys — your widths are remembered), watch a calm skeleton while a canvas compiles, and press ? for a full keyboard-shortcuts cheat sheet. Collaborator cursors and Claude's own cursor now match the design system, annotations snap to the dot grid as you drag, and focus rings, tooltips and scrollbars are consistent throughout. The View menu's Layers and Zoom are wired up, and a shortcut clash is gone — the inspector is ⌘⇧I, so I stays free for the canvas highlighter.",
|
|
168
|
+
"surface": "design-ui"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"id": "hub-sync-cold-start-safety",
|
|
172
|
+
"version": "0.30.0",
|
|
173
|
+
"date": "2026-06-12",
|
|
174
|
+
"kind": "feature",
|
|
175
|
+
"title": "Hub sync that can't eat your work",
|
|
176
|
+
"summary": "Boot two machines or checkouts linked to the same hub in any order — your local canvas work is safe. When both sides changed the same canvas, Maude keeps a copy of each, takes the newer one, and tells you exactly how to bring back the other; a clean catch-up just updates quietly. The sync status is honest now too: you can see which canvases are synced, pending, or rejected, and why.",
|
|
177
|
+
"surface": "design-ui"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"id": "canvas-annotations-figjam-v3",
|
|
181
|
+
"version": "0.30.0",
|
|
182
|
+
"date": "2026-06-12",
|
|
183
|
+
"kind": "feature",
|
|
184
|
+
"title": "Annotations grew up: diagram on the canvas",
|
|
185
|
+
"summary": "Marquee-select strokes and just drag them, group with ⌘G, copy/paste, align and reorder. Shapes rotate (hover near a corner), resize from edges with size-matching guides, hold text (double-click any shape), and switch kind — square, circle, diamond, triangle — right from the toolbar. Arrows snap to shapes and re-route as you move things, and the new Section tool (⇧S) frames a region that moves with its content. AI tools can read the board and draw on it too.",
|
|
186
|
+
"surface": "design-ui",
|
|
187
|
+
"tour": [
|
|
188
|
+
{
|
|
189
|
+
"target": ".dc-tool-palette",
|
|
190
|
+
"title": "New tools in the palette",
|
|
191
|
+
"body": "Section (⇧S) frames a region; the Shape tool's primitives can all hold text now — select a shape and press Enter to type."
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
},
|
|
4
195
|
{
|
|
5
196
|
"id": "studio-full-functionality",
|
|
6
197
|
"version": "0.29.0",
|
|
@@ -11,14 +202,17 @@
|
|
|
11
202
|
"surface": "design-ui",
|
|
12
203
|
"tour": [
|
|
13
204
|
{
|
|
14
|
-
"target": "
|
|
205
|
+
"target": "[data-tour='status']",
|
|
15
206
|
"title": "Live status, finally",
|
|
16
|
-
"body": "Zoom, artboard count, presence and hub-sync now reflect the real state — no more static placeholders."
|
|
207
|
+
"body": "Zoom, artboard count, presence and hub-sync now reflect the real state — no more static placeholders.",
|
|
208
|
+
"placement": "bottom"
|
|
17
209
|
},
|
|
18
210
|
{
|
|
19
|
-
"target": "
|
|
211
|
+
"target": "[data-tour='inspector']",
|
|
212
|
+
"inspector": true,
|
|
20
213
|
"title": "Inspector",
|
|
21
|
-
"body": "⌘-click an element
|
|
214
|
+
"body": "Opened from View → Inspector (I). ⌘-click an element to read its position, size, ancestry and markup — and now edit its CSS against your tokens, live.",
|
|
215
|
+
"placement": "left"
|
|
22
216
|
}
|
|
23
217
|
]
|
|
24
218
|
},
|
|
@@ -41,14 +235,16 @@
|
|
|
41
235
|
"surface": "design-ui",
|
|
42
236
|
"tour": [
|
|
43
237
|
{
|
|
44
|
-
"target": "
|
|
238
|
+
"target": "[data-tour='brand']",
|
|
45
239
|
"title": "Built in maude",
|
|
46
|
-
"body": "The studio chrome now dogfoods the maude DS — one cohesive material across the menubar, sidebar, canvas and status bar."
|
|
240
|
+
"body": "The studio chrome now dogfoods the maude DS — one cohesive material across the menubar, sidebar, canvas and status bar.",
|
|
241
|
+
"placement": "bottom"
|
|
47
242
|
},
|
|
48
243
|
{
|
|
49
|
-
"target": "
|
|
244
|
+
"target": "[data-tour='menus']",
|
|
50
245
|
"title": "Press ⌘K",
|
|
51
|
-
"body": "A
|
|
246
|
+
"body": "A command palette jumps you to the design-system view, comments, theme toggle, reload, and help — no mouse needed.",
|
|
247
|
+
"placement": "bottom"
|
|
52
248
|
}
|
|
53
249
|
]
|
|
54
250
|
},
|
|
@@ -62,9 +258,11 @@
|
|
|
62
258
|
"surface": "design-ui",
|
|
63
259
|
"tour": [
|
|
64
260
|
{
|
|
65
|
-
"target": "
|
|
261
|
+
"target": "[data-tour='viewport']",
|
|
262
|
+
"canvas": true,
|
|
66
263
|
"title": "New: Highlighter",
|
|
67
|
-
"body": "
|
|
264
|
+
"body": "Open a canvas and press P to enter annotate mode, then pick the Highlighter from the toolbar — a translucent marker whose overlapping strokes darken with multiply blend. Sticky notes, shapes and text all take Cmd+B/I/U too.",
|
|
265
|
+
"placement": "auto"
|
|
68
266
|
}
|
|
69
267
|
]
|
|
70
268
|
},
|
|
@@ -59,11 +59,27 @@
|
|
|
59
59
|
"properties": {
|
|
60
60
|
"target": {
|
|
61
61
|
"type": "string",
|
|
62
|
-
"description": "CSS selector or [data-tour=\"<key>\"] anchor to spotlight."
|
|
62
|
+
"description": "CSS selector or [data-tour=\"<key>\"] anchor to spotlight. Prefer the stable [data-tour] anchors over styling classes (which churn on redesigns)."
|
|
63
63
|
},
|
|
64
64
|
"title": { "type": "string", "minLength": 1 },
|
|
65
65
|
"body": { "type": "string", "minLength": 1 },
|
|
66
|
-
"placement": { "enum": ["top", "bottom", "left", "right", "auto"] }
|
|
66
|
+
"placement": { "enum": ["top", "bottom", "left", "right", "auto"] },
|
|
67
|
+
"canvas": {
|
|
68
|
+
"type": "boolean",
|
|
69
|
+
"description": "Setup: ensure a canvas is open before the step (the tour opens the first one if none)."
|
|
70
|
+
},
|
|
71
|
+
"inspector": {
|
|
72
|
+
"type": "boolean",
|
|
73
|
+
"description": "Setup: open the right-hand Inspector panel before the step."
|
|
74
|
+
},
|
|
75
|
+
"tab": {
|
|
76
|
+
"enum": ["inspect", "layers", "css"],
|
|
77
|
+
"description": "Setup: switch the Inspector to this tab before the step."
|
|
78
|
+
},
|
|
79
|
+
"requireSelection": {
|
|
80
|
+
"type": "boolean",
|
|
81
|
+
"description": "The target only exists once an element is ⌘-clicked in the (cross-origin) canvas. The card shows a '⌘-click to continue' hint and the spotlight snaps onto the real row when a selection lands."
|
|
82
|
+
}
|
|
67
83
|
}
|
|
68
84
|
}
|
|
69
85
|
}
|
package/apps/studio/ws.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import type { ServerWebSocket, WebSocketHandler } from 'bun';
|
|
5
5
|
|
|
6
|
+
import type { Acp } from './acp/index.ts';
|
|
6
7
|
import type { Activity } from './activity.ts';
|
|
7
8
|
import type { Api } from './api.ts';
|
|
8
9
|
import type { Collab, RoomConn } from './collab/index.ts';
|
|
@@ -36,6 +37,15 @@ export type WsData =
|
|
|
36
37
|
id: string;
|
|
37
38
|
remote: string;
|
|
38
39
|
kind: 'canvas-hmr';
|
|
40
|
+
}
|
|
41
|
+
| {
|
|
42
|
+
// Phase 31 (DDR-123) — ACP chat bridge socket. Main origin ONLY,
|
|
43
|
+
// loopback-guarded at upgrade (server.ts); NEVER opened from the canvas
|
|
44
|
+
// origin. Carries the JSON chat protocol (prompt / cancel ↔ update /
|
|
45
|
+
// turn-end), bridged to the user's own `claude` via createAcp.
|
|
46
|
+
id: string;
|
|
47
|
+
remote: string;
|
|
48
|
+
kind: 'acp';
|
|
39
49
|
};
|
|
40
50
|
|
|
41
51
|
/**
|
|
@@ -82,7 +92,8 @@ export function createWs(
|
|
|
82
92
|
api: Api,
|
|
83
93
|
inspect: Inspect,
|
|
84
94
|
collab: Collab,
|
|
85
|
-
activity: Activity
|
|
95
|
+
activity: Activity,
|
|
96
|
+
acp: Acp
|
|
86
97
|
): Ws {
|
|
87
98
|
const clients = new Set<ServerWebSocket<WsData>>();
|
|
88
99
|
|
|
@@ -143,6 +154,26 @@ export function createWs(
|
|
|
143
154
|
// emits, so this is a no-op for unlinked projects.
|
|
144
155
|
ctx.bus.on('sync:status', (payload: unknown) => broadcast({ type: 'sync:status', payload }));
|
|
145
156
|
|
|
157
|
+
// Phase 27 Task 5 (E2) — live dirty-state. git/watch.ts recomputes gitStatus on
|
|
158
|
+
// a versionable file change (trailing-debounced) and emits 'git-status'. Only
|
|
159
|
+
// the same-origin inspector clients (the shell) get it — it carries the changed-
|
|
160
|
+
// file list, so it stays off the untrusted canvas-origin feed like the other
|
|
161
|
+
// privileged broadcasts. Drives the Changes-panel count + tree M/A/D badges.
|
|
162
|
+
ctx.bus.on('git-status', (payload: unknown) => broadcast({ type: 'git-status', payload }));
|
|
163
|
+
|
|
164
|
+
// Phase 30 — live canvas-list refresh. api.createCanvas / deleteCanvas emit
|
|
165
|
+
// this; inspector clients (the shell) re-read the branch-scoped tree via
|
|
166
|
+
// /_index-data. Loopback-only (same dev-server) — cross-machine peers get a
|
|
167
|
+
// new canvas through git "Get latest", not this event.
|
|
168
|
+
ctx.bus.on('canvas-list-update', (payload: unknown) =>
|
|
169
|
+
broadcast({ type: 'canvas-list-update', payload })
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// Phase 31 (DDR-123) — `/design:chat` → `maude design chat-open` → POST
|
|
173
|
+
// /_api/acp/focus emits this; the shell (app.jsx, native-only) opens the
|
|
174
|
+
// Assistant panel. Inspector clients only — same-origin shell, like the rest.
|
|
175
|
+
ctx.bus.on('acp-focus', () => broadcast({ type: 'acp-focus' }));
|
|
176
|
+
|
|
146
177
|
// HMR broadcaster — turns fs:any change events into `canvas-hmr` messages.
|
|
147
178
|
// The iframe-side client (in _shell.html) decides reload strategy from `mode`.
|
|
148
179
|
// Uses broadcastHmr so the segregated canvas origin's HMR-only sockets get it.
|
|
@@ -189,6 +220,10 @@ export function createWs(
|
|
|
189
220
|
clients.add(ws);
|
|
190
221
|
return;
|
|
191
222
|
}
|
|
223
|
+
if (ws.data.kind === 'acp') {
|
|
224
|
+
acp.onOpen(ws);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
192
227
|
clients.add(ws);
|
|
193
228
|
// Snapshot carries the inspector state AND the activity map so a client
|
|
194
229
|
// opening mid-edit seeds its overlay state (Phase 13). Inspector-origin
|
|
@@ -197,6 +232,10 @@ export function createWs(
|
|
|
197
232
|
send(ws, { type: 'snapshot', state: inspect.state, activity: activity.state });
|
|
198
233
|
},
|
|
199
234
|
async close(ws) {
|
|
235
|
+
if (ws.data.kind === 'acp') {
|
|
236
|
+
acp.onClose(ws);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
200
239
|
if (ws.data.kind === 'collab') {
|
|
201
240
|
const binding = collabConns.get(ws.data.id);
|
|
202
241
|
if (binding) {
|
|
@@ -217,6 +256,10 @@ export function createWs(
|
|
|
217
256
|
// HMR-only canvas-origin socket: never accepts inbound messages (the
|
|
218
257
|
// canvas iframe only listens for canvas-hmr; it never sends).
|
|
219
258
|
if (ws.data.kind === 'canvas-hmr') return;
|
|
259
|
+
if (ws.data.kind === 'acp') {
|
|
260
|
+
acp.onMessage(ws, raw);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
220
263
|
if (ws.data.kind === 'collab') {
|
|
221
264
|
const binding = collabConns.get(ws.data.id);
|
|
222
265
|
if (!binding) return;
|
|
@@ -282,6 +282,90 @@ test('status reports zero-syncable when _sync.json is notSyncable (DDR-060 / 9.1
|
|
|
282
282
|
cleanup();
|
|
283
283
|
});
|
|
284
284
|
|
|
285
|
+
// --------------------------------------------------- DDR-102 status surfaces
|
|
286
|
+
|
|
287
|
+
test('DDR-102: status renders docs counts, rejected slugs, and the conflict recovery hint', async () => {
|
|
288
|
+
await runCli(['design', 'link', URL, '--token', 'mau_test']);
|
|
289
|
+
writeFileSync(
|
|
290
|
+
join(workspace, '.design/_sync.json'),
|
|
291
|
+
JSON.stringify({
|
|
292
|
+
state: 'online',
|
|
293
|
+
queuedOps: 0,
|
|
294
|
+
lastSyncAt: 1760000000000,
|
|
295
|
+
offlineSince: null,
|
|
296
|
+
flash: null,
|
|
297
|
+
updatedAt: Date.now(),
|
|
298
|
+
url: URL,
|
|
299
|
+
canvases: 83,
|
|
300
|
+
docs: { synced: 81, pending: 0, rejected: 2 },
|
|
301
|
+
rejectedSlugs: ['system-tokens', 'system-type'],
|
|
302
|
+
conflicts: [
|
|
303
|
+
{
|
|
304
|
+
slug: 'ui-maskot',
|
|
305
|
+
kind: 'cold-start-diverged',
|
|
306
|
+
winner: 'local',
|
|
307
|
+
snapshots: {
|
|
308
|
+
local: '2026-06-11T10:00:00.000Z',
|
|
309
|
+
hub: '2026-06-11T10:00:00.001Z',
|
|
310
|
+
},
|
|
311
|
+
at: 1760000001000,
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
}),
|
|
315
|
+
'utf8'
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const human = await runCli(['design', 'status']);
|
|
319
|
+
assert.equal(human.status, 0, human.stderr);
|
|
320
|
+
assert.match(human.stdout, /docs: 81 synced · 0 pending · 2 rejected/);
|
|
321
|
+
assert.match(human.stdout, /not syncing:\s+system-tokens, system-type/);
|
|
322
|
+
assert.match(human.stdout, /ui-maskot — diverged, kept local \(newest-wins\)/);
|
|
323
|
+
assert.match(human.stdout, /local@2026-06-11T10:00:00\.000Z/);
|
|
324
|
+
assert.match(human.stdout, /hub@2026-06-11T10:00:00\.001Z/);
|
|
325
|
+
assert.match(human.stdout, /\/design:rollback ui-maskot/);
|
|
326
|
+
|
|
327
|
+
// --json passes the new fields through verbatim.
|
|
328
|
+
const json = await runCli(['design', 'status', '--json']);
|
|
329
|
+
const payload = JSON.parse(json.stdout);
|
|
330
|
+
assert.equal(payload.sync.docs.rejected, 2);
|
|
331
|
+
assert.equal(payload.sync.conflicts[0].winner, 'local');
|
|
332
|
+
cleanup();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test('DDR-102: status renders an OLD-shape _sync.json (no docs/rejectedSlugs) unchanged', async () => {
|
|
336
|
+
await runCli(['design', 'link', URL, '--token', 'mau_test']);
|
|
337
|
+
writeFileSync(
|
|
338
|
+
join(workspace, '.design/_sync.json'),
|
|
339
|
+
JSON.stringify({
|
|
340
|
+
state: 'online',
|
|
341
|
+
queuedOps: 0,
|
|
342
|
+
lastSyncAt: null,
|
|
343
|
+
offlineSince: null,
|
|
344
|
+
flash: null,
|
|
345
|
+
updatedAt: Date.now(),
|
|
346
|
+
url: URL,
|
|
347
|
+
canvases: 5,
|
|
348
|
+
conflicts: [],
|
|
349
|
+
}),
|
|
350
|
+
'utf8'
|
|
351
|
+
);
|
|
352
|
+
const human = await runCli(['design', 'status']);
|
|
353
|
+
assert.equal(human.status, 0, human.stderr);
|
|
354
|
+
assert.match(human.stdout, /sync agent:\s+online/);
|
|
355
|
+
assert.doesNotMatch(human.stdout, /docs:/);
|
|
356
|
+
assert.doesNotMatch(human.stdout, /not syncing:/);
|
|
357
|
+
cleanup();
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('DDR-102: re-linking the same hub prints the token-replacement notice', async () => {
|
|
361
|
+
await runCli(['design', 'link', URL, '--token', 'mau_first']);
|
|
362
|
+
const second = await runCli(['design', 'link', URL, '--token', 'mau_second']);
|
|
363
|
+
assert.equal(second.status, 0, second.stderr);
|
|
364
|
+
assert.match(second.stdout, /replacing the stored token for/);
|
|
365
|
+
assert.match(second.stdout, /EVERY project linked to this hub on this machine/);
|
|
366
|
+
cleanup();
|
|
367
|
+
});
|
|
368
|
+
|
|
285
369
|
// --------------------------------------------------- DDR-054 F2/F4 trust gate
|
|
286
370
|
|
|
287
371
|
const REMOTE_URL = 'http://hub.invalid:9999'; // .invalid never resolves (RFC 6761)
|
package/cli/commands/design.mjs
CHANGED
|
@@ -42,6 +42,8 @@ const BIN_VERBS = new Set([
|
|
|
42
42
|
'svg-optimize',
|
|
43
43
|
'to-lottie',
|
|
44
44
|
'read-annotations',
|
|
45
|
+
'annotate',
|
|
46
|
+
'chat-open',
|
|
45
47
|
]);
|
|
46
48
|
|
|
47
49
|
// Bin verbs that boot the dev-server (directly, or by shelling into server-up.sh).
|
|
@@ -134,7 +136,7 @@ Lifecycle:
|
|
|
134
136
|
Dev-tooling (dispatch to the dev-server bash helpers — DDR-062):
|
|
135
137
|
screenshot · server-up · prep · slug · bootstrap-check · runtime-health
|
|
136
138
|
smoke · canvas-edit · handoff · asset-sweep · visual-sanity
|
|
137
|
-
draw-build · draw-proof · svg-optimize · to-lottie · read-annotations
|
|
139
|
+
draw-build · draw-proof · svg-optimize · to-lottie · read-annotations · annotate
|
|
138
140
|
Invoke the bundled helper of the same name. maude resolves it from its
|
|
139
141
|
own package root and sets CLAUDE_PLUGIN_ROOT for the child; stdout,
|
|
140
142
|
stderr, and exit code pass straight through (so command-substitution
|
|
@@ -165,7 +165,11 @@ test('`maude studio` aliases `maude design serve` (identical boot resolution)',
|
|
|
165
165
|
encoding: 'utf8',
|
|
166
166
|
timeout: 30000,
|
|
167
167
|
});
|
|
168
|
-
|
|
168
|
+
// Each invocation mkdtemp's its own root, and the diagnostic embeds that
|
|
169
|
+
// path — mask it so we compare the path-independent signature (the test's
|
|
170
|
+
// actual intent: same exit code + same error shape from both entrypoints).
|
|
171
|
+
const firstErr = ((r.stderr || '').split('\n').find(Boolean) ?? '').replaceAll(cwd, '<root>');
|
|
172
|
+
return { status: r.status, firstErr };
|
|
169
173
|
} finally {
|
|
170
174
|
rmSync(cwd, { recursive: true, force: true });
|
|
171
175
|
}
|
package/cli/lib/design-link.mjs
CHANGED
|
@@ -108,6 +108,16 @@ export async function runLink({ args, cwd = process.cwd(), forceAdopt = false })
|
|
|
108
108
|
process.exit(1);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
// DDR-102 incident learning (same-machine token overwrite): hubs.json is
|
|
112
|
+
// keyed per hub URL only, so a second `link --token` silently replaced the
|
|
113
|
+
// first machine-wide — both dev servers then authenticated as the same
|
|
114
|
+
// label and shared one hub rate bucket. Make the overwrite explicit.
|
|
115
|
+
if (getHub(normUrl)) {
|
|
116
|
+
process.stdout.write(
|
|
117
|
+
`[design link] note: replacing the stored token for ${normUrl} (the token is per-machine — this applies to EVERY project linked to this hub on this machine).\n`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
111
121
|
// Write hub side: token (+ adopt attestation) in ~/.config/maude/hubs.json.
|
|
112
122
|
const hubRecord = addHub(normUrl, token, adopt ? { adoptedAt: Date.now() } : {});
|
|
113
123
|
|
|
@@ -262,16 +272,56 @@ export async function runStatus({ args, cwd = process.cwd() }) {
|
|
|
262
272
|
}
|
|
263
273
|
|
|
264
274
|
const uptimeS = Math.round((probe.uptimeMs ?? 0) / 1000);
|
|
275
|
+
// DDR-102 — per-doc rollup (old payloads without `docs` render unchanged).
|
|
276
|
+
const docsLine = sync?.docs
|
|
277
|
+
? `, docs: ${sync.docs.synced} synced · ${sync.docs.pending} pending · ${sync.docs.rejected} rejected`
|
|
278
|
+
: '';
|
|
265
279
|
const syncLine = sync?.notSyncable
|
|
266
280
|
? `linked but 0 syncable canvases — ${sync.reason}`
|
|
267
281
|
: sync
|
|
268
282
|
? `${sync.state}${sync.sharedDoc ? ' [shared-doc]' : ''}${sync.queuedOps ? ` — ${sync.queuedOps} edit(s) queued` : ''}${
|
|
269
283
|
sync.lastSyncAt ? `, last sync ${new Date(sync.lastSyncAt).toISOString()}` : ''
|
|
270
|
-
}${sync.conflicts?.length ? `, ${sync.conflicts.length} conflict notice(s)` : ''}`
|
|
284
|
+
}${docsLine}${sync.conflicts?.length ? `, ${sync.conflicts.length} conflict notice(s)` : ''}`
|
|
271
285
|
: 'idle (start `maude design serve` in linked mode)';
|
|
272
286
|
process.stdout.write(
|
|
273
287
|
`Maude design — linked mode\n hub URL: ${url}\n linked at: ${new Date(cfg.linkedHub.linkedAt).toISOString()}\n adopt mode: ${cfg.linkedHub.adopt ? 'yes (push-on-first-sync)' : 'no (hub-wins)'}\n TSX sync: ${cfg.linkedHub.syncTsx === false ? 'off (opted out — linkedHub.syncTsx: false)' : 'on (default — DDR-079)'}\n token stored: ${hubRecord ? 'yes (~/.config/maude/hubs.json)' : "NO — re-run 'maude design link'"}\n hub status: ${probe.ok ? `up — v${probe.version}, ${uptimeS}s uptime, ${probe.tokenCount} token(s), ${probe.authMode}` : `UNREACHABLE — ${probe.error}`}\n sync agent: ${syncLine}\n`
|
|
274
288
|
);
|
|
289
|
+
// DDR-102 — auth-rejected canvases (per-slug honesty).
|
|
290
|
+
if (Array.isArray(sync?.rejectedSlugs) && sync.rejectedSlugs.length > 0) {
|
|
291
|
+
const total = sync.docs?.rejected ?? sync.rejectedSlugs.length;
|
|
292
|
+
const more =
|
|
293
|
+
total > sync.rejectedSlugs.length ? ` (+${total - sync.rejectedSlugs.length} more)` : '';
|
|
294
|
+
process.stdout.write(
|
|
295
|
+
` not syncing: ${sync.rejectedSlugs.join(', ')}${more} — hub rejected auth; the serve log names the reason (scope / invalid token / rate limit).\n`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
// DDR-102 — conflict log with winner + snapshot refs + the recovery story.
|
|
299
|
+
if (Array.isArray(sync?.conflicts) && sync.conflicts.length > 0) {
|
|
300
|
+
process.stdout.write(' conflicts:\n');
|
|
301
|
+
for (const c of sync.conflicts.slice(-10)) {
|
|
302
|
+
const at = c.at ? new Date(c.at).toISOString() : '?';
|
|
303
|
+
if (c.kind === 'cold-start-diverged') {
|
|
304
|
+
const snaps = c.snapshots
|
|
305
|
+
? ` snapshots: ${[
|
|
306
|
+
c.snapshots.local ? `local@${c.snapshots.local}` : null,
|
|
307
|
+
c.snapshots.hub ? `hub@${c.snapshots.hub}` : null,
|
|
308
|
+
]
|
|
309
|
+
.filter(Boolean)
|
|
310
|
+
.join(' ')}`
|
|
311
|
+
: '';
|
|
312
|
+
process.stdout.write(
|
|
313
|
+
` ${at} ${c.slug} — diverged, kept ${c.winner ?? 'hub'} (newest-wins);${snaps}\n recover the other version: /design:rollback ${c.slug}\n`
|
|
314
|
+
);
|
|
315
|
+
if (c.snapshotFailed) {
|
|
316
|
+
process.stdout.write(
|
|
317
|
+
` ⚠ the _history snapshot FAILED — local was KEPT instead of overwritten (DDR-102 fail-closed); check disk space / _history write perms.\n`
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
process.stdout.write(` ${at} ${c.slug} — ${c.kind}\n`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
275
325
|
// DDR-079 migration advisory: a config with no explicit `syncTsx` rides the
|
|
276
326
|
// default, which FLIPPED from off→on in maude 0.27. Surface it so an upgrader
|
|
277
327
|
// who relied on the old "TSX never syncs" behavior isn't surprised that every
|