@1agh/maude 0.58.3 → 0.60.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 +1180 -242
- 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 +320 -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 +40 -0
- package/apps/studio/client/styles/4-components.css +4 -4
- package/apps/studio/context.ts +4 -0
- 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 +788 -0
- package/apps/studio/figma/fig-decode.ts +839 -0
- package/apps/studio/figma/fig-differential.test.ts +182 -0
- package/apps/studio/figma/fig-kiwi.ts +410 -0
- package/apps/studio/figma/fig-translator.test.ts +192 -0
- package/apps/studio/figma/fig-vector.test.ts +113 -0
- package/apps/studio/figma/fig-vector.ts +145 -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 +146 -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-artboard.ts +41 -1
- package/apps/studio/figma/to-render.ts +25 -3
- package/apps/studio/figma/types.ts +6 -1
- package/apps/studio/http.ts +94 -0
- package/apps/studio/sync/asset-push-worker.ts +84 -0
- package/apps/studio/sync/asset-push.ts +441 -39
- package/apps/studio/sync/asset-sweep.ts +262 -0
- package/apps/studio/sync/connection-state.ts +71 -3
- package/apps/studio/sync/index.ts +39 -6
- package/apps/studio/sync/presentation.ts +21 -0
- package/apps/studio/sync/status.ts +18 -0
- package/apps/studio/sync/supervisor.ts +20 -0
- package/apps/studio/test/canvas-origin-gate.test.ts +13 -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-worker.test.ts +183 -0
- package/apps/studio/test/sync-asset-push.test.ts +639 -47
- package/apps/studio/test/sync-asset-sweep.test.ts +243 -0
- package/apps/studio/test/sync-connection-state.test.ts +66 -0
- package/apps/studio/test/sync-panel-surface.test.ts +123 -0
- package/apps/studio/test/sync-resync-routes.test.ts +125 -0
- package/apps/studio/test/sync-status.test.ts +28 -0
- package/apps/studio/test/sync-supervisor.test.ts +46 -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 +87 -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
package/apps/studio/http.ts
CHANGED
|
@@ -1163,6 +1163,33 @@ export function createHttp(
|
|
|
1163
1163
|
},
|
|
1164
1164
|
};
|
|
1165
1165
|
},
|
|
1166
|
+
// `--explode` from the panel. Same lazy import and same ONE implementation
|
|
1167
|
+
// as the routes above — and note what is NOT here: the raw codegen response
|
|
1168
|
+
// never leaves the verb, so this route returns the same code-owned
|
|
1169
|
+
// accounting an agent would see on stdout (DDR-219 D10).
|
|
1170
|
+
async explode({ canvas, artboard, dryRun, confirmDocument }) {
|
|
1171
|
+
const mod = await import('./bin/_import-figma.mjs');
|
|
1172
|
+
const r = await mod.explodeArtboard({
|
|
1173
|
+
root: ctx.paths.repoRoot,
|
|
1174
|
+
designRootRel: ctx.paths.designRel,
|
|
1175
|
+
canvasRel: canvas,
|
|
1176
|
+
artboardId: artboard,
|
|
1177
|
+
dryRun: Boolean(dryRun),
|
|
1178
|
+
confirmDocument: Boolean(confirmDocument),
|
|
1179
|
+
});
|
|
1180
|
+
return {
|
|
1181
|
+
summary: {
|
|
1182
|
+
canvas: r.canvas,
|
|
1183
|
+
artboard: r.artboardId,
|
|
1184
|
+
route: 'codegen',
|
|
1185
|
+
endpoint: 'local',
|
|
1186
|
+
responseSha256: r.responseSha256,
|
|
1187
|
+
written: r.written,
|
|
1188
|
+
assets: r.assets ?? null,
|
|
1189
|
+
dispositions: r.report.entries,
|
|
1190
|
+
},
|
|
1191
|
+
};
|
|
1192
|
+
},
|
|
1166
1193
|
});
|
|
1167
1194
|
const gitJson = (r: { status: number; json: unknown }) =>
|
|
1168
1195
|
Response.json(r.json, { status: r.status, headers: { 'Cache-Control': 'no-store' } });
|
|
@@ -2441,6 +2468,53 @@ export function createHttp(
|
|
|
2441
2468
|
return gitJson(await cloudApi.detach());
|
|
2442
2469
|
},
|
|
2443
2470
|
|
|
2471
|
+
// ── Resync (feature-sync-resync-and-out-of-process-sweep) ───────────────
|
|
2472
|
+
// MAIN-ORIGIN ONLY: absent from BOTH CANVAS_SAFE_API and startCanvasServer's
|
|
2473
|
+
// `routes` map (DDR-088's two-allowlist rule). Canvas content is untrusted
|
|
2474
|
+
// (DDR-054) and must not be able to command the desktop to re-push a whole
|
|
2475
|
+
// project — a canvas that could would be an amplification primitive against
|
|
2476
|
+
// the person's own hub, and a way to spend their rate-limit budget.
|
|
2477
|
+
'/_api/sync/resync': async (req: Request) => {
|
|
2478
|
+
if (req.method !== 'POST') return new Response('Method not allowed', { status: 405 });
|
|
2479
|
+
if (!sameOriginWrite(req))
|
|
2480
|
+
return new Response('cross-origin write rejected', { status: 403 });
|
|
2481
|
+
if (!isTrustedRequestHost(req))
|
|
2482
|
+
return new Response('local request required', { status: 403 });
|
|
2483
|
+
const control = ctx.syncControl;
|
|
2484
|
+
if (!control) {
|
|
2485
|
+
return gitJson({
|
|
2486
|
+
status: 200,
|
|
2487
|
+
json: { ok: false, reason: 'no-supervisor', detail: 'Restart Maude to start syncing.' },
|
|
2488
|
+
});
|
|
2489
|
+
}
|
|
2490
|
+
// REFUSED, not queued — see `SyncSupervisor.busy`. A second press must
|
|
2491
|
+
// not buy a second full re-link of every canvas.
|
|
2492
|
+
if (control.busy?.()) {
|
|
2493
|
+
return gitJson({
|
|
2494
|
+
status: 409,
|
|
2495
|
+
json: { ok: false, reason: 'busy', detail: 'Syncing is already restarting.' },
|
|
2496
|
+
});
|
|
2497
|
+
}
|
|
2498
|
+
// No argument: KEEP the configured hub. Only an explicit `null` unlinks,
|
|
2499
|
+
// and this route must never be able to.
|
|
2500
|
+
const sync = await control.restart();
|
|
2501
|
+
return gitJson({ status: 200, json: { ok: true, sync } });
|
|
2502
|
+
},
|
|
2503
|
+
|
|
2504
|
+
'/_api/sync/cancel-assets': async (req: Request) => {
|
|
2505
|
+
// Cancel is scoped to the ASSET SWEEP: killing a reconnect mid-handshake
|
|
2506
|
+
// is not a meaningful gesture, killing a multi-hundred-megabyte upload
|
|
2507
|
+
// is. Safe by construction — uploads are idempotent and the hub writes
|
|
2508
|
+
// temp-then-rename, so no half-written asset can survive this.
|
|
2509
|
+
if (req.method !== 'POST') return new Response('Method not allowed', { status: 405 });
|
|
2510
|
+
if (!sameOriginWrite(req))
|
|
2511
|
+
return new Response('cross-origin write rejected', { status: 403 });
|
|
2512
|
+
if (!isTrustedRequestHost(req))
|
|
2513
|
+
return new Response('local request required', { status: 403 });
|
|
2514
|
+
const cancelled = ctx.syncControl?.current?.()?.cancelAssetSweep() ?? false;
|
|
2515
|
+
return gitJson({ status: 200, json: { ok: true, cancelled } });
|
|
2516
|
+
},
|
|
2517
|
+
|
|
2444
2518
|
'/_api/github/identity': async (req: Request) => {
|
|
2445
2519
|
if (req.method !== 'GET') return new Response('Method not allowed', { status: 405 });
|
|
2446
2520
|
if (!isTrustedRequestHost(req))
|
|
@@ -2548,6 +2622,26 @@ export function createHttp(
|
|
|
2548
2622
|
return gitJson(await figmaApi.runImport(body));
|
|
2549
2623
|
},
|
|
2550
2624
|
|
|
2625
|
+
// `--explode` — one artboard, via the LOCAL Dev Mode MCP (DDR-219 D2).
|
|
2626
|
+
//
|
|
2627
|
+
// Same triple gate, and it needs it more than the others: this route reaches
|
|
2628
|
+
// an unauthenticated loopback service that reads whatever Figma document the
|
|
2629
|
+
// user has open, and then WRITES into the design root. A canvas-reachable
|
|
2630
|
+
// version of it would hand the untrusted iframe (DDR-054) a primitive that
|
|
2631
|
+
// reads the user's open design — so it is in NEITHER canvas allowlist, the
|
|
2632
|
+
// same standing rule DDR-088 sets for every privileged route, asserted in
|
|
2633
|
+
// `test/canvas-origin-gate.test.ts` and by the grep test in
|
|
2634
|
+
// `cli/lib/figma-codegen-reachability.test.mjs`.
|
|
2635
|
+
'/_api/figma/explode': async (req: Request) => {
|
|
2636
|
+
if (req.method !== 'POST') return new Response('Method not allowed', { status: 405 });
|
|
2637
|
+
if (!sameOriginWrite(req))
|
|
2638
|
+
return new Response('cross-origin write rejected', { status: 403 });
|
|
2639
|
+
if (!isTrustedRequestHost(req))
|
|
2640
|
+
return new Response('local request required', { status: 403 });
|
|
2641
|
+
const body = await readJson<unknown>(req, 8 * 1024);
|
|
2642
|
+
return gitJson(await figmaApi.explode(body));
|
|
2643
|
+
},
|
|
2644
|
+
|
|
2551
2645
|
'/_api/github/create-project': async (req: Request) => {
|
|
2552
2646
|
if (req.method !== 'POST') return new Response('Method not allowed', { status: 405 });
|
|
2553
2647
|
if (!sameOriginWrite(req))
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// The asset sweep, in its own process — feature-sync-resync-and-out-of-process-sweep.
|
|
2
|
+
//
|
|
3
|
+
// WHY A SEPARATE PROCESS.
|
|
4
|
+
//
|
|
5
|
+
// The sweep crashes Bun 1.3.3 *when it runs alongside the dev server*, proven by
|
|
6
|
+
// isolation: the identical sweep against the same live hub completes standalone
|
|
7
|
+
// (182/182, 0 failures) and crash-loops in-process (three `Segmentation fault at
|
|
8
|
+
// address 0x0` plus a bus error, then the supervisor gave up after 3 restarts).
|
|
9
|
+
// Nobody has a fix for the fault itself, so the boundary is the fix: a fault
|
|
10
|
+
// here costs the sweep, and the editor — canvases, browser UI, ACP panel —
|
|
11
|
+
// stays up. The parent reports the dead child as a failed sweep.
|
|
12
|
+
//
|
|
13
|
+
// It is also what makes a Resync button safe to offer at all: pressing it
|
|
14
|
+
// re-runs this, and a button that can kill the person's dev server is not a
|
|
15
|
+
// button.
|
|
16
|
+
//
|
|
17
|
+
// PROTOCOL. argv gives the design root, the hub URL and a file holding the
|
|
18
|
+
// credential. stdout carries NDJSON — one tagged JSON object per line:
|
|
19
|
+
//
|
|
20
|
+
// {"t":"progress", …AssetPushProgress} many, throttled by pushAssets
|
|
21
|
+
// {"t":"result", …AssetPushResult} exactly one, on a clean finish
|
|
22
|
+
// {"t":"error", "message": "…"} exactly one, on a thrown sweep
|
|
23
|
+
//
|
|
24
|
+
// Tagged rather than positional ("the last line is the result") so a parent
|
|
25
|
+
// reading a truncated stream can still tell a final answer from a progress
|
|
26
|
+
// tick. Nothing else may reach stdout — one stray `console.log` from an import
|
|
27
|
+
// would land mid-stream — so diagnostics go to stderr, which the parent logs.
|
|
28
|
+
//
|
|
29
|
+
// THE CREDENTIAL ARRIVES IN A FILE, NEVER IN ARGV. `ps` is world-readable on
|
|
30
|
+
// every platform this ships to; a hub token on a command line is a hub token
|
|
31
|
+
// handed to every other user on the machine. The file is written 0600 by the
|
|
32
|
+
// parent and unlinked when the sweep ends. It is re-read per call, not cached,
|
|
33
|
+
// so a parent that rewrites it after a silent renewal is picked up mid-sweep.
|
|
34
|
+
|
|
35
|
+
import { readFileSync } from 'node:fs';
|
|
36
|
+
|
|
37
|
+
import { type AssetPushProgress, type AssetPushResult, pushAssets } from './asset-push.ts';
|
|
38
|
+
|
|
39
|
+
const [, , designRoot, hubUrl, tokenFile] = process.argv;
|
|
40
|
+
|
|
41
|
+
/** One NDJSON line. Synchronous — ordering with the result line is the contract. */
|
|
42
|
+
function emit(line: { t: 'progress' | 'result' | 'error' } & Record<string, unknown>): void {
|
|
43
|
+
process.stdout.write(`${JSON.stringify(line)}\n`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function main(): Promise<void> {
|
|
47
|
+
if (!designRoot || !hubUrl || !tokenFile) {
|
|
48
|
+
emit({ t: 'error', message: 'usage: <designRoot> <hubUrl> <tokenFile>' });
|
|
49
|
+
process.exit(2);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Read at call time — `pushAssets` asks per request, and a renewal mid-sweep
|
|
53
|
+
// rewrites the file underneath us. A read that fails (the parent unlinked it
|
|
54
|
+
// during teardown) falls back to the last value we saw rather than sending an
|
|
55
|
+
// empty Authorization, which the hub would answer 401 for every remaining
|
|
56
|
+
// file and report as 182 failures instead of one dead sweep.
|
|
57
|
+
let lastToken = '';
|
|
58
|
+
const token = (): string => {
|
|
59
|
+
try {
|
|
60
|
+
lastToken = readFileSync(tokenFile, 'utf8').trim() || lastToken;
|
|
61
|
+
} catch {
|
|
62
|
+
/* keep the last good one */
|
|
63
|
+
}
|
|
64
|
+
return lastToken;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const result: AssetPushResult = await pushAssets({
|
|
68
|
+
designRoot,
|
|
69
|
+
hubUrl,
|
|
70
|
+
token,
|
|
71
|
+
// stderr, never stdout — stdout is the protocol.
|
|
72
|
+
log: {
|
|
73
|
+
log: (...a: unknown[]) => process.stderr.write(`${a.join(' ')}\n`),
|
|
74
|
+
warn: (...a: unknown[]) => process.stderr.write(`${a.join(' ')}\n`),
|
|
75
|
+
},
|
|
76
|
+
onProgress: (p: AssetPushProgress) => emit({ t: 'progress', ...p }),
|
|
77
|
+
});
|
|
78
|
+
emit({ t: 'result', ...result });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
main().catch((err) => {
|
|
82
|
+
emit({ t: 'error', message: String((err as Error)?.message ?? err).slice(0, 2000) });
|
|
83
|
+
process.exit(1);
|
|
84
|
+
});
|