@gakim-digital/dexter-bridge 0.13.5 → 0.13.6
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/package.json +1 -1
- package/src/framerRequirementContract.js +40 -11
- package/src/framerTools.js +9 -2
package/package.json
CHANGED
|
@@ -67,6 +67,20 @@ export function mergeRequirements(current, next) {
|
|
|
67
67
|
return [...byId.values()];
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// A requirement may name layers its own edit creates by their temporary ids,
|
|
71
|
+
// or a breakpoint copy of one as the breakpoint id followed by that temporary
|
|
72
|
+
// id. Once Framer gives those layers real ids, the requirement names them.
|
|
73
|
+
export function renameRequirementNodes(requirements, renamedIds = {}) {
|
|
74
|
+
const renames = Object.entries(renamedIds || {}).filter(([from, to]) => from && to && from !== to);
|
|
75
|
+
if (!renames.length) return requirements;
|
|
76
|
+
const rename = (id) => {
|
|
77
|
+
if (renamedIds[id]) return renamedIds[id];
|
|
78
|
+
const copy = renames.find(([from]) => id.endsWith(from) && /^[A-Za-z0-9_-]{9}$/.test(id.slice(0, -from.length)));
|
|
79
|
+
return copy ? `${id.slice(0, -copy[0].length)}${copy[1]}` : id;
|
|
80
|
+
};
|
|
81
|
+
return requirements.map((item) => ({ ...item, nodeIds: [...new Set(item.nodeIds.map(rename))] }));
|
|
82
|
+
}
|
|
83
|
+
|
|
70
84
|
function finiteNumber(value) {
|
|
71
85
|
const number = Number(value);
|
|
72
86
|
return Number.isFinite(number) ? number : null;
|
|
@@ -312,22 +326,29 @@ export function summarizeRequirements(requirements, checkResults) {
|
|
|
312
326
|
}
|
|
313
327
|
|
|
314
328
|
// Compares two snapshots of layers that had to stay as they were. A snapshot
|
|
315
|
-
// maps a layer id to { name, origin, rect, props } where props is
|
|
316
|
-
// serialized layer without children, origin is the layer a breakpoint or
|
|
317
|
-
// variant copy follows,
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
// below it.
|
|
322
|
-
//
|
|
323
|
-
//
|
|
329
|
+
// maps a layer id to { name, origin, rect, props, children } where props is
|
|
330
|
+
// the serialized layer without children, origin is the layer a breakpoint or
|
|
331
|
+
// variant copy follows, rect is null when Dexter could not measure the layer,
|
|
332
|
+
// so only its settings are compared, and children lists its child layers in
|
|
333
|
+
// order as { id, name, origin }. Sizes are compared only when both readings
|
|
334
|
+
// came from the same source (the user's editor or Framer MCP), and only size
|
|
335
|
+
// counts, not position: a section that grows moves every section below it.
|
|
336
|
+
// The order of child layers does count: a layer inserted without an index
|
|
337
|
+
// lands at the end of its parent.
|
|
338
|
+
// A change is edited when the run edited the layer, the layer it follows, or
|
|
339
|
+
// for a new order one of the child layers; any other change happened without
|
|
340
|
+
// the model touching the layer.
|
|
341
|
+
function childOrder(layer) {
|
|
342
|
+
return layer.children.map((child) => child.id).join(',');
|
|
343
|
+
}
|
|
344
|
+
|
|
324
345
|
export function unchangedViolations(before, after, edited = new Set(), tolerance = 1) {
|
|
325
346
|
const changes = [];
|
|
326
347
|
for (const [id, earlier] of Object.entries(before || {})) {
|
|
327
348
|
const later = after?.[id];
|
|
328
349
|
const name = earlier.name || id;
|
|
329
|
-
const
|
|
330
|
-
|
|
350
|
+
const editedLayer = (layer) => edited.has(layer.id) || Boolean(layer.origin && edited.has(layer.origin));
|
|
351
|
+
let source = edited.has(id) ? 'self' : earlier.origin && edited.has(earlier.origin) ? 'origin' : null;
|
|
331
352
|
const a = earlier.rect;
|
|
332
353
|
const b = later?.rect;
|
|
333
354
|
const comparable = a && b && (a.source || null) === (b.source || null);
|
|
@@ -335,7 +356,15 @@ export function unchangedViolations(before, after, edited = new Set(), tolerance
|
|
|
335
356
|
if (!later) text = `${name} was removed`;
|
|
336
357
|
else if (comparable && ['width', 'height'].some((key) => Math.abs(a[key] - b[key]) > tolerance)) {
|
|
337
358
|
text = `${name} changed size (${a.width}×${a.height} → ${b.width}×${b.height})`;
|
|
359
|
+
} else if (earlier.children && later.children && childOrder(earlier) !== childOrder(later)) {
|
|
360
|
+
text = `${name} now holds its layers in this order: ${later.children.slice(0, 12).map((child) => child.name || child.id).join(', ')}`;
|
|
361
|
+
if (!source && [...earlier.children, ...later.children].some(editedLayer)) source = 'children';
|
|
338
362
|
} else if (earlier.props !== later.props) text = `${name} has different settings`;
|
|
363
|
+
const note = source === 'self'
|
|
364
|
+
? ' (you edited it)'
|
|
365
|
+
: source === 'origin'
|
|
366
|
+
? ` (it follows your edit of ${earlier.origin})`
|
|
367
|
+
: source === 'children' ? ' (you edited layers inside it)' : '';
|
|
339
368
|
if (text) changes.push({ text: `${text}${note}`, edited: Boolean(source) });
|
|
340
369
|
if (changes.length >= 20) break;
|
|
341
370
|
}
|
package/src/framerTools.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
normalizeChecks,
|
|
10
10
|
normalizeRequirements,
|
|
11
11
|
summarizeRequirements,
|
|
12
|
+
renameRequirementNodes,
|
|
12
13
|
unchangedViolations,
|
|
13
14
|
verifyChecks,
|
|
14
15
|
} from './framerRequirementContract.js';
|
|
@@ -879,7 +880,12 @@ for (const id of ids) {
|
|
|
879
880
|
const rect = (await __dexterLaidOut(id)) ? await __dexterRect(id) : null;
|
|
880
881
|
let props = '';
|
|
881
882
|
try { const { children, ...rest } = JSON.parse(JSON.stringify(node)); props = fingerprint(JSON.stringify(rest)); } catch {}
|
|
882
|
-
|
|
883
|
+
const children = (Array.isArray(node.children) ? node.children : []).slice(0, 60).map((child) => {
|
|
884
|
+
const childId = child && typeof child === 'object' ? child.id : child;
|
|
885
|
+
const childNode = childId ? tree.get(childId) : null;
|
|
886
|
+
return { id: childId, name: (childNode && childNode.name) || null, origin: (childNode && childNode.$originalId) || null };
|
|
887
|
+
}).filter((child) => child.id);
|
|
888
|
+
out[id] = { name: node.name || null, origin: node.$originalId || null, rect: rect ? { x: rect.x, y: rect.y, width: rect.width, height: rect.height } : null, props, children };
|
|
883
889
|
}
|
|
884
890
|
return out;`;
|
|
885
891
|
}
|
|
@@ -1341,6 +1347,7 @@ export function createFramerAgentToolRuntime({
|
|
|
1341
1347
|
await snapshotUnchanged();
|
|
1342
1348
|
const result = await client.exec(wrapProgram(program));
|
|
1343
1349
|
const applied = applyReceipt(result, screenshotNodeIds);
|
|
1350
|
+
requirements = renameRequirementNodes(requirements, applied.receipt.renamedIds);
|
|
1344
1351
|
const programError = programErrorOf(applied.output, wrappedLineOffset(program));
|
|
1345
1352
|
// Layers created by this program only get their real ids once it
|
|
1346
1353
|
// finishes, so screenshots are taken in a second call.
|
|
@@ -1648,7 +1655,7 @@ export function createFramerAgentToolRuntime({
|
|
|
1648
1655
|
}
|
|
1649
1656
|
const unchecked = uncheckedRequirements();
|
|
1650
1657
|
if (unchecked.length) {
|
|
1651
|
-
facts.push(`These requirements from your plan have not been checked since your last change, so they are not confirmed either way: ${unchecked.map((item) => item.text).join('; ')}.`);
|
|
1658
|
+
facts.push(`These requirements from your plan have not been checked since your last change, so they are not confirmed either way: ${unchecked.map((item) => `${item.id}: ${item.text}`).join('; ')}.`);
|
|
1652
1659
|
}
|
|
1653
1660
|
if (unchangedChanges.length) {
|
|
1654
1661
|
facts.push(`These layers you planned to leave as they were now read differently: ${unchangedChanges.map((change) => change.text).join('; ')}.`);
|