@1agh/maude 0.58.2 → 0.58.3
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-bindings.ts +83 -4
- package/apps/studio/api.ts +6 -1
- package/apps/studio/bin/_fetch-asset.mjs +169 -5
- package/apps/studio/bin/_import-asset.mjs +72 -0
- package/apps/studio/bin/_import-figma.mjs +1121 -0
- package/apps/studio/bin/_video-playwright.mjs +86 -3
- package/apps/studio/bin/import-figma.sh +38 -0
- package/apps/studio/bin/read-annotations.mjs +11 -1
- package/apps/studio/bun.lock +16 -22
- package/apps/studio/canvas-edit.ts +29 -5
- package/apps/studio/client/app.jsx +44 -1
- package/apps/studio/client/export-center.jsx +42 -4
- package/apps/studio/client/panels/CloudBar.jsx +92 -1
- package/apps/studio/client/panels/FigmaImportPanel.jsx +264 -0
- package/apps/studio/client/panels/GitPanel.jsx +26 -6
- package/apps/studio/client/panels/SettingsPanel.jsx +181 -0
- package/apps/studio/client/panels/SetupChecklist.jsx +26 -2
- package/apps/studio/client/panels/TimelinePanel.jsx +2 -2
- package/apps/studio/client/panels/timeline-parse.js +3 -3
- package/apps/studio/client/styles/3-shell-maude.css +7 -0
- package/apps/studio/client/styles/4-components.css +134 -0
- package/apps/studio/clip-ops.ts +93 -17
- package/apps/studio/cloud/endpoints.ts +78 -10
- package/apps/studio/cloud/renew.ts +183 -0
- package/apps/studio/context.ts +2 -1
- package/apps/studio/dist/client.bundle.js +1261 -1261
- package/apps/studio/dist/runtime/@remotion_media.js +56 -136
- package/apps/studio/dist/runtime/@remotion_player.js +18 -18
- package/apps/studio/dist/runtime/@remotion_transitions.js +9 -9
- package/apps/studio/dist/runtime/@remotion_transitions_clock-wipe.js +1 -1
- package/apps/studio/dist/runtime/remotion.js +12 -12
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/exporters/_browser-bundles.ts +20 -6
- package/apps/studio/exporters/_runtime.ts +19 -0
- package/apps/studio/exporters/degraded.ts +92 -0
- package/apps/studio/exporters/index.ts +5 -0
- package/apps/studio/exporters/jobs.ts +19 -0
- package/apps/studio/exporters/unsupported-media.ts +170 -0
- package/apps/studio/exporters/video-encode-lib.ts +27 -1
- package/apps/studio/exporters/video-render-lib.ts +6 -0
- package/apps/studio/exporters/video.ts +62 -1
- package/apps/studio/figma/assets.test.ts +372 -0
- package/apps/studio/figma/assets.ts +398 -0
- package/apps/studio/figma/client.test.ts +395 -0
- package/apps/studio/figma/client.ts +513 -0
- package/apps/studio/figma/comments-to-strokes.test.ts +194 -0
- package/apps/studio/figma/comments-to-strokes.ts +173 -0
- package/apps/studio/figma/endpoints.ts +200 -0
- package/apps/studio/figma/sanitize.test.ts +256 -0
- package/apps/studio/figma/sanitize.ts +315 -0
- package/apps/studio/figma/style-map.ts +352 -0
- package/apps/studio/figma/to-artboard.test.ts +808 -0
- package/apps/studio/figma/to-artboard.ts +701 -0
- package/apps/studio/figma/to-render.test.ts +180 -0
- package/apps/studio/figma/to-render.ts +306 -0
- package/apps/studio/figma/to-strokes-roundtrip.test.ts +152 -0
- package/apps/studio/figma/to-strokes.test.ts +705 -0
- package/apps/studio/figma/to-strokes.ts +749 -0
- package/apps/studio/figma/to-tokens.test.ts +321 -0
- package/apps/studio/figma/to-tokens.ts +305 -0
- package/apps/studio/figma/types.ts +539 -0
- package/apps/studio/figma/url.test.ts +167 -0
- package/apps/studio/figma/url.ts +160 -0
- package/apps/studio/http.ts +129 -0
- package/apps/studio/sync/asset-push.ts +124 -0
- package/apps/studio/sync/connection-state.ts +11 -0
- package/apps/studio/sync/hub-link.ts +63 -7
- package/apps/studio/sync/hubs-config.ts +31 -3
- package/apps/studio/sync/index.ts +276 -26
- package/apps/studio/sync/migrate-flat-fallback.ts +121 -0
- package/apps/studio/sync/presentation.ts +45 -1
- package/apps/studio/sync/supervisor.ts +5 -1
- package/apps/studio/sync/workspace-signin.ts +7 -3
- package/apps/studio/test/annotations-bindings.test.ts +150 -12
- package/apps/studio/test/canvas-create-api.test.ts +4 -1
- package/apps/studio/test/canvas-origin-gate.test.ts +13 -0
- package/apps/studio/test/capture-determinism-shape.test.ts +135 -0
- package/apps/studio/test/clip-addressing.test.ts +6 -1
- package/apps/studio/test/clip-ops.test.ts +5 -1
- package/apps/studio/test/cloud-endpoints.test.ts +96 -0
- package/apps/studio/test/cloud-renew.test.ts +205 -0
- package/apps/studio/test/cloud-shell-surfaces.test.ts +11 -2
- package/apps/studio/test/exporters/degraded-propagation.test.ts +123 -0
- package/apps/studio/test/exporters/unsupported-media.test.ts +123 -0
- package/apps/studio/test/fetch-asset-gate.test.ts +189 -0
- package/apps/studio/test/figma-provenance.test.ts +108 -0
- package/apps/studio/test/figma-routes.test.ts +294 -0
- package/apps/studio/test/git-cloud-posture.test.ts +50 -0
- package/apps/studio/test/hub-link.test.ts +11 -0
- package/apps/studio/test/import-figma.test.ts +479 -0
- package/apps/studio/test/sync-asset-push.test.ts +124 -0
- package/apps/studio/test/sync-connection-state.test.ts +13 -0
- package/apps/studio/test/sync-hubs-config.test.ts +5 -0
- package/apps/studio/test/sync-migrate-flat-fallback.test.ts +98 -0
- package/apps/studio/test/sync-path-pull.test.ts +63 -0
- package/apps/studio/test/sync-presentation.test.ts +77 -0
- package/apps/studio/test/sync-runtime.test.ts +316 -1
- package/apps/studio/test/video-comp.test.ts +23 -1
- package/apps/studio/test/workspace-containment.test.ts +1 -0
- package/apps/studio/video-comp.tsx +70 -6
- package/apps/studio/whats-new.json +27 -0
- package/apps/studio/workspace-mode.ts +4 -0
- package/cli/commands/design.mjs +8 -0
- package/package.json +8 -8
- package/plugins/flow/.claude-plugin/config.schema.json +3 -3
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// Silent hub-credential renewal — the missing half of the cell-token model.
|
|
2
|
+
//
|
|
3
|
+
// The cell session token dies with the 12 h project token that minted it
|
|
4
|
+
// (Phase 23 B2 — deliberate: that cap IS the revocation window, and it stays).
|
|
5
|
+
// The account/device token next to it never expires. Nothing connected the
|
|
6
|
+
// two: the desktop held a forever credential and a 12 h credential side by
|
|
7
|
+
// side, and when the short one died the user was told to press Connect again —
|
|
8
|
+
// or, worse, told nothing (the alligators incident: 73 canvases permanently
|
|
9
|
+
// `connecting…`).
|
|
10
|
+
//
|
|
11
|
+
// This module replays the attach lane's credential tail WITHOUT the ceremony:
|
|
12
|
+
// GET /api/projects (account token) → which project owns this hub
|
|
13
|
+
// POST /projects/open (account token) → fresh 12 h project token
|
|
14
|
+
// POST <cell>/auth/login (project token) → fresh cell session
|
|
15
|
+
// saveHubCredential(…) → hubs.json, role preserved
|
|
16
|
+
//
|
|
17
|
+
// It deliberately does NOT touch `.design/config.json` (the link is already
|
|
18
|
+
// recorded; renewal is not a re-link) and does NOT cycle the sync runtime (the
|
|
19
|
+
// caller swaps the token in place). A renewal fails only on a REAL event —
|
|
20
|
+
// signed out, token revoked on the Account page, removed from the project —
|
|
21
|
+
// and that failure, not a clock, is what should reach the user.
|
|
22
|
+
//
|
|
23
|
+
// Trust posture: every hop talks only to the CONFIGURED cloud address or to
|
|
24
|
+
// the workspace URL the cloud's own /api/projects listing names for the
|
|
25
|
+
// matched project — never to an address read from repo-committed state.
|
|
26
|
+
|
|
27
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
28
|
+
import { homedir } from 'node:os';
|
|
29
|
+
import { join } from 'node:path';
|
|
30
|
+
|
|
31
|
+
import { saveHubCredential } from '../sync/hub-link.ts';
|
|
32
|
+
import { getHubRecord, normalizeUrl } from '../sync/hubs-config.ts';
|
|
33
|
+
|
|
34
|
+
export interface RenewedCredential {
|
|
35
|
+
token: string;
|
|
36
|
+
expiresAt: number | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type RenewFailureReason =
|
|
40
|
+
| 'not-signed-in'
|
|
41
|
+
| 'account-revoked'
|
|
42
|
+
| 'no-matching-project'
|
|
43
|
+
| 'open-refused'
|
|
44
|
+
| 'cell-refused'
|
|
45
|
+
| 'save-failed'
|
|
46
|
+
| 'unreachable';
|
|
47
|
+
|
|
48
|
+
export type RenewResult =
|
|
49
|
+
| ({ ok: true } & RenewedCredential)
|
|
50
|
+
| { ok: false; reason: RenewFailureReason };
|
|
51
|
+
|
|
52
|
+
/** Mirrors cloud/endpoints.ts — resolved per call so tests/desktop can set it late. */
|
|
53
|
+
function cloudUrl(): string {
|
|
54
|
+
return (process.env.MAUDE_CLOUD_URL ?? 'https://cloud.maude.sh').replace(/\/+$/, '');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function cloudConfigPath(): string {
|
|
58
|
+
return process.env.MAUDE_CLOUD_CONFIG ?? join(homedir(), '.config', 'maude', 'cloud.json');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The account credential, or null when this machine is signed out. */
|
|
62
|
+
function readAccountToken(): string | null {
|
|
63
|
+
const p = cloudConfigPath();
|
|
64
|
+
if (!existsSync(p)) return null;
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse(readFileSync(p, 'utf8'));
|
|
67
|
+
return parsed && typeof parsed.token === 'string' && parsed.token ? parsed.token : null;
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const FETCH_TIMEOUT_MS = 15_000;
|
|
74
|
+
|
|
75
|
+
async function jsonFetch(
|
|
76
|
+
doFetch: typeof fetch,
|
|
77
|
+
url: string,
|
|
78
|
+
init: RequestInit = {}
|
|
79
|
+
): Promise<{ status: number; body: Record<string, unknown> }> {
|
|
80
|
+
const ctl = new AbortController();
|
|
81
|
+
const timer = setTimeout(() => ctl.abort(), FETCH_TIMEOUT_MS);
|
|
82
|
+
try {
|
|
83
|
+
const res = await doFetch(url, { ...init, signal: ctl.signal });
|
|
84
|
+
const body = (await res.json().catch(() => ({}))) as Record<string, unknown>;
|
|
85
|
+
return { status: res.status, body };
|
|
86
|
+
} catch {
|
|
87
|
+
return { status: 0, body: {} };
|
|
88
|
+
} finally {
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Mint a fresh cell session for `hubUrl` from the signed-in Maude account.
|
|
95
|
+
*
|
|
96
|
+
* Single network path, no interaction. Callers treat any `ok:false` as "keep
|
|
97
|
+
* the old behaviour" — the stored (possibly dead) credential stays in place so
|
|
98
|
+
* a failed renewal never makes things worse than before it ran.
|
|
99
|
+
*/
|
|
100
|
+
export async function renewHubCredential(
|
|
101
|
+
hubUrl: string,
|
|
102
|
+
doFetch: typeof fetch = fetch
|
|
103
|
+
): Promise<RenewResult> {
|
|
104
|
+
const account = readAccountToken();
|
|
105
|
+
if (!account) return { ok: false, reason: 'not-signed-in' };
|
|
106
|
+
|
|
107
|
+
let norm: string;
|
|
108
|
+
try {
|
|
109
|
+
norm = normalizeUrl(hubUrl);
|
|
110
|
+
} catch {
|
|
111
|
+
return { ok: false, reason: 'no-matching-project' };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Which cloud project opens onto this hub? The listing is the only mapping
|
|
115
|
+
// that exists (the hub URL is derived from the project id at open time), and
|
|
116
|
+
// asking it fresh means a renamed/removed project fails here — the right
|
|
117
|
+
// place — instead of minting for a project this account no longer has.
|
|
118
|
+
const listed = await jsonFetch(doFetch, `${cloudUrl()}/api/projects`, {
|
|
119
|
+
headers: { authorization: `Bearer ${account}` },
|
|
120
|
+
});
|
|
121
|
+
if (listed.status === 401) return { ok: false, reason: 'account-revoked' };
|
|
122
|
+
if (listed.status !== 200) return { ok: false, reason: 'unreachable' };
|
|
123
|
+
const projects = Array.isArray(listed.body.projects) ? listed.body.projects : [];
|
|
124
|
+
const match = projects.find((p) => {
|
|
125
|
+
const url = (p as { url?: unknown })?.url;
|
|
126
|
+
if (typeof url !== 'string' || !url) return false;
|
|
127
|
+
try {
|
|
128
|
+
return normalizeUrl(url) === norm;
|
|
129
|
+
} catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}) as { id?: unknown; role?: unknown } | undefined;
|
|
133
|
+
// A viewer's Connect is a different lane (share-view); renewing an editor
|
|
134
|
+
// credential for one would mint capability the panel never offered.
|
|
135
|
+
if (!match || typeof match.id !== 'string' || match.role === 'viewer') {
|
|
136
|
+
return { ok: false, reason: 'no-matching-project' };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const opened = await jsonFetch(doFetch, `${cloudUrl()}/projects/open`, {
|
|
140
|
+
method: 'POST',
|
|
141
|
+
headers: { authorization: `Bearer ${account}`, 'content-type': 'application/json' },
|
|
142
|
+
body: JSON.stringify({ project: match.id }),
|
|
143
|
+
});
|
|
144
|
+
if (opened.status === 0) return { ok: false, reason: 'unreachable' };
|
|
145
|
+
if (opened.status !== 200 || typeof opened.body.token !== 'string' || !opened.body.token) {
|
|
146
|
+
// This is where a removed member's renewal dies — exactly where it should.
|
|
147
|
+
return { ok: false, reason: 'open-refused' };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const cellLogin = await jsonFetch(doFetch, `${norm}/auth/login`, {
|
|
151
|
+
method: 'POST',
|
|
152
|
+
headers: { 'content-type': 'application/json' },
|
|
153
|
+
body: JSON.stringify({ token: opened.body.token }),
|
|
154
|
+
});
|
|
155
|
+
if (cellLogin.status === 0) return { ok: false, reason: 'unreachable' };
|
|
156
|
+
if (
|
|
157
|
+
cellLogin.status !== 200 ||
|
|
158
|
+
typeof cellLogin.body.token !== 'string' ||
|
|
159
|
+
!cellLogin.body.token
|
|
160
|
+
) {
|
|
161
|
+
return { ok: false, reason: 'cell-refused' };
|
|
162
|
+
}
|
|
163
|
+
const expiresAt =
|
|
164
|
+
typeof cellLogin.body.expiresAt === 'number' && Number.isFinite(cellLogin.body.expiresAt)
|
|
165
|
+
? cellLogin.body.expiresAt
|
|
166
|
+
: null;
|
|
167
|
+
|
|
168
|
+
// The upsert replaces the whole record, so the role must be re-supplied. F5:
|
|
169
|
+
// prefer the role the cell JUST vouched (its /auth/login echoes `user.role`)
|
|
170
|
+
// over the stored one — this is the one moment the code learns the current
|
|
171
|
+
// role, and carrying the stale value forward would discard exactly that.
|
|
172
|
+
// Fall back to the prior role only when the cell didn't say (older cell).
|
|
173
|
+
const freshRoleRaw = (cellLogin.body.user as { role?: unknown } | undefined)?.role;
|
|
174
|
+
const freshRole = typeof freshRoleRaw === 'string' && freshRoleRaw ? freshRoleRaw : undefined;
|
|
175
|
+
const role = freshRole ?? getHubRecord(norm)?.role;
|
|
176
|
+
try {
|
|
177
|
+
saveHubCredential(norm, cellLogin.body.token, role, expiresAt ?? undefined);
|
|
178
|
+
} catch {
|
|
179
|
+
return { ok: false, reason: 'save-failed' };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return { ok: true, token: cellLogin.body.token, expiresAt };
|
|
183
|
+
}
|
package/apps/studio/context.ts
CHANGED
|
@@ -191,7 +191,8 @@ export interface Context {
|
|
|
191
191
|
* non-serving entry points construct a Context without one.
|
|
192
192
|
*/
|
|
193
193
|
syncControl?: {
|
|
194
|
-
|
|
194
|
+
/** `null` means UNLINK: clear the in-memory link and cycle back to solo. */
|
|
195
|
+
restart(linkedHub?: LinkedHub | null): Promise<{
|
|
195
196
|
syncing: boolean;
|
|
196
197
|
canvases: number;
|
|
197
198
|
reason?: string;
|