@vosjs/cli 0.65.4 → 0.66.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/README.md CHANGED
@@ -263,7 +263,7 @@ Verified the flow in agent-browser already? `vos actions from-agent-browser step
263
263
 
264
264
  **Tilt.** `doc.tilt`: source-anchored, non-overlapping spans where the card leans to a pose and returns to rest: `[{ "id": "u0", "in": 4, "out": 8, "rx": 6, "ry": -9, "source": "manual" }]`. `rx`/`ry` are degrees (±45 hard limit; ±5..18 reads well); `+rx` brings the top edge toward the camera, `+ry` the left edge, so a lean toward a right-side focus is a negative `ry`. Rest is flat. `doc.tiltStyle` (`off`, `subtle`, `medium`, `strong`) is the intensity the planner derives automatic spans from.
265
265
 
266
- **Text overlays.** `doc.overlays`: screen-space clips above the card, outside the zoom, output-anchored. `{ "id": "t0", "kind": "text", "start": 1, "duration": 3, "text": "Ship it", "preset": "title", "transform": { "x": 0.5, "y": 0.82, "scale": 1, "rotation": 0 }, "enter": "rise", "exit": "fade" }`. Presets `title`, `caption`, `label`, overridable with `size` (12..200 design px) and `color`; `\n` breaks lines; a caption (a layer with no referent) is a lower third at `y` ≈ 0.82. Enter and exit: `rise`, `fade`, `none`. Fonts load at render start, fail-open to system stacks.
266
+ **Text overlays.** `doc.overlays`: screen-space clips above the card, outside the zoom, output-anchored. `{ "id": "t0", "kind": "text", "start": 1, "duration": 3, "text": "Ship it", "preset": "title", "transform": { "x": 0.5, "y": 0.82, "scale": 1, "rotation": 0 }, "enter": "rise", "exit": "fade" }`. Presets `title`, `caption`, `label`, overridable with `size` (12..200 design px) and `color`; `\n` breaks lines; a caption (a layer with no referent) is a lower third at `y` ≈ 0.82. Enter and exit: `rise`, `fade`, `none`. Fonts load at render start, fail-open to system stacks. **Styled words are runs.** `text` is a string, or a list of runs when parts of it are set differently: `"text": [{ "text": "Ship " }, { "text": "faster", "weight": 700, "underline": true }]`. A run is a piece of text plus the fields in which it departs from the layer: `weight` (700 is bold; snapped to what the family hosts), `italic`, `color`, `underline`, `strike`, `highlight` (a colour behind it). There is no markup in the words (an asterisk is an asterisk), a style may start and end anywhere, a run may hold `\n`, and a per-word or per-character entrance keeps every unit in its own style. `vos validate` names a run field it does not know and prints the runs to write for a layer still using the retired `emphasis`.
267
267
 
268
268
  **Callouts in the grammar.** `vos callout <take> note --step copy --kicker "index.css" --title "Every token you tuned, as CSS variables." --mark ring` writes a callout in the house grammar (three shapes a viewer learns once: `note`, a kicker, a title and a line; `tag`, one label on the accent; `code`, the payload block) from the product's REGISTER: `BRAND.md` beside the take (`bgA` the ground, `accent`, `fontBody`; a kit whose accent is RESERVED, a recorder's red that marks time and nothing else, names a `callout` role and the notes speak in that hue instead) or `--ground #hex --accent #hex --font "…"`. The card inverts the app's value in the product's hue (a light app gets a dark card, a dark app a light one), keeps the accent for the kicker, sets its title at 1.35× the app's body AS SEEN ON SCREEN at the layer's start (the camera's level is read from the document; `--body-px <n>` instead sets the body size on the DELIVERED frame and is never rescaled, which is the flag a rich capture needs, since a 2560-wide take on a padded frame sits near scale 0.6 and lands every default on the grammar's floor; the verb prints the three sizes and the scale it measured), lifts on a real shadow and a hairline, rises in and fades out, opens a beat after the step's press lands and closes before the next scroll or navigation, and is pinned to the step (`--side`, `--mark`, `--leader`, `--color` refine the pin; `--color` paints the mark and the leader, never the kicker, which is the accent). `--at <s> --seconds <n>` places one without a step; `--print` prints the clip instead of writing it. `vos validate <take> --picture` renders the footage under every html layer at its start and reports the ΔE between the card's ground and what it covers: under 8 is a problem (the card reads as one more panel), under 16 a warning. An unpinned html or media layer whose window holds a step with an element gets the pin named in a warning.
