@1agh/maude 0.58.3 → 0.59.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/apps/studio/annotations-layer.tsx +49 -15
- package/apps/studio/bin/_import-asset.mjs +18 -0
- package/apps/studio/bin/_import-figma.mjs +868 -214
- package/apps/studio/bin/_perf-probe-safari.mjs +332 -0
- package/apps/studio/bin/_perf-probe.mjs +228 -0
- package/apps/studio/bin/_perf-shared.mjs +345 -0
- package/apps/studio/bin/_video-playwright.mjs +17 -4
- package/apps/studio/bin/import-figma.sh +10 -1
- package/apps/studio/bin/perf.sh +228 -0
- package/apps/studio/bin/smoke.sh +49 -5
- package/apps/studio/canvas-lib.tsx +148 -6
- package/apps/studio/client/app.jsx +152 -37
- package/apps/studio/client/panels/SyncPanel.jsx +229 -0
- package/apps/studio/client/panels/TimelinePanel.jsx +29 -1
- package/apps/studio/client/panels/timeline-comp-target.js +101 -0
- package/apps/studio/client/styles/3-shell-maude.css +30 -0
- package/apps/studio/client/styles/4-components.css +4 -4
- package/apps/studio/dist/client.bundle.js +772 -772
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/exporters/video-encode-lib.ts +8 -5
- package/apps/studio/exporters/video.ts +10 -0
- package/apps/studio/figma/assets.test.ts +92 -0
- package/apps/studio/figma/assets.ts +63 -9
- package/apps/studio/figma/codegen-client.test.ts +276 -0
- package/apps/studio/figma/codegen-client.ts +509 -0
- package/apps/studio/figma/codegen-fonts.test.ts +103 -0
- package/apps/studio/figma/codegen-fonts.ts +195 -0
- package/apps/studio/figma/codegen-values.test.ts +179 -0
- package/apps/studio/figma/codegen-values.ts +270 -0
- package/apps/studio/figma/endpoints.ts +73 -0
- package/apps/studio/figma/fig-decode.test.ts +702 -0
- package/apps/studio/figma/fig-decode.ts +617 -0
- package/apps/studio/figma/fig-kiwi.ts +410 -0
- package/apps/studio/figma/fig-zip.ts +270 -0
- package/apps/studio/figma/from-codegen.test.ts +408 -0
- package/apps/studio/figma/from-codegen.ts +1103 -0
- package/apps/studio/figma/sanitize.test.ts +69 -0
- package/apps/studio/figma/sanitize.ts +139 -47
- package/apps/studio/figma/tailwind-map.test.ts +142 -0
- package/apps/studio/figma/tailwind-map.ts +545 -0
- package/apps/studio/figma/to-render.ts +25 -3
- package/apps/studio/figma/types.ts +6 -1
- package/apps/studio/http.ts +47 -0
- package/apps/studio/sync/asset-push.ts +346 -38
- package/apps/studio/sync/connection-state.ts +71 -3
- package/apps/studio/sync/index.ts +10 -1
- package/apps/studio/sync/status.ts +18 -0
- package/apps/studio/test/canvas-origin-gate.test.ts +4 -0
- package/apps/studio/test/figma-explode.test.ts +438 -0
- package/apps/studio/test/fixtures/perf-canvas.mjs +201 -0
- package/apps/studio/test/import-figma.test.ts +192 -4
- package/apps/studio/test/sync-asset-push.test.ts +490 -47
- package/apps/studio/test/sync-connection-state.test.ts +66 -0
- package/apps/studio/test/sync-panel-surface.test.ts +90 -0
- package/apps/studio/test/sync-status.test.ts +28 -0
- package/apps/studio/test/timeline-comp-target.test.ts +139 -0
- package/apps/studio/test/video-comp.test.ts +81 -1
- package/apps/studio/test/video-encode-lib.test.ts +63 -0
- package/apps/studio/use-artboard-drag.tsx +37 -3
- package/apps/studio/video-comp.tsx +51 -0
- package/apps/studio/whats-new.json +71 -0
- package/cli/commands/design.mjs +7 -0
- package/cli/commands/kg.mjs +8 -1
- package/cli/commands/kg.test.mjs +24 -0
- package/cli/lib/figma-codegen-reachability.test.mjs +104 -0
- package/cli/lib/figma-import-controls.test.mjs +70 -0
- package/package.json +8 -8
|
@@ -1,6 +1,77 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./whats-new.schema.json",
|
|
3
3
|
"entries": [
|
|
4
|
+
{
|
|
5
|
+
"id": "cloud-asset-push-completes",
|
|
6
|
+
"version": "0.59.0",
|
|
7
|
+
"date": "2026-08-12",
|
|
8
|
+
"kind": "fix",
|
|
9
|
+
"title": "Every asset reaches the cloud, in one go",
|
|
10
|
+
"summary": "A project with a lot of images, fonts and video used to trickle a handful of files to the cloud per boot and never catch up. Now the whole set uploads in one pass, and a file the hub refuses is named with the real reason instead of a bare error code.",
|
|
11
|
+
"surface": "design-ui"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": "figma-explode-artboard",
|
|
15
|
+
"version": "0.59.0",
|
|
16
|
+
"date": "2026-08-12",
|
|
17
|
+
"kind": "feature",
|
|
18
|
+
"title": "Make an imported Figma artboard editable",
|
|
19
|
+
"summary": "A Figma page imports as faithful pictures of your frames. Now you can pick one artboard and turn it into real, editable layout — Maude asks Figma's own Dev Mode for that frame's resolved design and converts it locally, so you get flex boxes and live text instead of an image. Needs the Figma desktop app open on the same file, with Dev Mode on.",
|
|
20
|
+
"surface": "design-ui"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "figma-import-fidelity-fixes",
|
|
24
|
+
"version": "0.59.0",
|
|
25
|
+
"date": "2026-08-12",
|
|
26
|
+
"kind": "fix",
|
|
27
|
+
"title": "Figma imports keep their fonts and their board",
|
|
28
|
+
"summary": "Three things a real migration surfaced: text in imported frames fell back to a serif when the font wasn't installed, an imported FigJam board landed dark inside screen chrome instead of on white paper, and a slow render check could quietly drop every image in a large import. Imported files now look like the ones you imported.",
|
|
29
|
+
"surface": "design-ui"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "canvas-perf-benchmark",
|
|
33
|
+
"version": "0.59.0",
|
|
34
|
+
"date": "2026-08-12",
|
|
35
|
+
"kind": "improvement",
|
|
36
|
+
"title": "Measure a canvas's pan/zoom performance",
|
|
37
|
+
"summary": "A new `maude design perf` command drives a scripted pan and zoom over a canvas and reports frame timings plus how much React work the gesture caused — with a delta against your previous run, so a change is either faster or it isn't. It can measure Chrome or Safari (WebKit, the engine the desktop app uses), and `/design:smoke --perf` folds it into a smoke sweep. Under the hood, pan and zoom no longer re-render the canvas tree while you're moving: on a 128-artboard canvas that removed about 90% of the renders a gesture used to trigger.",
|
|
38
|
+
"surface": "design-ui"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "video-export-degraded-visible",
|
|
42
|
+
"version": "0.59.0",
|
|
43
|
+
"date": "2026-08-12",
|
|
44
|
+
"kind": "fix",
|
|
45
|
+
"title": "A video export tells you when it's missing audio",
|
|
46
|
+
"summary": "If an MP4/WebM export can't get audio — an unsupported media element, or the fast renderer had to fall back to a frame-by-frame capture — the export no longer quietly reports \"done\" with a clean checkmark. The Exports panel now shows a degraded pill and a completion toast explaining exactly what was dropped and how to fix it, and it's recorded in your export history too.",
|
|
47
|
+
"surface": "design-ui"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "sync-panel-per-file",
|
|
51
|
+
"version": "0.59.0",
|
|
52
|
+
"date": "2026-08-12",
|
|
53
|
+
"kind": "feature",
|
|
54
|
+
"title": "Watch your project sync, file by file",
|
|
55
|
+
"summary": "Click the hub sync chip in the status bar to open the new Sync panel: every canvas with its own live state (syncing, synced, or refused and why), plus your images and fonts as they upload to the cloud. “Is my work actually up there?” now has a per-file answer.",
|
|
56
|
+
"surface": "design-ui",
|
|
57
|
+
"tour": [
|
|
58
|
+
{
|
|
59
|
+
"target": "[data-testid=\"open-sync\"]",
|
|
60
|
+
"title": "Per-file sync progress",
|
|
61
|
+
"body": "The hub sync chip is now a button — click it to see every canvas and asset move to the cloud, and which ones need attention.",
|
|
62
|
+
"placement": "top"
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "cloud-brand-assets-and-steady-link",
|
|
68
|
+
"version": "0.59.0",
|
|
69
|
+
"date": "2026-08-12",
|
|
70
|
+
"kind": "fix",
|
|
71
|
+
"title": "Brand logos load in the cloud (and a steadier status bar)",
|
|
72
|
+
"summary": "Your design system's own logos, signs, fonts and photos now show up in the cloud copy instead of grey placeholders — the last piece of \"the cloud copy matches your desktop\". And the desktop status bar no longer flickers to \"reconnecting\" when you step away for a minute; an idle link stays live.",
|
|
73
|
+
"surface": "design-ui"
|
|
74
|
+
},
|
|
4
75
|
{
|
|
5
76
|
"id": "cloud-sync-matches-your-desktop",
|
|
6
77
|
"version": "0.58.3",
|
package/cli/commands/design.mjs
CHANGED
|
@@ -126,6 +126,13 @@ const BIN_VERBS = new Set([
|
|
|
126
126
|
// host — see DDR-185's rejected alternatives). No dev server dependency —
|
|
127
127
|
// pure Node, no `<designRoot>` involvement.
|
|
128
128
|
'curl-local',
|
|
129
|
+
// feature-canvas-render-performance (Task 1 + 11). `perf` drives a scripted
|
|
130
|
+
// pan/zoom against a live canvas and reports frame-time percentiles plus the
|
|
131
|
+
// React render count during the gesture, delta'd against the previous run of
|
|
132
|
+
// the same (canvas, engine) pair. It is a benchmark a human reads, never a CI
|
|
133
|
+
// gate — headless frame timings swing with GPU load, so an absolute threshold
|
|
134
|
+
// would go red for reasons unrelated to the code.
|
|
135
|
+
'perf',
|
|
129
136
|
// DDR-185 security addendum. `agent-browser-safe` replaces the bare
|
|
130
137
|
// `Bash(agent-browser:*)` allow-list entry a security review found was a
|
|
131
138
|
// zero-confirmation session-hijack primitive (agent-browser's own bundled
|
package/cli/commands/kg.mjs
CHANGED
|
@@ -406,7 +406,14 @@ async function verbRecordLog(state, args, pkgRoot) {
|
|
|
406
406
|
// Temp file + `kg ingest --file`, matching the importer: the same plumbing
|
|
407
407
|
// that ingested 310 decisions during migration, so no new stdin path to get
|
|
408
408
|
// wrong (and a verdict body is far past comfortable argv size anyway).
|
|
409
|
-
|
|
409
|
+
// `built.slug` is `${scope.repo}/<slug>` under this store's per-repo
|
|
410
|
+
// namespacing convention (scopedSlug) — a bare join() with that slash reads
|
|
411
|
+
// as an implied subdirectory `writeFileSync` never creates, so every call
|
|
412
|
+
// in a repo-namespaced store failed ENOENT before it ever reached `kg
|
|
413
|
+
// ingest`. This is a filesystem staging path, not the graph identity, so
|
|
414
|
+
// sanitizing it here doesn't touch what actually gets recorded.
|
|
415
|
+
const tmpSlug = built.slug.replace(/\//g, '_');
|
|
416
|
+
const tmp = join(tmpdir(), `kg-record-${kind}-${tmpSlug}.json`);
|
|
410
417
|
writeFileSync(tmp, JSON.stringify({ decisions: [built.decision] }));
|
|
411
418
|
const status = runKg(state, ['ingest', '--file', tmp], {
|
|
412
419
|
timeoutMs: 30000,
|
package/cli/commands/kg.test.mjs
CHANGED
|
@@ -116,3 +116,27 @@ test('passthrough strips the maude-owned --root flag from the kg argv', () => {
|
|
|
116
116
|
assert.match(r.stdout, /ARGV:context --about x/);
|
|
117
117
|
assert.doesNotMatch(r.stdout, /--root/);
|
|
118
118
|
});
|
|
119
|
+
|
|
120
|
+
test('record-log succeeds when the scoped slug contains a slash (repo-namespaced store)', () => {
|
|
121
|
+
// scopedSlug() prefixes the log's slug with `${scope.repo}/` under this
|
|
122
|
+
// store's per-repo namespacing convention, so `built.slug` is e.g.
|
|
123
|
+
// "acme/foo" — a temp filename built by bare string interpolation reads
|
|
124
|
+
// that slash as an implied subdirectory writeFileSync never creates, and
|
|
125
|
+
// every record-log call in a repo-namespaced store failed ENOENT before it
|
|
126
|
+
// ever reached `kg ingest`.
|
|
127
|
+
const root = repoWith({ mode: 'on', scope: { repo: 'acme', dept: 'dev' } });
|
|
128
|
+
const logDir = join(root, '.ai', 'logs', 'rca');
|
|
129
|
+
mkdirSync(logDir, { recursive: true });
|
|
130
|
+
writeFileSync(join(logDir, 'foo.md'), '# RCA: something broke\n\nbody text\n');
|
|
131
|
+
const stub = join(root, 'kg-stub.sh');
|
|
132
|
+
writeFileSync(stub, '#!/usr/bin/env bash\nexit 0\n');
|
|
133
|
+
spawnSync('chmod', ['+x', stub]);
|
|
134
|
+
const r = spawnSync(
|
|
135
|
+
'node',
|
|
136
|
+
[MAUDE_BIN, 'kg', 'record-log', '--file', '.ai/logs/rca/foo.md', '--root', root],
|
|
137
|
+
{ cwd: root, encoding: 'utf8', env: { ...process.env, NO_COLOR: '1', KGAI_BIN: stub } }
|
|
138
|
+
);
|
|
139
|
+
assert.equal(r.status ?? 0, 0, r.stderr);
|
|
140
|
+
assert.doesNotMatch(r.stderr, /ENOENT/);
|
|
141
|
+
assert.match(r.stdout, /\[kg\] recorded rca:acme\/foo/);
|
|
142
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// DDR-219 D2 regression guard — the codegen channel is a CONTROL, not an intent.
|
|
2
|
+
//
|
|
3
|
+
// The first draft of DDR-219 routed Figma Dev Mode codegen through the REMOTE
|
|
4
|
+
// MCP (`https://mcp.figma.com/mcp`) and treated our own code's inability to
|
|
5
|
+
// reach it as the safety property. The design-stage review took that apart:
|
|
6
|
+
//
|
|
7
|
+
// - the remote catalog "allowlist" is enforced on a self-reported OAuth
|
|
8
|
+
// `client_name`, and a public bypass for it exists — so it is a waitlist,
|
|
9
|
+
// not a boundary;
|
|
10
|
+
// - the LOCAL Dev Mode server (`http://127.0.0.1:3845/mcp`) takes plain
|
|
11
|
+
// loopback HTTP with NO credential and no catalog gate at all, so nothing
|
|
12
|
+
// but intent stands between a future contributor and `fetch(...)` inside
|
|
13
|
+
// `apps/studio/figma/`;
|
|
14
|
+
// - DDR-185's `curl-local` *permits* that endpoint by construction (its rule
|
|
15
|
+
// is "every resolved address is loopback", and 127.0.0.1 is), and an ACP
|
|
16
|
+
// session auto-approves `Bash(maude:*)` — so the one egress control the
|
|
17
|
+
// session trusts is, for this specific service, an allow rule.
|
|
18
|
+
//
|
|
19
|
+
// Reaching the remote endpoint from product code would put a third-party model's
|
|
20
|
+
// output into an ingestion path that DDR-216 D1 says is deterministic end to
|
|
21
|
+
// end. Reaching the local one from anywhere except the single designated client
|
|
22
|
+
// would make codegen a bulk ingestion route by accident — DDR-219 D1's table
|
|
23
|
+
// silently false, its fallback never firing, and nothing in CI noticing.
|
|
24
|
+
//
|
|
25
|
+
// So both halves are asserted here rather than described in a decision record.
|
|
26
|
+
|
|
27
|
+
import assert from 'node:assert/strict';
|
|
28
|
+
import { execSync } from 'node:child_process';
|
|
29
|
+
import { test } from 'node:test';
|
|
30
|
+
|
|
31
|
+
/** The ONE module allowed to name the local Dev Mode endpoint (DDR-219 D2). */
|
|
32
|
+
const CODEGEN_CLIENT = 'apps/studio/figma/codegen-client.ts';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Tests under the Figma lane may name it too, and the widening is narrow on
|
|
36
|
+
* purpose. `codegen-client.test.ts` asserts the endpoint constant — that IS the
|
|
37
|
+
* control ("the URL is never influenced by input"), and a guard that forbids
|
|
38
|
+
* asserting the thing it guards would push the assertion out of the repo.
|
|
39
|
+
* `from-codegen.test.ts` carries a captured response fixture whose asset
|
|
40
|
+
* constants are loopback URLs, and the property under test is that the converter
|
|
41
|
+
* DISCARDS them — which can only be asserted against a fixture that has them.
|
|
42
|
+
*
|
|
43
|
+
* Neither executes anything against the port. What stays absolutely banned is a
|
|
44
|
+
* second non-test module reaching it, which is the shape that would make codegen
|
|
45
|
+
* a bulk ingestion route by accident.
|
|
46
|
+
*/
|
|
47
|
+
const TEST_FILE_RE = /^apps\/studio\/figma\/[a-z-]+\.test\.ts$/;
|
|
48
|
+
|
|
49
|
+
/** Trees that ship or execute. Prose (`.ai/`, `docs/`, `site/`) may name both. */
|
|
50
|
+
const RUNTIME_TREES = ['apps/studio', 'cli', 'plugins'];
|
|
51
|
+
|
|
52
|
+
/** Source only. `node_modules` vendors binaries that contain arbitrary strings
|
|
53
|
+
* (the bundled agent-sdk `claude` matched `mcp.figma.com` on the first run),
|
|
54
|
+
* and `dist/` is build output, not a code path anyone edits. `-I` drops binary
|
|
55
|
+
* files outright so a vendored blob can never turn this guard into noise —
|
|
56
|
+
* a guard that cries wolf gets deleted, which would be worse than not having it. */
|
|
57
|
+
function grep(pattern, trees) {
|
|
58
|
+
// `|| true` because grep exits 1 on no-match, which is the passing case.
|
|
59
|
+
const out = execSync(
|
|
60
|
+
`grep -rlIE --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=.git ${JSON.stringify(pattern)} ${trees.join(' ')} 2>/dev/null || true`,
|
|
61
|
+
{ encoding: 'utf8', cwd: new URL('../../', import.meta.url).pathname }
|
|
62
|
+
);
|
|
63
|
+
return out
|
|
64
|
+
.trim()
|
|
65
|
+
.split('\n')
|
|
66
|
+
.filter(Boolean)
|
|
67
|
+
.filter((f) => !f.endsWith('figma-codegen-reachability.test.mjs'));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
test('no runtime code names the REMOTE Figma MCP endpoint', () => {
|
|
71
|
+
const offenders = grep('mcp\\.figma\\.com', RUNTIME_TREES);
|
|
72
|
+
assert.deepEqual(
|
|
73
|
+
offenders,
|
|
74
|
+
[],
|
|
75
|
+
`These files name the remote Figma MCP endpoint. It is banned from every runtime code path (DDR-219 D2, and \`feature-figma-import\` § Out of scope before it): it is reachable only by a catalog-listed agent client, so any use here means a model is in the ingestion path. Codegen goes over the LOCAL server, with apps/studio as the client:\n ${offenders.join('\n ')}`
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('only the designated client names the LOCAL Dev Mode endpoint', () => {
|
|
80
|
+
// Match the endpoint shape, not a bare `3845`, which could legitimately occur
|
|
81
|
+
// as a byte count or a hash fragment.
|
|
82
|
+
const offenders = grep('(localhost|127\\.0\\.0\\.1|:)3845', RUNTIME_TREES).filter(
|
|
83
|
+
(f) => f !== CODEGEN_CLIENT && !TEST_FILE_RE.test(f)
|
|
84
|
+
);
|
|
85
|
+
assert.deepEqual(
|
|
86
|
+
offenders,
|
|
87
|
+
[],
|
|
88
|
+
`Only ${CODEGEN_CLIENT} may reach the local Figma Dev Mode MCP server (DDR-219 D2). A second caller makes codegen an ingestion route by accident — and the local server is UNAUTHENTICATED loopback, so there is no credential step to notice it. If codegen is genuinely needed elsewhere, route it through the client:\n ${offenders.join('\n ')}`
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('no canvas-reachable codegen route exists', () => {
|
|
93
|
+
// A canvas-origin codegen route would hand the untrusted iframe (DDR-054) a
|
|
94
|
+
// primitive that reads the user's open Figma document. It belongs in NEITHER
|
|
95
|
+
// allowlist — the same standing rule DDR-088 sets for every privileged route.
|
|
96
|
+
const offenders = grep('CANVAS_SAFE_API[^\\n]*codegen|codegen[^\\n]*CANVAS_SAFE_API', [
|
|
97
|
+
'apps/studio',
|
|
98
|
+
]);
|
|
99
|
+
assert.deepEqual(
|
|
100
|
+
offenders,
|
|
101
|
+
[],
|
|
102
|
+
`A codegen route appears near CANVAS_SAFE_API. Codegen is privileged-origin only (DDR-219 D2):\n ${offenders.join('\n ')}`
|
|
103
|
+
);
|
|
104
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// DDR-216 D7 + D8 existence guard — the anti-recurrence test.
|
|
2
|
+
//
|
|
3
|
+
// This repo has now written the same mistake into three consecutive decision
|
|
4
|
+
// records: a DDR asserts a control, nobody builds it, and the NEXT DDR cites it
|
|
5
|
+
// as an existing mitigation. DDR-216 D7 promised `/design:edit` a pre-flight
|
|
6
|
+
// untrusted-content banner on an imported canvas; D8 promised Pass A.10 run
|
|
7
|
+
// promoted-to-blocker for `imported-figma`. Both were "shipped" in prose. When
|
|
8
|
+
// DDR-219 leaned on the A.10 promotion as a control that was "still enforced,
|
|
9
|
+
// unchanged", the design-stage review ran one command —
|
|
10
|
+
//
|
|
11
|
+
// grep -rn "imported-figma" plugins/ → zero matches
|
|
12
|
+
//
|
|
13
|
+
// — and the whole chain came apart. A decision record cannot assert its way to a
|
|
14
|
+
// control. This test is the cheapest possible thing that would have caught it,
|
|
15
|
+
// and it is deliberately dumb: it checks the string is REACHABLE from the files
|
|
16
|
+
// that must act on it, not that the behaviour is correct.
|
|
17
|
+
|
|
18
|
+
import assert from 'node:assert/strict';
|
|
19
|
+
import { readFileSync } from 'node:fs';
|
|
20
|
+
import { test } from 'node:test';
|
|
21
|
+
|
|
22
|
+
const root = new URL('../../', import.meta.url).pathname;
|
|
23
|
+
const read = (p) => readFileSync(root + p, 'utf8');
|
|
24
|
+
|
|
25
|
+
test('DDR-216 D7 — /design:edit banners an imported canvas before reading it', () => {
|
|
26
|
+
const edit = read('plugins/design/commands/edit.md');
|
|
27
|
+
assert.match(
|
|
28
|
+
edit,
|
|
29
|
+
/imported-figma/,
|
|
30
|
+
"/design:edit must detect an `imported-figma` canvas at pre-flight (DDR-216 D7). Without it, several hundred third-party strings enter a tool-holding agent's context with nothing saying what they are."
|
|
31
|
+
);
|
|
32
|
+
assert.match(
|
|
33
|
+
edit,
|
|
34
|
+
/untrusted content, never instructions/i,
|
|
35
|
+
"The banner must say the content is untrusted and is never instructions — that exact framing is the control (DDR-216 D7, DDR-085's whiteboard trust model)."
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('DDR-216 D8 — Pass A.10 is promoted to blocker on an imported canvas', () => {
|
|
40
|
+
const keeper = read('plugins/design/agents/design-system-keeper.md');
|
|
41
|
+
const a10 = keeper.slice(keeper.indexOf('## Pass A.10'));
|
|
42
|
+
assert.notEqual(a10, '', 'Pass A.10 section is missing from design-system-keeper.');
|
|
43
|
+
assert.match(
|
|
44
|
+
a10,
|
|
45
|
+
/imported-figma/,
|
|
46
|
+
'Pass A.10 must special-case `imported-figma` (DDR-216 D8). As a plain warning it is not a gate.'
|
|
47
|
+
);
|
|
48
|
+
assert.match(
|
|
49
|
+
a10,
|
|
50
|
+
/blocker/i,
|
|
51
|
+
'A.10 findings must be blockers on an imported canvas — the mass-drift stacking ladder exists to avoid nagging a human over one deliberate overlay, and that reasoning does not survive a machine-authored file.'
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('A.10 does not let an imported canvas skip via its declared kind', () => {
|
|
56
|
+
// The skip condition is "no kind=\"web\" artboard" — and on an import the
|
|
57
|
+
// TRANSLATOR picks the kind, so the gate would be satisfied by the very code
|
|
58
|
+
// it audits. DDR-216 D8's Round-1 correction named this; the exception closes it.
|
|
59
|
+
const keeper = read('plugins/design/agents/design-system-keeper.md');
|
|
60
|
+
const a10 = keeper.slice(keeper.indexOf('## Pass A.10'), keeper.indexOf('## Pass B'));
|
|
61
|
+
const skipIdx = a10.indexOf('Skip entirely');
|
|
62
|
+
assert.ok(skipIdx > -1, 'A.10 skip condition not found — did the section change shape?');
|
|
63
|
+
assert.match(
|
|
64
|
+
a10.slice(skipIdx, skipIdx + 1200),
|
|
65
|
+
// `[\s\S]` rather than `[^]` — same "any char incl. newline" meaning, but
|
|
66
|
+
// `[^]` is a negated EMPTY class, which biome flags and which reads as a typo.
|
|
67
|
+
/EXCEPTION[\s\S]*imported-figma/,
|
|
68
|
+
'The skip condition must carry an explicit imported-canvas exception, stated next to the skip itself where a reader will actually meet it.'
|
|
69
|
+
);
|
|
70
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1agh/maude",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.0",
|
|
4
4
|
"description": "Marketplace of Claude Code plugins by Michal Dovrtěl: `design` (canvas-first design iteration) + `flow` (generic agentic workflow loop with .ai second brain). Ships the `maude` CLI (with `mdcc` legacy alias) to scaffold workspace, run the design dev server, and manage configs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -54,13 +54,13 @@
|
|
|
54
54
|
"test:e2e:desktop:parity:cloud": "pnpm --filter @maude/desktop-e2e e2e:parity:cloud"
|
|
55
55
|
},
|
|
56
56
|
"optionalDependencies": {
|
|
57
|
-
"@1agh/maude-darwin-arm64": "0.
|
|
58
|
-
"@1agh/maude-darwin-x64": "0.
|
|
59
|
-
"@1agh/maude-linux-arm64": "0.
|
|
60
|
-
"@1agh/maude-linux-arm64-musl": "0.
|
|
61
|
-
"@1agh/maude-linux-x64": "0.
|
|
62
|
-
"@1agh/maude-linux-x64-musl": "0.
|
|
63
|
-
"@1agh/maude-win32-x64": "0.
|
|
57
|
+
"@1agh/maude-darwin-arm64": "0.59.0",
|
|
58
|
+
"@1agh/maude-darwin-x64": "0.59.0",
|
|
59
|
+
"@1agh/maude-linux-arm64": "0.59.0",
|
|
60
|
+
"@1agh/maude-linux-arm64-musl": "0.59.0",
|
|
61
|
+
"@1agh/maude-linux-x64": "0.59.0",
|
|
62
|
+
"@1agh/maude-linux-x64-musl": "0.59.0",
|
|
63
|
+
"@1agh/maude-win32-x64": "0.59.0"
|
|
64
64
|
},
|
|
65
65
|
"files": [
|
|
66
66
|
"cli",
|