@1agh/maude 0.40.0 → 0.42.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/ai-banner.tsx +2 -2
- package/apps/studio/annotations-layer.tsx +53 -2
- package/apps/studio/api.ts +504 -44
- package/apps/studio/artboard-marquee.tsx +1 -1
- package/apps/studio/bin/_canvas-rects-playwright.mjs +55 -0
- package/apps/studio/bin/_canvas-rects-static.mjs +166 -0
- package/apps/studio/bin/_fetch-asset.mjs +556 -0
- package/apps/studio/bin/_html-playwright.mjs +26 -4
- package/apps/studio/bin/_pdf-playwright.mjs +13 -2
- package/apps/studio/bin/_png-playwright.mjs +15 -2
- package/apps/studio/bin/_pptx-playwright.mjs +17 -4
- package/apps/studio/bin/_screenshot-playwright.mjs +17 -2
- package/apps/studio/bin/_svg-playwright.mjs +26 -4
- package/apps/studio/bin/annotate.mjs +576 -34
- package/apps/studio/bin/canvas-rects.sh +152 -0
- package/apps/studio/bin/fetch-asset.sh +34 -0
- package/apps/studio/bin/read-annotations.mjs +138 -7
- package/apps/studio/bin/screenshot.sh +53 -4
- package/apps/studio/bin/smoke.sh +42 -6
- package/apps/studio/build.ts +21 -0
- package/apps/studio/canvas-comment-mount.tsx +138 -4
- package/apps/studio/canvas-edit.ts +744 -11
- package/apps/studio/canvas-lib.tsx +219 -2
- package/apps/studio/canvas-shell.tsx +487 -20
- package/apps/studio/client/app.jsx +1476 -76
- package/apps/studio/client/comments-overlay.css +130 -126
- package/apps/studio/client/export-center.jsx +426 -0
- package/apps/studio/client/github.js +8 -0
- package/apps/studio/client/styles/3-shell-maude.css +65 -0
- package/apps/studio/client/styles/4-components.css +150 -0
- package/apps/studio/comments-overlay.tsx +148 -41
- package/apps/studio/config.schema.json +2 -2
- package/apps/studio/context-menu.tsx +15 -5
- package/apps/studio/contextual-toolbar.tsx +262 -4
- package/apps/studio/cursors-overlay.tsx +4 -4
- package/apps/studio/dist/client.bundle.js +20 -20
- package/apps/studio/dist/comment-mount.js +59 -1
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/dom-selection.ts +127 -1
- package/apps/studio/drag-state.ts +24 -0
- package/apps/studio/equal-spacing-detector.ts +205 -0
- package/apps/studio/export-dialog.tsx +19 -26
- package/apps/studio/exporters/_runtime.ts +104 -0
- package/apps/studio/exporters/html.ts +12 -20
- package/apps/studio/exporters/index.ts +14 -2
- package/apps/studio/exporters/jobs.ts +334 -0
- package/apps/studio/exporters/pdf.ts +16 -20
- package/apps/studio/exporters/png.ts +12 -20
- package/apps/studio/exporters/pptx.ts +22 -23
- package/apps/studio/exporters/scope.ts +1 -0
- package/apps/studio/exporters/svg.ts +14 -22
- package/apps/studio/exporters/video.ts +15 -17
- package/apps/studio/git/service.ts +3 -1
- package/apps/studio/history.ts +47 -1
- package/apps/studio/http.ts +368 -50
- package/apps/studio/input-router.tsx +12 -0
- package/apps/studio/marquee-overlay.tsx +1 -1
- package/apps/studio/measure-overlay.tsx +241 -0
- package/apps/studio/participants-chrome.tsx +3 -3
- package/apps/studio/server.ts +3 -1
- package/apps/studio/sizing-mode.ts +117 -0
- package/apps/studio/spacing-handles.ts +166 -0
- package/apps/studio/test/annotate-write.test.ts +890 -0
- package/apps/studio/test/camera-reveal.test.tsx +173 -0
- package/apps/studio/test/canvas-edit.test.ts +50 -0
- package/apps/studio/test/canvas-origin-gate.test.ts +24 -0
- package/apps/studio/test/canvas-rects.test.ts +198 -0
- package/apps/studio/test/comments-overlay.test.ts +117 -0
- package/apps/studio/test/dns-rebinding-guard.test.ts +27 -0
- package/apps/studio/test/dom-selection.test.ts +130 -0
- package/apps/studio/test/edit-css-occurrence.test.ts +81 -0
- package/apps/studio/test/edit-scope-api.test.ts +115 -0
- package/apps/studio/test/element-resize.test.ts +136 -0
- package/apps/studio/test/element-structural-api.test.ts +360 -0
- package/apps/studio/test/element-structural-edit.test.ts +233 -0
- package/apps/studio/test/equal-spacing-detector.test.ts +165 -1
- package/apps/studio/test/export-center.test.tsx +287 -0
- package/apps/studio/test/export-shim-multi-capture.test.ts +83 -0
- package/apps/studio/test/exporters/history.test.ts +32 -3
- package/apps/studio/test/exporters/jobs.test.ts +263 -0
- package/apps/studio/test/history-rollback.test.ts +26 -0
- package/apps/studio/test/knob-props-authored.test.ts +87 -0
- package/apps/studio/test/read-annotations.test.ts +154 -0
- package/apps/studio/test/sizing-mode.test.ts +102 -0
- package/apps/studio/test/spacing-handles.test.ts +138 -0
- package/apps/studio/test/specimen-select.test.ts +88 -0
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +84 -0
- package/apps/studio/test/undo-sequence-byte-compare.test.ts +211 -0
- package/apps/studio/tool-palette.tsx +122 -2
- package/apps/studio/undo-hud.tsx +2 -2
- package/apps/studio/use-element-resize.tsx +732 -0
- package/apps/studio/use-keyboard-discipline.tsx +205 -15
- package/apps/studio/use-selection-set.tsx +14 -0
- package/apps/studio/use-spacing-handles.tsx +388 -0
- package/apps/studio/whats-new.json +45 -0
- package/apps/studio/ws.ts +6 -0
- package/cli/commands/design.mjs +6 -1
- package/cli/commands/design.test.mjs +49 -1
- package/cli/lib/fetch-asset.test.mjs +213 -0
- package/cli/lib/gitignore-block.mjs +1 -0
- package/package.json +8 -8
- package/plugins/design/dependencies.json +10 -2
|
@@ -13,10 +13,11 @@ import path from 'node:path';
|
|
|
13
13
|
import JSZip from 'jszip';
|
|
14
14
|
|
|
15
15
|
import { getBrowserBundle } from './_browser-bundles.ts';
|
|
16
|
-
import { exportShimPath,
|
|
16
|
+
import { exportShimPath, runShim } from './_runtime.ts';
|
|
17
17
|
import {
|
|
18
18
|
canvasShellUrl,
|
|
19
19
|
type ExportContext,
|
|
20
|
+
type ExportHooks,
|
|
20
21
|
type ExportOptions,
|
|
21
22
|
type ExportResult,
|
|
22
23
|
} from './index.ts';
|
|
@@ -30,7 +31,8 @@ async function captureSvg(
|
|
|
30
31
|
ctx: ExportContext,
|
|
31
32
|
outDir: string,
|
|
32
33
|
timeoutSec: number,
|
|
33
|
-
bundlePath: string
|
|
34
|
+
bundlePath: string,
|
|
35
|
+
hooks?: ExportHooks
|
|
34
36
|
): Promise<string[]> {
|
|
35
37
|
const args = [
|
|
36
38
|
SVG_PLAYWRIGHT,
|
|
@@ -55,31 +57,20 @@ async function captureSvg(
|
|
|
55
57
|
// Run via a resolved node/bun runtime so the shim's `import 'playwright'`
|
|
56
58
|
// resolves against dev-server/node_modules (playwright is a devDep). `npm exec`
|
|
57
59
|
// doesn't bridge the module path for ESM imports — confirmed against npm 10.x.
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
|
|
60
|
+
// runShim resolves the runtime internally so a compiled binary without
|
|
61
|
+
// `node` on PATH surfaces an actionable error, not `posix_spawn 'node'`.
|
|
62
|
+
return runShim(args, {
|
|
61
63
|
cwd: path.dirname(SVG_PLAYWRIGHT),
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
signal: hooks?.signal,
|
|
65
|
+
onProgress: hooks?.onProgress,
|
|
64
66
|
});
|
|
65
|
-
const [stdout, stderr] = await Promise.all([
|
|
66
|
-
new Response(proc.stdout).text(),
|
|
67
|
-
new Response(proc.stderr).text(),
|
|
68
|
-
]);
|
|
69
|
-
const code = await proc.exited;
|
|
70
|
-
if (code !== 0) {
|
|
71
|
-
throw new Error(`_svg-playwright exited ${code}: ${stderr.trim() || stdout.trim()}`);
|
|
72
|
-
}
|
|
73
|
-
return stdout
|
|
74
|
-
.split('\n')
|
|
75
|
-
.map((s) => s.trim())
|
|
76
|
-
.filter(Boolean);
|
|
77
67
|
}
|
|
78
68
|
|
|
79
69
|
export async function run(
|
|
80
70
|
targets: Target[],
|
|
81
71
|
options: ExportOptions,
|
|
82
|
-
ctx: ExportContext
|
|
72
|
+
ctx: ExportContext,
|
|
73
|
+
hooks?: ExportHooks
|
|
83
74
|
): Promise<ExportResult> {
|
|
84
75
|
if (!targets.length) {
|
|
85
76
|
return { filename: 'export.svg', contentType: 'image/svg+xml', body: new Uint8Array(0) };
|
|
@@ -96,9 +87,10 @@ export async function run(
|
|
|
96
87
|
|
|
97
88
|
try {
|
|
98
89
|
const written: string[] = [];
|
|
99
|
-
for (
|
|
100
|
-
const paths = await captureSvg(
|
|
90
|
+
for (let i = 0; i < elementTargets.length; i += 1) {
|
|
91
|
+
const paths = await captureSvg(elementTargets[i], ctx, tmp, timeoutSec, bundlePath, hooks);
|
|
101
92
|
written.push(...paths);
|
|
93
|
+
hooks?.onProgress?.({ current: i + 1, total: elementTargets.length });
|
|
102
94
|
}
|
|
103
95
|
if (!written.length) {
|
|
104
96
|
return { filename: 'export.svg', contentType: 'image/svg+xml', body: new Uint8Array(0) };
|
|
@@ -20,10 +20,11 @@ import { tmpdir } from 'node:os';
|
|
|
20
20
|
import path from 'node:path';
|
|
21
21
|
|
|
22
22
|
import { getEncodeLibBundle, getWebRendererBundle } from './_browser-bundles.ts';
|
|
23
|
-
import { exportShimPath,
|
|
23
|
+
import { exportShimPath, runShim } from './_runtime.ts';
|
|
24
24
|
import {
|
|
25
25
|
canvasShellUrl,
|
|
26
26
|
type ExportContext,
|
|
27
|
+
type ExportHooks,
|
|
27
28
|
type ExportOptions,
|
|
28
29
|
type ExportResult,
|
|
29
30
|
} from './index.ts';
|
|
@@ -47,7 +48,8 @@ async function runVideo(
|
|
|
47
48
|
format: VideoFormat,
|
|
48
49
|
targets: Target[],
|
|
49
50
|
options: ExportOptions,
|
|
50
|
-
ctx: ExportContext
|
|
51
|
+
ctx: ExportContext,
|
|
52
|
+
hooks?: ExportHooks
|
|
51
53
|
): Promise<ExportResult> {
|
|
52
54
|
const el = targets.find((t): t is Extract<Target, { kind: 'element' }> => t.kind === 'element');
|
|
53
55
|
if (!el) {
|
|
@@ -119,25 +121,18 @@ async function runVideo(
|
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
try {
|
|
122
|
-
const
|
|
124
|
+
const stdoutLines = await runShim(args, {
|
|
123
125
|
cwd: path.dirname(VIDEO_PLAYWRIGHT),
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
signal: hooks?.signal,
|
|
127
|
+
// No MAUDE_PROGRESS line from this shim yet — frame-count progress is a
|
|
128
|
+
// follow-up (video scope is always a single artboard, never multi).
|
|
126
129
|
});
|
|
127
|
-
const [stdout, stderr] = await Promise.all([
|
|
128
|
-
new Response(proc.stdout).text(),
|
|
129
|
-
new Response(proc.stderr).text(),
|
|
130
|
-
]);
|
|
131
|
-
const code = await proc.exited;
|
|
132
|
-
if (code !== 0) {
|
|
133
|
-
throw new Error(`_video-playwright exited ${code}: ${stderr.trim() || stdout.trim()}`);
|
|
134
|
-
}
|
|
135
130
|
const body = new Uint8Array(readFileSync(outPath));
|
|
136
131
|
// The container can differ from the request (mp4 → webm fallback when the
|
|
137
132
|
// browser has no H.264 encoder). Read the summary line for the real ext.
|
|
138
133
|
let ext: string = format;
|
|
139
134
|
try {
|
|
140
|
-
const lastLine =
|
|
135
|
+
const lastLine = stdoutLines.at(-1) ?? '{}';
|
|
141
136
|
const summary = JSON.parse(lastLine) as { container?: string };
|
|
142
137
|
if (summary.container) ext = summary.container;
|
|
143
138
|
} catch {
|
|
@@ -174,11 +169,14 @@ function resolveFrames(options: ExportOptions, fps: number | undefined): number
|
|
|
174
169
|
}
|
|
175
170
|
|
|
176
171
|
export const mp4 = {
|
|
177
|
-
run: (t: Target[], o: ExportOptions, c: ExportContext
|
|
172
|
+
run: (t: Target[], o: ExportOptions, c: ExportContext, h?: ExportHooks) =>
|
|
173
|
+
runVideo('mp4', t, o, c, h),
|
|
178
174
|
};
|
|
179
175
|
export const webm = {
|
|
180
|
-
run: (t: Target[], o: ExportOptions, c: ExportContext
|
|
176
|
+
run: (t: Target[], o: ExportOptions, c: ExportContext, h?: ExportHooks) =>
|
|
177
|
+
runVideo('webm', t, o, c, h),
|
|
181
178
|
};
|
|
182
179
|
export const gif = {
|
|
183
|
-
run: (t: Target[], o: ExportOptions, c: ExportContext
|
|
180
|
+
run: (t: Target[], o: ExportOptions, c: ExportContext, h?: ExportHooks) =>
|
|
181
|
+
runVideo('gif', t, o, c, h),
|
|
184
182
|
};
|
|
@@ -211,7 +211,9 @@ function isMaudeRuntimeState(p: string): boolean {
|
|
|
211
211
|
return (
|
|
212
212
|
/(^|\/)_(?:server|active|sync|preflight|locator|export-history)\.json$/.test(p) ||
|
|
213
213
|
/(^|\/)_server\.(?:lock|log)$/.test(p) ||
|
|
214
|
-
/(^|\/)_(?:history|trash|draw|smoke|canvas-state|state|chat|comments|untrusted)(?:\/|$)/.test(
|
|
214
|
+
/(^|\/)_(?:history|trash|draw|smoke|canvas-state|state|chat|comments|untrusted|export-jobs)(?:\/|$)/.test(
|
|
215
|
+
p
|
|
216
|
+
)
|
|
215
217
|
);
|
|
216
218
|
}
|
|
217
219
|
|
package/apps/studio/history.ts
CHANGED
|
@@ -6,11 +6,24 @@
|
|
|
6
6
|
|
|
7
7
|
import type { Dirent } from 'node:fs';
|
|
8
8
|
import { existsSync } from 'node:fs';
|
|
9
|
-
import { readdir } from 'node:fs/promises';
|
|
9
|
+
import { readdir, unlink } from 'node:fs/promises';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
|
|
12
12
|
import type { Context } from './context.ts';
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Per-slug snapshot cap (G3 security, DDR-152). The structural-edit routes
|
|
16
|
+
* (delete / insert / resize-artboard) each write a WHOLE-FILE snapshot here, and
|
|
17
|
+
* an untrusted active canvas can drive them in a loop — so `_history/<slug>/`
|
|
18
|
+
* was the uncapped disk-fill surface the adversarial review flagged. Pruning the
|
|
19
|
+
* oldest pairs bounds disk regardless of edit rate; 300 pairs is still a deep
|
|
20
|
+
* rollback runway for real work. Read lazily so tests can tune it via env.
|
|
21
|
+
*/
|
|
22
|
+
function maxSnapshotsPerSlug(): number {
|
|
23
|
+
const env = Number(process.env.MAUDE_MAX_SNAPSHOTS);
|
|
24
|
+
return Number.isFinite(env) && env > 0 ? Math.floor(env) : 300;
|
|
25
|
+
}
|
|
26
|
+
|
|
14
27
|
export interface Snapshot {
|
|
15
28
|
slug: string;
|
|
16
29
|
ts: string; // ISO
|
|
@@ -92,9 +105,42 @@ export function createHistory(ctx: Context): History {
|
|
|
92
105
|
};
|
|
93
106
|
await Bun.write(contentPath, contentBytes);
|
|
94
107
|
await Bun.write(meta.metaPath, JSON.stringify({ ...meta, file }, null, 2));
|
|
108
|
+
await pruneSnapshots(slug).catch(() => {
|
|
109
|
+
/* pruning is best-effort — never fail a snapshot over it */
|
|
110
|
+
});
|
|
95
111
|
return meta;
|
|
96
112
|
}
|
|
97
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Keep only the newest MAX_SNAPSHOTS_PER_SLUG snapshot pairs for a slug,
|
|
116
|
+
* unlinking the oldest content-blob + `.json` sidecar beyond the cap.
|
|
117
|
+
* Filenames are ts-derived (`tsForFilename`) so a lexical sort is chronological.
|
|
118
|
+
*/
|
|
119
|
+
async function pruneSnapshots(slug: string): Promise<void> {
|
|
120
|
+
const dir = path.join(ctx.paths.historyDir, slug);
|
|
121
|
+
let names: string[];
|
|
122
|
+
try {
|
|
123
|
+
names = await readdir(dir);
|
|
124
|
+
} catch {
|
|
125
|
+
return; // no dir yet
|
|
126
|
+
}
|
|
127
|
+
const stems = names
|
|
128
|
+
.filter((n) => n.endsWith('.json'))
|
|
129
|
+
.map((n) => n.slice(0, -'.json'.length))
|
|
130
|
+
.sort();
|
|
131
|
+
const excess = stems.length - maxSnapshotsPerSlug();
|
|
132
|
+
if (excess <= 0) return;
|
|
133
|
+
for (const stem of stems.slice(0, excess)) {
|
|
134
|
+
for (const n of names) {
|
|
135
|
+
// The meta (`<stem>.json`) and its content blob (`<stem>.<ext>`) share
|
|
136
|
+
// the stem; the fixed-length ISO stem is never a prefix of another.
|
|
137
|
+
if (n === `${stem}.json` || n.startsWith(`${stem}.`)) {
|
|
138
|
+
await unlink(path.join(dir, n)).catch(() => {});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
98
144
|
async function listSnapshots(file: string): Promise<Snapshot[]> {
|
|
99
145
|
const slug = fileSlug(file, ctx.paths.designRel);
|
|
100
146
|
const dir = path.join(ctx.paths.historyDir, slug);
|