@aglyn/plugins-ai 1.0.0-beta.258 → 1.0.0-beta.260

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.
@@ -18,6 +18,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { linealRelationshipPermits } from "@aglyn/aglyn/app-utils/lineal-order";
19
19
  import { SCREEN_SEO_TEXT_FIELDS, SCREEN_SEO_TEXT_GUIDANCE } from "@aglyn/aglyn/app-utils/screen-seo-fields";
20
20
  import { CANVAS_ROOT_ELEMENT_ID } from "@aglyn/aglyn/foundation/constants/canvas";
21
+ import { ANIMATION_EASINGS, ANIMATION_MAX_DELAY_MS, ANIMATION_MAX_DURATION_MS, ANIMATION_MAX_STAGGER_STEP_MS, ANIMATION_NONE, ANIMATION_PRESETS, ANIMATION_TRIGGERS, NODE_ANIMATION_DELAY_PROP, NODE_ANIMATION_DURATION_PROP, NODE_ANIMATION_EASE_PROP, NODE_ANIMATION_PROP, NODE_ANIMATION_REPEAT_PROP, NODE_ANIMATION_STAGGER_PROP, NODE_ANIMATION_STAGGER_STEP_PROP, NODE_ANIMATION_TRIGGER_PROP } from "@aglyn/aglyn/app-utils/element-animation";
21
22
  import { NODE_MAX_INTERACTIONS, nodeInteractionSelector } from "@aglyn/aglyn/app-utils/node-interactions";
22
23
  import { ACTION_MAX_STEPS, CLIENT_INTERACTION_STEP_LABELS, validateInteraction } from "@aglyn/aglyn/app-utils/site-interactions";
23
24
  import { ASSIST_EDIT_ACTION_ID, ASSIST_EDIT_CONTEXT_MAX_NODES, ASSIST_EDIT_CONTEXT_MAX_PROPS, ASSIST_EDIT_INTERACTION_EVENTS, ASSIST_EDIT_INTERACTION_STEPS, ASSIST_EDIT_MAX_DROPPED_REASONS, ASSIST_EDIT_OP_KINDS, ASSIST_EDIT_OUTLINE_TEXT_CHARS, ASSIST_EDIT_SELECTED_TEXT_CHARS, ASSIST_EDIT_TOOL_NAME, summarizeAssistEditOps } from "../model/assist-edit.js";
@@ -220,6 +221,66 @@ const strictObject = (properties)=>({
220
221
  strict: true
221
222
  };
222
223
  }