269
269
 
@@ -214,7 +214,12 @@ import {
214
214
  pinCandidates,
215
215
  pinRectOnScreen,
216
216
  pinReferent,
217
- parseEmphasis,
217
+ marksEmphasis,
218
+ migrateTextClip,
219
+ plainText,
220
+ resolveOverlayStyle,
221
+ snapRunWeight,
222
+ RUN_STYLE_KEYS,
218
223
  ratedSegments,
219
224
  recommendedExportResolution,
220
225
  resolvePins,
@@ -244,7 +249,7 @@ function wordsOf(text) {
244
249
  return text.toLowerCase().split(/[^a-z0-9]+/).filter((x) => x.length >= 3 && !LABEL_STOPWORDS.has(x));
245
250
  }
246
251
  function layerWords(o) {
247
- const src = typeof o.html === "string" ? o.html.replace(/<[^>]*>/g, " ") : typeof o.text === "string" ? o.text : "";
252
+ const src = typeof o.html === "string" ? o.html.replace(/<[^>]*>/g, " ") : typeof o.text === "string" || Array.isArray(o.text) ? plainText(o.text) : "";
248
253
  return new Set(wordsOf(src));
249
254
  }
250
255
  function selectorLabelWords(selector) {
@@ -1063,28 +1068,75 @@ function lintDoc(docIn) {
1063
1068
  }
1064
1069
  checkPin(o, name, docIn, recording, problems, warnings);
1065
1070
  if (o.kind === "text") {
1066
- if (typeof o.text !== "string")
1067
- problems.push(`${name}.text must be a string`);
1071
+ if (Array.isArray(o.text)) {
1072
+ const HINTS = {
1073
+ bold: "weight: 700",
1074
+ fontWeight: "weight",
1075
+ strikethrough: "strike: true",
1076
+ background: "highlight",
1077
+ colour: "color"
1078
+ };
1079
+ o.text.forEach((run2, ri) => {
1080
+ const at2 = `${name}.text[${ri}]`;
1081
+ if (!run2 || typeof run2 !== "object" || Array.isArray(run2)) {
1082
+ problems.push(
1083
+ `${at2} must be a run: { text, weight?, italic?, color?, underline?, strike?, highlight? }`
1084
+ );
1085
+ return;
1086
+ }
1087
+ const r = run2;
1088
+ if (typeof r.text !== "string")
1089
+ problems.push(`${at2}.text must be a string`);
1090
+ else if (!r.text)
1091
+ warnings.push(`${at2} has no text and paints nothing; drop it`);
1092
+ for (const k of Object.keys(r)) {
1093
+ if (k === "text" || RUN_STYLE_KEYS.includes(k))
1094
+ continue;
1095
+ problems.push(
1096
+ `${at2}.${k} is not a run field (${RUN_STYLE_KEYS.join(", ")})${HINTS[k] ? `: write ${HINTS[k]}` : ""}`
1097
+ );
1098
+ }
1099
+ if (r.weight !== void 0 && (!isNum(r.weight) || r.weight < 100 || r.weight > 900))
1100
+ problems.push(`${at2}.weight must be 100..900`);
1101
+ for (const k of ["italic", "underline", "strike"]) {
1102
+ if (r[k] !== void 0 && typeof r[k] !== "boolean")
1103
+ problems.push(`${at2}.${k} must be true or false`);
1104
+ }
1105
+ for (const k of ["color", "highlight"]) {
1106
+ if (r[k] !== void 0 && typeof r[k] !== "string")
1107
+ problems.push(`${at2}.${k} must be a CSS colour`);
1108
+ }
1109
+ if (isNum(r.weight) && r.weight >= 100 && r.weight <= 900) {
1110
+ const layer = o;
1111
+ const own = resolveOverlayStyle({ ...layer, text: "" }).weight;
1112
+ if (r.weight !== own && snapRunWeight(layer, r.weight) === own)
1113
+ warnings.push(
1114
+ `${at2}.weight ${r.weight} paints no differently: the layer's family hosts no weight nearer to it than the layer's own ${own}`
1115
+ );
1116
+ }
1117
+ });
1118
+ } else if (typeof o.text !== "string") {
1119
+ problems.push(
1120
+ `${name}.text must be a string, or a list of runs ([{ text, weight?, italic?, color?, underline?, strike?, highlight? }])`
1121
+ );
1122
+ }
1068
1123
  if (typeof o.preset === "string" && !PRESETS.includes(o.preset)) {
1069
1124
  warnings.push(
1070
1125
  `${name}.preset "${String(o.preset)}" is not a known preset (${PRESETS.join("|")}) \u2014 falls back to "title"`
1071
1126
  );
1072
1127
  }
1073
- if (o.emphasis !== void 0) {
1074
- const em = o.emphasis;
1075
- if (!em || typeof em !== "object" || Array.isArray(em)) {
1076
- problems.push(
1077
- `${name}.emphasis must be { weight?, color? } ({} turns *markers* on)`
1078
- );
1079
- } else {
1080
- if (em.weight !== void 0 && (!isNum(em.weight) || em.weight < 100 || em.weight > 900))
1081
- problems.push(`${name}.emphasis.weight must be 100..900`);
1082
- if (em.color !== void 0 && typeof em.color !== "string")
1083
- problems.push(`${name}.emphasis.color must be a CSS colour`);
1084
- }
1085
- } else if (typeof o.text === "string" && parseEmphasis(o.text) !== null) {
1128
+ if (o.emphasis !== void 0 && typeof o.text === "string") {
1129
+ const runs = migrateTextClip(o).text;
1130
+ warnings.push(
1131
+ `${name}.emphasis is retired (styled words are runs now, with no markers in the text). It is still read, but write it as: "text": ${JSON.stringify(runs)} and drop "emphasis"`
1132
+ );
1133
+ } else if (typeof o.text === "string" && marksEmphasis(o.text)) {
1134
+ const runs = migrateTextClip({
1135
+ ...o,
1136
+ emphasis: {}
1137
+ }).text;
1086
1138
  warnings.push(
1087
- `${name}.text has *marked* words but no emphasis, so the asterisks show as typed; add emphasis: {} to set them bold (\\* keeps one literal)`
1139
+ `${name}.text has words between asterisks, which show as typed. To set them bold write runs: "text": ${JSON.stringify(runs)}`
1088
1140
  );
1089
1141
  }
1090
1142
  if (o.size !== void 0 && (!isNum(o.size) || o.size < 12 || o.size > 200)) {
@@ -4333,8 +4385,9 @@ import {
4333
4385
  computeCardLayout,
4334
4386
  docStillTime as docStillTime2,
4335
4387
  migrateHostedDoc,
4388
+ overlayPlainText,
4336
4389
  overlayRect,
4337
- resolveOverlayStyle
4390
+ resolveOverlayStyle as resolveOverlayStyle2
4338
4391
  } from "@vosjs/studio-core";
4339
4392
  var POSTER_CLASSES = [
4340
4393
  "landscape",
@@ -4453,7 +4506,7 @@ function posterTextBoxes(doc, px, time) {
4453
4506
  if (clip.kind !== "text") continue;
4454
4507
  if (time < clip.start || time > clip.start + clip.duration) continue;
4455
4508
  const rect = overlayRect(clip, estimateWidth, W, H);
4456
- const style = resolveOverlayStyle(clip);
4509
+ const style = resolveOverlayStyle2(clip);
4457
4510
  const r3 = (v) => Math.round(v * 1e3) / 1e3;
4458
4511
  out.push({
4459
4512
  x: r3((rect.cx - rect.w / 2) / W),
@@ -4461,7 +4514,7 @@ function posterTextBoxes(doc, px, time) {
4461
4514
  w: r3(rect.w / W),
4462
4515
  h: r3(rect.h / H),
4463
4516
  role: clip.preset === "title" ? "headline" : "body",
4464
- label: clip.text.split("\n")[0].slice(0, 40),
4517
+ label: overlayPlainText(clip).split("\n")[0].slice(0, 40),
4465
4518
  .../^#[0-9a-f]{6}$/i.test(style.color) ? { color: style.color } : {}
4466
4519
  });
4467
4520
  }
@@ -15093,4 +15146,4 @@ export {
15093
15146
  verbHelp,
15094
15147
  run
15095
15148
  };
15096
- //# sourceMappingURL=chunk-IDPTF5PP.js.map
15149
+ //# sourceMappingURL=chunk-SFU4U3GM.js.map