@1agh/maude 0.49.2 → 0.50.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/api.ts +751 -52
- package/apps/studio/canvas-artifacts.ts +153 -0
- package/apps/studio/canvas-create.ts +23 -0
- package/apps/studio/canvas-slug.ts +30 -0
- package/apps/studio/client/app.jsx +547 -90
- package/apps/studio/client/panels/CloudBar.jsx +284 -0
- package/apps/studio/client/styles/3-shell-maude.css +26 -0
- package/apps/studio/client/tree-row-menu.jsx +132 -0
- package/apps/studio/client/use-tree-drag.js +120 -0
- package/apps/studio/cloud/endpoints.ts +236 -0
- package/apps/studio/collab/registry.ts +23 -0
- package/apps/studio/dist/client.bundle.js +960 -958
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/http.ts +113 -0
- package/apps/studio/inspect.ts +49 -0
- package/apps/studio/server.ts +15 -0
- package/apps/studio/test/canvas-artifacts.test.ts +125 -0
- package/apps/studio/test/canvas-create-api.test.ts +159 -1
- package/apps/studio/test/canvas-move-api.test.ts +556 -0
- package/apps/studio/test/canvas-origin-gate.test.ts +6 -0
- package/apps/studio/test/fs-mkdir-api.test.ts +248 -0
- package/apps/studio/whats-new.json +18 -0
- package/cli/bin/maude.mjs +1 -0
- package/cli/commands/hub-workspace.mjs +294 -7
- package/cli/commands/init.mjs +33 -2
- package/cli/commands/kg.mjs +129 -4
- package/cli/commands/share.mjs +184 -0
- package/cli/lib/ddr-to-kgai.mjs +310 -82
- package/cli/lib/ddr-to-kgai.test.mjs +20 -0
- package/cli/lib/share-plan.mjs +96 -0
- package/cli/lib/share-plan.test.mjs +57 -0
- package/cli/lib/workspace-plan.mjs +130 -12
- package/cli/lib/workspace-plan.test.mjs +160 -0
- package/package.json +8 -8
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// The canonical inventory of every on-disk artifact belonging to a canvas,
|
|
2
|
+
// derived from its `rel` path (designRoot-relative, POSIX, e.g. "ui/Foo.tsx").
|
|
3
|
+
// Pure — no `Bun.*` / `node:fs`, so it's fully unit-testable. `deleteCanvas`
|
|
4
|
+
// and `moveCanvas` (api.ts) both consume this instead of hand-listing sidecars,
|
|
5
|
+
// so the DDR-115 runtime-state taxonomy is expressed in exactly one place.
|
|
6
|
+
//
|
|
7
|
+
// Deliberately NOT included: `_photo/` (asset-keyed, not canvas-slug-keyed —
|
|
8
|
+
// see photo-store.ts) and `_draw/<slug>.proof.tsx` (mark-slug-keyed, a
|
|
9
|
+
// different slug namespace). Adding either here would silently relocate/trash
|
|
10
|
+
// something that isn't actually addressed by this canvas's slug.
|
|
11
|
+
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
|
|
14
|
+
import { canvasSlugFromRel } from './canvas-slug.ts';
|
|
15
|
+
import type { Paths } from './context.ts';
|
|
16
|
+
|
|
17
|
+
export interface CanvasArtifact {
|
|
18
|
+
/** Absolute on-disk path. */
|
|
19
|
+
abs: string;
|
|
20
|
+
/** The primary `.tsx`, a same-basename sibling, or a slug-keyed sidecar. */
|
|
21
|
+
kind: 'primary' | 'sibling' | 'slug-keyed';
|
|
22
|
+
/**
|
|
23
|
+
* True when the artifact's on-disk NAME is derived from the canvas's slug
|
|
24
|
+
* (so a rename — not just a directory change — requires renaming this
|
|
25
|
+
* artifact too). False for primary/sibling, whose name tracks the canvas's
|
|
26
|
+
* own basename and is otherwise untouched by a pure directory move.
|
|
27
|
+
*/
|
|
28
|
+
rekey: boolean;
|
|
29
|
+
/** True when the artifact is git-tracked (vs. gitignored runtime state —
|
|
30
|
+
* see DDR-115). Informational only; callers don't need to branch on it
|
|
31
|
+
* today, but it keeps the taxonomy legible at the one place it's listed. */
|
|
32
|
+
versioned: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Every on-disk artifact belonging to the canvas at `rel`. Does not check
|
|
37
|
+
* existence — callers `stat`/`rename` best-effort, since most canvases don't
|
|
38
|
+
* have every sidecar (a fresh brief-board has no history, no comments, …).
|
|
39
|
+
*/
|
|
40
|
+
export function canvasArtifacts(input: { rel: string; paths: Paths }): CanvasArtifact[] {
|
|
41
|
+
const { rel, paths } = input;
|
|
42
|
+
const dir = path.posix.dirname(rel.replace(/\\/g, '/'));
|
|
43
|
+
const groupDirAbs = path.join(paths.designRoot, dir === '.' ? '' : dir);
|
|
44
|
+
const base = path.basename(rel).replace(/\.tsx$/i, '');
|
|
45
|
+
const slug = canvasSlugFromRel(rel, paths.designRel);
|
|
46
|
+
|
|
47
|
+
const out: CanvasArtifact[] = [];
|
|
48
|
+
|
|
49
|
+
// Same-dir siblings — same basename, dir tracks the canvas's own dir. Only
|
|
50
|
+
// the primary `.tsx` is guaranteed to exist; the rest are best-effort.
|
|
51
|
+
for (const ext of ['.tsx', '.meta.json', '.css', '.registry.json']) {
|
|
52
|
+
out.push({
|
|
53
|
+
abs: path.join(groupDirAbs, `${base}${ext}`),
|
|
54
|
+
kind: ext === '.tsx' ? 'primary' : 'sibling',
|
|
55
|
+
rekey: false,
|
|
56
|
+
versioned: true,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Slug-keyed sidecars.
|
|
61
|
+
out.push({
|
|
62
|
+
abs: path.join(paths.historyDir, slug),
|
|
63
|
+
kind: 'slug-keyed',
|
|
64
|
+
rekey: true,
|
|
65
|
+
versioned: false,
|
|
66
|
+
});
|
|
67
|
+
out.push({
|
|
68
|
+
abs: path.join(paths.canvasStateDir, `${slug}.json`),
|
|
69
|
+
kind: 'slug-keyed',
|
|
70
|
+
rekey: true,
|
|
71
|
+
versioned: false,
|
|
72
|
+
});
|
|
73
|
+
out.push({
|
|
74
|
+
// DDR-115 — the per-machine camera view file.
|
|
75
|
+
abs: path.join(paths.canvasStateDir, `${slug}.view.json`),
|
|
76
|
+
kind: 'slug-keyed',
|
|
77
|
+
rekey: true,
|
|
78
|
+
versioned: false,
|
|
79
|
+
});
|
|
80
|
+
out.push({
|
|
81
|
+
abs: path.join(paths.commentsDir, `${slug}.json`),
|
|
82
|
+
kind: 'slug-keyed',
|
|
83
|
+
rekey: true,
|
|
84
|
+
versioned: false,
|
|
85
|
+
});
|
|
86
|
+
out.push({
|
|
87
|
+
// collab/persistence.ts ydocBinPath — the `.ydoc.bin` cache under `_state/`.
|
|
88
|
+
abs: path.join(paths.designRoot, '_state', `${slug}.ydoc.bin`),
|
|
89
|
+
kind: 'slug-keyed',
|
|
90
|
+
rekey: true,
|
|
91
|
+
versioned: false,
|
|
92
|
+
});
|
|
93
|
+
out.push({
|
|
94
|
+
// Versioned — a real git rename on move, which is correct and expected.
|
|
95
|
+
abs: path.join(paths.designRoot, `${slug}.annotations.svg`),
|
|
96
|
+
kind: 'slug-keyed',
|
|
97
|
+
rekey: true,
|
|
98
|
+
versioned: true,
|
|
99
|
+
});
|
|
100
|
+
out.push({
|
|
101
|
+
// Footage EDL (feature-footage-analysis-director) — also versioned.
|
|
102
|
+
abs: path.join(paths.designRoot, `${slug}.edl.json`),
|
|
103
|
+
kind: 'slug-keyed',
|
|
104
|
+
rekey: true,
|
|
105
|
+
versioned: true,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* `_locator.json`'s top-level key for a canvas at `rel` — POSIX,
|
|
113
|
+
* extension-less, but NOT lowercased or dash-flattened (unlike
|
|
114
|
+
* `canvasSlugFromRel`). Matches `locator.ts`'s `canvasSlug()`. Kept OUT of
|
|
115
|
+
* `canvasArtifacts()`'s slug-keyed list on purpose: folding it in would let
|
|
116
|
+
* the two divergent slug shapes get confused for each other, which the plan
|
|
117
|
+
* calls out as the single most likely bug in this feature.
|
|
118
|
+
*/
|
|
119
|
+
export function locatorKeyFor(rel: string): string {
|
|
120
|
+
const p = rel.replace(/\\/g, '/');
|
|
121
|
+
const dot = p.lastIndexOf('.');
|
|
122
|
+
return dot > 0 ? p.slice(0, dot) : p;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Where `artifact` lands after the canvas at `fromRel` moves to `toRel`.
|
|
127
|
+
* Primary/sibling artifacts track the destination's directory + basename;
|
|
128
|
+
* slug-keyed artifacts stay in their fixed directory but swap the old slug
|
|
129
|
+
* for the new one in their filename (the `_history/<slug>` dir included —
|
|
130
|
+
* there the "filename" IS the slug).
|
|
131
|
+
*/
|
|
132
|
+
export function relocatedName(
|
|
133
|
+
artifact: CanvasArtifact,
|
|
134
|
+
fromRel: string,
|
|
135
|
+
toRel: string,
|
|
136
|
+
paths: Paths
|
|
137
|
+
): string {
|
|
138
|
+
if (artifact.kind === 'slug-keyed') {
|
|
139
|
+
const fromSlug = canvasSlugFromRel(fromRel, paths.designRel);
|
|
140
|
+
const toSlug = canvasSlugFromRel(toRel, paths.designRel);
|
|
141
|
+
const dir = path.dirname(artifact.abs);
|
|
142
|
+
const name = path.basename(artifact.abs);
|
|
143
|
+
if (name === fromSlug) return path.join(dir, toSlug);
|
|
144
|
+
return path.join(dir, name.replace(fromSlug, toSlug));
|
|
145
|
+
}
|
|
146
|
+
const fromBase = path.basename(fromRel).replace(/\.tsx$/i, '');
|
|
147
|
+
const toBase = path.basename(toRel).replace(/\.tsx$/i, '');
|
|
148
|
+
const oldName = path.basename(artifact.abs);
|
|
149
|
+
const suffix = oldName.slice(fromBase.length); // '.tsx' | '.meta.json' | '.css' | '.registry.json'
|
|
150
|
+
const toDir = path.posix.dirname(toRel.replace(/\\/g, '/'));
|
|
151
|
+
const newDirAbs = path.join(paths.designRoot, toDir === '.' ? '' : toDir);
|
|
152
|
+
return path.join(newDirAbs, `${toBase}${suffix}`);
|
|
153
|
+
}
|
|
@@ -37,6 +37,29 @@ export interface NameValidation {
|
|
|
37
37
|
error?: string;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Folder names reuse `NAME_RE` — the same path/JSX/JSON-safe allowlist as
|
|
42
|
+
* canvas names. Folder names never reach JSX or a JSON string interpolation,
|
|
43
|
+
* but sharing the regex keeps one thing to audit rather than two allowlists
|
|
44
|
+
* that could drift.
|
|
45
|
+
*/
|
|
46
|
+
export function validateFolderName(raw: unknown): NameValidation {
|
|
47
|
+
if (typeof raw !== 'string') return { ok: false, error: 'name must be a string' };
|
|
48
|
+
const name = raw.normalize('NFC').trim().replace(/ +/g, ' ');
|
|
49
|
+
if (!name) return { ok: false, error: 'name is required' };
|
|
50
|
+
if (name.length > 60) return { ok: false, error: 'name must be 60 characters or fewer' };
|
|
51
|
+
if (!NAME_RE.test(name)) {
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
error: 'name may contain only letters, numbers, spaces, hyphens and underscores',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (name.includes('..') || name.includes('/') || name.includes('\\') || name.startsWith('.')) {
|
|
58
|
+
return { ok: false, error: 'name may not contain path separators or leading dots' };
|
|
59
|
+
}
|
|
60
|
+
return { ok: true, name };
|
|
61
|
+
}
|
|
62
|
+
|
|
40
63
|
export function validateCanvasName(raw: unknown): NameValidation {
|
|
41
64
|
if (typeof raw !== 'string') return { ok: false, error: 'name must be a string' };
|
|
42
65
|
// NFC-normalize so a decomposed (NFD) name and its composed sibling can't hash
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Canonical canvas slug derivation. Extracted from api.ts so canvas-artifacts.ts
|
|
2
|
+
// (a pure, dependency-free artifact-inventory module) and api.ts can both depend
|
|
3
|
+
// on it without a cycle between the two. api.ts re-exports this for existing
|
|
4
|
+
// external callers (canvas-list-watch.ts, tests) so their import surface is
|
|
5
|
+
// unchanged.
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Canonical canvas slug from a (repo- or design-root-relative) canvas path.
|
|
9
|
+
* Pure — the `fileSlug` closure inside `createApi` delegates here, and the
|
|
10
|
+
* external-canvas watcher (`canvas-list-watch.ts`) imports it so both creation
|
|
11
|
+
* paths derive identical `canvas-list-update` slugs. Strips an optional
|
|
12
|
+
* `<designRel>/` prefix, then `/`→`-`, whitespace→`_`, drops the `.tsx`/`.html`
|
|
13
|
+
* extension, and lowercases.
|
|
14
|
+
*/
|
|
15
|
+
export function canvasSlugFromRel(file: string, designRel: string): string {
|
|
16
|
+
let p = String(file).replace(/^\/+|\/+$/g, '');
|
|
17
|
+
try {
|
|
18
|
+
p = decodeURIComponent(p);
|
|
19
|
+
} catch {
|
|
20
|
+
/* ignore */
|
|
21
|
+
}
|
|
22
|
+
const prefix = `${designRel.replace(/^\/+|\/+$/g, '')}/`;
|
|
23
|
+
if (p.startsWith(prefix)) p = p.slice(prefix.length);
|
|
24
|
+
return p
|
|
25
|
+
.replace(/\//g, '-')
|
|
26
|
+
.replace(/\s+/g, '_')
|
|
27
|
+
.replace(/\.(tsx|html)$/i, '')
|
|
28
|
+
.replace(/^\.+/, '')
|
|
29
|
+
.toLowerCase();
|
|
30
|
+
}
|