224
+ /* ------------------------------------------------------------------ *
225
+ * An element's entrance motion (AGL-3838)
226
+ * ------------------------------------------------------------------ */ /**
227
+ * The settings an element's Animation section writes, and the values each
228
+ * takes: the vocabulary's own lists, and a whole number of milliseconds up to
229
+ * its cap. `none` takes an animation off, as the section's None does.
230
+ */ const MOTION_SETTINGS = {
231
+ [NODE_ANIMATION_PROP]: ANIMATION_PRESETS,
232
+ [NODE_ANIMATION_TRIGGER_PROP]: ANIMATION_TRIGGERS,
233
+ [NODE_ANIMATION_EASE_PROP]: ANIMATION_EASINGS,
234
+ [NODE_ANIMATION_DURATION_PROP]: ANIMATION_MAX_DURATION_MS,
235
+ [NODE_ANIMATION_DELAY_PROP]: ANIMATION_MAX_DELAY_MS,
236
+ [NODE_ANIMATION_STAGGER_STEP_PROP]: ANIMATION_MAX_STAGGER_STEP_MS,
237
+ [NODE_ANIMATION_STAGGER_PROP]: 'switch',
238
+ [NODE_ANIMATION_REPEAT_PROP]: 'switch'
239
+ };
240
+ /**
241
+ * The line of the edit protocol that offers motion: a pure function of the
242
+ * vocabulary, so it sits in the cached prefix with the rest of the block.
243
+ */ export const ASSIST_EDIT_MOTION_LINE = `- Animation: updateProps also sets ${NODE_ANIMATION_PROP} (${ANIMATION_PRESETS.filter((preset)=>preset !== ANIMATION_NONE).join(', ')}; ${ANIMATION_NONE} removes it), ` + `${NODE_ANIMATION_TRIGGER_PROP} (${ANIMATION_TRIGGERS.join(', ')}), ${NODE_ANIMATION_EASE_PROP} (${ANIMATION_EASINGS.join(', ')}), ` + `${NODE_ANIMATION_DURATION_PROP} and ${NODE_ANIMATION_DELAY_PROP} (ms ≤${ANIMATION_MAX_DURATION_MS}), ` + `${NODE_ANIMATION_STAGGER_PROP} ("true": its children in turn), ${NODE_ANIMATION_STAGGER_STEP_PROP} (ms ≤${ANIMATION_MAX_STAGGER_STEP_MS}).`;
244
+ /**
245
+ * An `updateProps` patch split into its motion settings, held to the
246
+ * vocabulary, and everything else, which the palette validator judges.
247
+ *
248
+ * Every element takes the motion settings — the renderer reads them off any
249
+ * node — so they are in no palette entry and the palette validator would drop
250
+ * them all. They are held here instead, and a value outside the vocabulary or
251
+ * past a cap is REFUSED with its reason, never coerced: a proposal that says
252
+ * "slide in over ten seconds" and lands as three would be an edit the card
253
+ * misdescribes.
254
+ */ export function assistEditMotionPatch(props) {
255
+ const motion = {};
256
+ const rest = {};
257
+ const refused = [];
258
+ for (const [name, raw] of Object.entries(props)){
259
+ const allowed = MOTION_SETTINGS[name];
260
+ if (allowed === undefined) {
261
+ rest[name] = raw;
262
+ continue;
263
+ }
264
+ const text = String(raw != null ? raw : '').trim();
265
+ if (allowed === 'switch') {
266
+ if (text === 'true' || text === 'false') motion[name] = text === 'true';
267
+ else refused.push(`${name} is "true" or "false"`);
268
+ } else if (typeof allowed === 'number') {
269
+ const ms = /^\d{1,5}$/.test(text) ? Number(text) : NaN;
270
+ if (ms <= allowed) motion[name] = ms;
271
+ else refused.push(`${name} is a whole number of milliseconds up to ${allowed}`);
272
+ } else if (allowed.includes(text)) {
273
+ motion[name] = text;
274
+ } else {
275
+ refused.push(`${name} is one of ${allowed.join(', ')}`);
276
+ }
277
+ }
278
+ return {
279
+ motion,
280
+ rest,
281
+ refused
282
+ };
283
+ }
223
284
  /* ------------------------------------------------------------------ *
224
285
  * The cached block
225
286
  * ------------------------------------------------------------------ */ const DOCUMENT_NOUNS = {
@@ -244,6 +305,7 @@ const strictObject = (properties)=>({
244
305
  ASSIST_MUSIC_EDIT_LINE,
245
306
  'Operations — every field is required; fill the ones an operation does not use with "", [] or -1:',
246
307
  '- updateProps: nodeId and props — only the settings to change, as name/value strings ("true" or "false" for a switch). Every other setting on the element is kept.',
308
+ ASSIST_EDIT_MOTION_LINE,
247
309
  `- updateSx: nodeId and sx as key/value/breakpoint. A color is a theme token — ${AI_SX_TOKENS.palette.join(', ')} — never a hex, rgb or color name. A unitless number is theme spacing units; a size carries its unit ("320px", "50%"). breakpoint is "" or one of ${AI_SX_TOKENS.breakpoints.join(', ')}.`,
248
310
  '- insertSubtree: parentId, index (-1 appends) and nodes — the new elements, nodes[0] their root, each listing its children by the ids you gave them in this operation. Build only from the elements below, and keep it small.',
249
311
  '- move: nodeId, parentId and index — the position the element takes among the parent’s children once moved (-1 appends).',
@@ -1096,38 +1158,54 @@ const DELAYED_STEPS = new Set([
1096
1158
  return;
1097
1159
  }
1098
1160
  case 'updateProps':
1161
+ {
1162
+ // Motion settings are held to the animation vocabulary, the rest to the palette (AGL-3838).
1163
+ const { motion, rest, refused } = assistEditMotionPatch(pending.props);
1164
+ if (refused.length) {
1165
+ violations.push(`${label}: ${refused.join('; ')}`);
1166
+ return;
1167
+ }
1168
+ if (!Object.keys(rest).length) {
1169
+ ops.push(_extends({}, pending, {
1170
+ props: motion
1171
+ }));
1172
+ return;
1173
+ }
1174
+ const result = validateAiNodePatch(pending.componentId, {
1175
+ props: rest
1176
+ });
1177
+ if (result.ok === false) {
1178
+ violations.push(`${label}: ${result.error}`);
1179
+ return;
1180
+ }
1181
+ if (!Object.keys(result.props).length) {
1182
+ violations.push(`${label}: ${result.repairs.length ? result.repairs.slice(0, 2).join('; ') : 'none of the settings named are ones this element takes'}`);
1183
+ return;
1184
+ }
1185
+ ops.push(_extends({}, pending, {
1186
+ props: _extends({}, result.props, motion)
1187
+ }));
1188
+ return;
1189
+ }
1099
1190
  case 'updateSx':
1100
1191
  {
1101
- const result = validateAiNodePatch(pending.componentId, pending.op === 'updateProps' ? {
1102
- props: pending.props
1103
- } : {
1192
+ const result = validateAiNodePatch(pending.componentId, {
1104
1193
  sx: pending.sx
1105
1194
  });
1106
1195
  if (result.ok === false) {
1107
1196
  violations.push(`${label}: ${result.error}`);
1108
1197
  return;
1109
1198
  }
1110
- // The first repairs say why a patch came out empty — a prop the
1199
+ // The first repairs say why a patch came out empty — a style the
1111
1200
  // element does not take reads differently from one that carried
1112
1201
  // markup, and the reader of `dropped` needs to tell them apart.
1113
- const why = (fallback)=>result.repairs.length ? result.repairs.slice(0, 2).join('; ') : fallback;
1114
- if (pending.op === 'updateProps') {
1115
- if (!Object.keys(result.props).length) {
1116
- violations.push(`${label}: ${why('none of the settings named are ones this element takes')}`);
1117
- return;
1118
- }
1119
- ops.push(_extends({}, pending, {
1120
- props: result.props
1121
- }));
1122
- } else {
1123
- if (!result.sx) {
1124
- violations.push(`${label}: ${why('none of the styles named are allowed')}`);
1125
- return;
1126
- }
1127
- ops.push(_extends({}, pending, {
1128
- sx: result.sx
1129
- }));
1202
+ if (!result.sx) {
1203
+ violations.push(`${label}: ${result.repairs.length ? result.repairs.slice(0, 2).join('; ') : 'none of the styles named are allowed'}`);
1204
+ return;
1130
1205
  }
1206
+ ops.push(_extends({}, pending, {
1207
+ sx: result.sx
1208
+ }));
1131
1209
  return;
1132
1210
  }
1133
1211
  case 'move':