@vosjs/cli 0.79.0 → 0.81.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 +92 -32
- package/dist/{assetManifest-U25FJER6.js → assetManifest-27NKSS7N.js} +2 -2
- package/dist/{chunk-4ZUHEXUX.js → chunk-352AC3V6.js} +235 -27
- package/dist/chunk-352AC3V6.js.map +1 -0
- package/dist/{chunk-Z6TLHRFQ.js → chunk-B373NVJE.js} +6 -2
- package/dist/chunk-B373NVJE.js.map +1 -0
- package/dist/{chunk-TWAP4BKR.js → chunk-S3RI2SHH.js} +2866 -715
- package/dist/chunk-S3RI2SHH.js.map +1 -0
- package/dist/cli.js +13 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +29 -29
- package/dist/index.js +3 -3
- package/dist/{run-UKWKAGDK.js → run-MEDD2Q74.js} +8 -4
- package/dist/{shotList-EE3S5LVF.js → shotList-PL7XP6AC.js} +35 -3
- package/dist/shotList-PL7XP6AC.js.map +1 -0
- package/package.json +5 -5
- package/schema/actions.schema.json +256 -49
- package/schema/doc.schema.json +36 -5
- package/skills/VERSION +1 -1
- package/skills/launch-kit/SKILL.md +6 -2
- package/skills/product-video/SKILL.md +27 -0
- package/skills/product-video/references/taste.md +7 -3
- package/skills/vos-cut/SKILL.md +17 -2
- package/dist/chunk-4ZUHEXUX.js.map +0 -1
- package/dist/chunk-TWAP4BKR.js.map +0 -1
- package/dist/chunk-Z6TLHRFQ.js.map +0 -1
- package/dist/shotList-EE3S5LVF.js.map +0 -1
- /package/dist/{assetManifest-U25FJER6.js.map → assetManifest-27NKSS7N.js.map} +0 -0
- /package/dist/{run-UKWKAGDK.js.map → run-MEDD2Q74.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plugin/shotList.ts"],"sourcesContent":["/**\n * The shot list: an `actions.json` said in words, for a person who will\n * record it by hand. The last rung of the session ladder is \"the human\n * records, the agent cuts\", and an agent that falls to it should hand over\n * more than an apology: the flow it worked out, as numbered beats with the\n * pace it wanted, so its knowledge survives the hop to a human. The take\n * comes back through the ordinary handoff and the agent cuts it.\n *\n * Pure: an ActionsFile in, lines out. Selectors are said as what they name\n * where the script says (an id, a caption, a text= locator), and as the\n * selector itself where it does not: a person reading `#filter-late` can\n * find it; a person reading nothing cannot.\n */\nimport type { ActionStep, ActionsFile } from './actions'\nimport { askedMs } from './pace'\nimport { pressSequence, splitChord } from '@vosjs/render-core/record'\n\n/** A key or a chord as a person reads it: `Mod` is ⌘ on a Mac, Ctrl elsewhere. */\nexport function sayChord(chord: string): string {\n const { mods, key } = splitChord(chord)\n const word = (k: string) =>\n k === 'Mod' || k === 'ControlOrMeta'\n ? '⌘/Ctrl'\n : k === 'Control'\n ? 'Ctrl'\n : k === 'Meta'\n ? '⌘'\n : k === 'Alt'\n ? 'Alt/Option'\n : k === ' '\n ? 'Space'\n : k\n return [...mods, key].map(word).join('+')\n}\n\n/** A viewport point as a person finds it: where it is, and the script's numbers. */\nfunction sayPoint(p: { x?: number; y?: number }): string {\n const x = p.x ?? 0\n const y = p.y ?? 0\n return `the spot at ${Math.round(x)}, ${Math.round(y)} (${y < 360 ? 'upper' : 'lower'} ${x < 640 ? 'left' : 'right'})`\n}\n\n/** A list in words: `a`, `a then b`, `a, b then c`. */\nconst thenList = (xs: string[]): string =>\n xs.length <= 1\n ? (xs[0] ?? '')\n : `${xs.slice(0, -1).join(', ')} then ${xs[xs.length - 1]}`\n\nexport interface ShotBeat {\n n: number\n /** what to do, in words */\n line: string\n /** how long to hold after, in seconds, when the script asks for a hold */\n holdS?: number\n}\n\nexport interface ShotList {\n url?: string\n viewport?: { width: number; height: number }\n beats: ShotBeat[]\n /** the take's asked length, from the script's own pacing */\n aboutS: number\n setup: number\n /** The script's teardown steps: what the person undoes once they stop. */\n teardown?: number\n /**\n * What each other person does (the script's `peers`), as beats tied to\n * the take's own: a second pair of hands, in their own browser, on the\n * same page.\n */\n peers?: { id: string; lines: string[] }[]\n}\n\n/** A selector as a person reads it: the text it names, or the selector itself. */\nexport function sayTarget(selector: string): string {\n const text = /^text=(['\"]?)(.+?)\\1$/.exec(selector)\n if (text) return `\"${text[2]}\"`\n const has = /:has-text\\((['\"])(.+?)\\1\\)/.exec(selector)\n if (has) return `\"${has[2]}\"`\n const role = /getByRole\\(['\"](\\w+)['\"],\\s*\\{\\s*name:\\s*(['\"])(.+?)\\2/.exec(\n selector,\n )\n if (role) return `the ${role[1]} \"${role[3]}\"`\n const label = /\\[aria-label=(['\"])(.+?)\\1\\]/.exec(selector)\n if (label) return `\"${label[2]}\"`\n const ph = /\\[placeholder=(['\"])(.+?)\\1\\]/.exec(selector)\n if (ph) return `the \"${ph[2]}\" field`\n return `\\`${selector}\\``\n}\n\nconst s1 = (ms: number) => `${Math.round(ms / 100) / 10}`.replace(/\\.0$/, '')\n\n/** A step's id, said as a name when it reads as one (`late`, `new-order`, `openBilling`). */\nexport function sayId(id: string | undefined): string | null {\n if (!id || /^\\d+$/.test(id) || id.length < 3) return null\n const words = id\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .replace(/[-_]+/g, ' ')\n .toLowerCase()\n .trim()\n return words.length >= 3 ? words : null\n}\n\n/** What a step points at, for a person: the words the selector names, else the id's, else the selector. */\nfunction sayStepTarget(step: { selector: string; id?: string }): string {\n const bySelector = sayTarget(step.selector)\n if (!bySelector.startsWith('`')) return bySelector\n const byId = sayId(step.id)\n return byId ? `${byId} (${bySelector})` : bySelector\n}\n\n/** A page condition, for a person: what they should see before going on. */\nfunction sayCondition(c: {\n selector?: string\n text?: string\n gone?: string\n enabled?: boolean\n js?: string\n equals?: unknown\n id?: string\n}): string {\n // A state the app keeps in script: a person reads it in the browser's\n // console, so the beat names the expression to run there.\n if (c.js !== undefined)\n return c.equals !== undefined\n ? `the console's \\`${c.js}\\` gives ${JSON.stringify(c.equals)}`\n : `the console's \\`${c.js}\\` is true`\n if (c.text !== undefined) return `\"${c.text}\" shows on the page`\n if (c.gone !== undefined) return `${sayTarget(c.gone)} is gone`\n const target = sayStepTarget({ selector: c.selector ?? '', id: c.id })\n if (c.enabled === true) return `${target} is enabled`\n if (c.enabled === false) return `${target} is disabled`\n return `${target} is showing`\n}\n\nfunction beatOf(step: ActionStep): string | null {\n const line = gestureBeat(step)\n if (!line) return line\n // What the cut's camera does here, said where a person reads it: a step\n // marked as the beat's payoff is zoomed in on, one marked against it is not.\n const camera =\n step.zoom === true\n ? ' (zoomed in on in the cut)'\n : step.zoom === false\n ? ' (the cut stays wide here)'\n : ''\n // A step that spends something real is marked where a person reads it.\n return step.guard\n ? `${line}${camera} (this spends something real: do it only if you mean to)`\n : `${line}${camera}`\n}\n\nfunction gestureBeat(step: ActionStep): string | null {\n const cap = step.caption\n const named = cap ? `${cap}: ` : ''\n switch (step.do) {\n case 'expect':\n return `${named}Check that ${sayCondition(step)}`\n case 'await':\n return `${named}Wait until ${sayCondition({ ...step.until, id: step.id })} (off camera: pause the recording meanwhile)`\n case 'download':\n return step.selector !== undefined\n ? `${named}Download the file ${sayStepTarget({ selector: step.selector, id: step.id })} gives${step.as ? `, as ${step.as}` : ''}`\n : `${named}Download what ${sayTarget(step.src ?? '')} plays${step.as ? `, as ${step.as}` : ''}`\n case 'wait':\n return null\n case 'hover':\n return `${named}Rest the pointer on ${step.selector !== undefined ? sayStepTarget({ selector: step.selector, id: step.id }) : sayPoint(step)}`\n case 'click': {\n const verb =\n step.count === 3\n ? 'Triple-click'\n : step.count === 2\n ? 'Double-click'\n : 'Click'\n const button =\n step.button === 'right'\n ? ' with the right button'\n : step.button === 'middle'\n ? ' with the middle button'\n : ''\n const target =\n step.selector !== undefined\n ? sayStepTarget({ selector: step.selector, id: step.id })\n : sayPoint(step)\n const holding = step.modifiers?.length\n ? `Hold ${step.modifiers.map(sayChord).join('+')} and ${verb.toLowerCase()}`\n : verb\n return `${named}${holding} ${target}${button}`\n }\n case 'type':\n return `${named}${step.replace ? 'Replace what is in' : 'Type'}${step.replace ? ` ${sayStepTarget(step)} with \"${step.text}\"` : ` \"${step.text}\" into ${sayStepTarget(step)}`}${step.at === 'end' ? ', at the end' : step.at === 'start' ? ', at the start' : ''}`\n case 'press':\n return `${named}Press ${thenList(pressSequence(step).map(sayChord))}${step.selector ? ` in ${sayStepTarget(step as { selector: string; id?: string })}` : ''}`\n case 'paste': {\n const what =\n step.text !== undefined\n ? `\"${step.text.length > 60 ? `${step.text.slice(0, 57)}...` : step.text}\"`\n : `the file ${step.file}`\n return `${named}Copy ${what}, then paste it${step.selector ? ` into ${sayStepTarget({ selector: step.selector, id: step.id })}` : ''} (⌘/Ctrl+V)`\n }\n case 'scroll':\n return `${named}Scroll ${step.dy < 0 ? 'up' : 'down'} ${step.dy < 0 ? 'a little' : 'a little'}`\n case 'move':\n return `${named}Move the pointer to the ${step.y < 360 ? 'upper' : 'lower'} ${step.x < 640 ? 'left' : 'right'}`\n case 'drag': {\n const holding = step.modifiers?.length\n ? `Hold ${step.modifiers.map(sayChord).join('+')} and `\n : ''\n const verb = holding ? 'drag' : 'Drag'\n if (step.path !== undefined)\n return `${named}${holding}${holding ? 'draw' : 'Draw'} the stroke ${typeof step.path === 'string' ? `in ${step.path}` : `of ${step.path.length} points`}, in one press`\n return `${named}${holding}${verb} ${step.selector ? sayStepTarget(step as { selector: string; id?: string }) : 'from the start point'} across`\n }\n }\n}\n\nexport function shotList(actions: ActionsFile): ShotList {\n const beats: ShotBeat[] = []\n let pendingHold = 0\n let total = 0\n // Which beat a take step is (or, for a wait, the beat it holds), so a\n // peer's step can say when it happens.\n const beatOfId = new Map<string, number>()\n for (const step of actions.steps) {\n if (step.id)\n beatOfId.set(\n step.id,\n Math.max(1, beats.length + (step.do === 'wait' ? 0 : 1)),\n )\n total += askedMs(step)\n if (step.do === 'wait') {\n // A wait after a beat is that beat's hold; a leading wait is the\n // settle before the first, which a person does without being told.\n if (beats.length) {\n const last = beats[beats.length - 1]\n last.holdS = (last.holdS ?? 0) + step.ms / 1000\n } else pendingHold += step.ms\n continue\n }\n const line = beatOf(step)\n if (!line) continue\n const beat: ShotBeat = { n: beats.length + 1, line }\n if (step.do === 'hover') beat.holdS = askedMs(step) / 1000\n beats.push(beat)\n }\n void pendingHold\n const peers = (actions.peers ?? []).map((peer) => ({\n id: peer.id,\n lines: peer.steps\n .map((step) => {\n const line = step.do === 'wait' ? `Wait ${s1(step.ms)} s` : beatOf(step)\n if (!line) return null\n const when = step.with\n ? `As beat ${beatOfId.get(step.with) ?? '?'} starts: `\n : step.after\n ? `Once beat ${beatOfId.get(step.after) ?? '?'} is done: `\n : ''\n return `${when}${line}`\n })\n .filter((l): l is string => l !== null),\n }))\n return {\n url: actions.url,\n viewport: actions.viewport,\n beats,\n // gestures the recorder adds around each ask: about a second a beat\n aboutS: Math.round((total + beats.length * 1000) / 1000),\n setup: actions.setup?.length ?? 0,\n ...(actions.teardown?.length ? { teardown: actions.teardown.length } : {}),\n ...(peers.length ? { peers } : {}),\n }\n}\n\n/** The list as the person reads it. */\nexport function formatShotList(list: ShotList): string {\n const out: string[] = []\n out.push(\n `Record this with the vosso extension in your own signed-in browser, about ${list.aboutS} seconds${list.viewport ? `, window about ${list.viewport.width}×${list.viewport.height}` : ''}.`,\n )\n if (list.url)\n out.push(`Start on ${list.url}, signed in, with the page settled.`)\n if (list.setup)\n out.push(\n `Before you press record: the script's ${list.setup} setup step${list.setup === 1 ? '' : 's'} (sign in, dismiss banners) are yours to do first; they are not part of the take.`,\n )\n out.push('')\n for (const b of list.beats) {\n const hold =\n b.holdS && b.holdS >= 0.4 ? ` Hold ${s1(b.holdS * 1000)} s.` : ''\n out.push(`${b.n}. ${b.line}.${hold}`)\n }\n for (const peer of list.peers ?? []) {\n out.push('')\n out.push(\n `A second person (${peer.id}) does this at the same time, in their own browser on the same page; their screen is not recorded, the app shows what they do:`,\n )\n peer.lines.forEach((l, i) => out.push(` ${peer.id}${i + 1}. ${l}.`))\n }\n out.push('')\n out.push(\n 'Move at a talking pace, let each screen settle before the next beat, and end on the finished state. Press the icon again to stop; the take opens in the studio and saves to your shelf.',\n )\n if (list.teardown)\n out.push(\n `After you stop: the script's ${list.teardown} teardown step${list.teardown === 1 ? '' : 's'} (remove what the take created) are yours to do; they are not part of the take.`,\n )\n return out.join('\\n')\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vosjs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.81.0",
|
|
4
4
|
"description": "The vos CLI: record the real product from a scripted browser flow, auto-zoom from the cursor track, cut as data in doc.json, render deterministic video, deliver a release's video cuts per destination spec, and sync with vos.so. One binary, every verb, MIT.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -53,12 +53,12 @@
|
|
|
53
53
|
"playwright": "^1.49.0",
|
|
54
54
|
"@vosjs/core": "^0.31.0",
|
|
55
55
|
"@vosjs/editor": "^1.6.0",
|
|
56
|
-
"@vosjs/render-core": "^0.8.0",
|
|
57
|
-
"@vosjs/studio-core": "^0.44.0",
|
|
58
56
|
"@vosjs/elements": "^0.12.1",
|
|
59
57
|
"@vosjs/shared": "^0.7.0",
|
|
60
|
-
"@vosjs/
|
|
61
|
-
"@vosjs/
|
|
58
|
+
"@vosjs/render-core": "^0.10.0",
|
|
59
|
+
"@vosjs/studio-core": "^0.46.0",
|
|
60
|
+
"@vosjs/tween": "^0.8.3",
|
|
61
|
+
"@vosjs/timeline": "^0.5.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@types/node": "^22",
|
|
@@ -33,51 +33,14 @@
|
|
|
33
33
|
"type": "array",
|
|
34
34
|
"description": "Run BEFORE the camera rolls, after the first navigation: a sign-in form, a cookie banner, an onboarding tour. Plain actions with no cursor, no frames, no pace and nothing in meta.steps; then the recorder opens `url` again and the take begins where the setup left it. A selector that never appears fails the take before anything is recorded (exit 2). A `type` step's text may be { \"env\": \"NAME\" }, read at run time and never logged or stored; a literal typed into a password field is refused, because actions.json is committed and pushed.",
|
|
35
35
|
"items": {
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"id": {
|
|
45
|
-
"type": "string"
|
|
46
|
-
},
|
|
47
|
-
"selector": {
|
|
48
|
-
"type": "string"
|
|
49
|
-
},
|
|
50
|
-
"text": {
|
|
51
|
-
"oneOf": [
|
|
52
|
-
{
|
|
53
|
-
"type": "string"
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"type": "object",
|
|
57
|
-
"required": ["env"],
|
|
58
|
-
"additionalProperties": false,
|
|
59
|
-
"properties": {
|
|
60
|
-
"env": {
|
|
61
|
-
"type": "string",
|
|
62
|
-
"description": "the environment variable holding the value"
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
]
|
|
67
|
-
},
|
|
68
|
-
"key": {
|
|
69
|
-
"type": "string",
|
|
70
|
-
"description": "press: a Playwright key name (Enter, Tab, Escape)"
|
|
71
|
-
},
|
|
72
|
-
"url": {
|
|
73
|
-
"type": "string",
|
|
74
|
-
"description": "goto: a page to open, for a sign-in that lives elsewhere"
|
|
75
|
-
},
|
|
76
|
-
"ms": {
|
|
77
|
-
"type": "number",
|
|
78
|
-
"description": "wait: the pause; click: the read after the page settled, held from its last visual change (default 150); type and scroll: the settle after (defaults 150/200)"
|
|
79
|
-
}
|
|
80
|
-
}
|
|
36
|
+
"$ref": "#/$defs/offCameraStep"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"teardown": {
|
|
40
|
+
"type": "array",
|
|
41
|
+
"description": "Run AFTER the take, off camera, with the same verbs and rules as setup: what the take created in the product (an API key, a project, an invite) removed again, so a rehearsal or a re-record leaves the account as it found it. The steps run once the screencast has stopped, on the take's own page and session, after a --dry-run rehearsal too, and also when the take stops early or throws after its first step ran. Nothing of them is captured or written to meta.steps. Bounded at 60 s in all. A step that fails stops the teardown and is reported (the --json done event's `teardown`), and never fails the take or changes its exit code. A { \"env\": \"NAME\" } the shell does not hold refuses the take before it begins.",
|
|
42
|
+
"items": {
|
|
43
|
+
"$ref": "#/$defs/offCameraStep"
|
|
81
44
|
}
|
|
82
45
|
},
|
|
83
46
|
"ready": {
|
|
@@ -190,6 +153,10 @@
|
|
|
190
153
|
"maxLength": 80,
|
|
191
154
|
"description": "a beat's caption: vos deliver lands it as a lower-third at this step's moment on cuts that take words (feed cuts, the demo), never on a loop"
|
|
192
155
|
},
|
|
156
|
+
"zoom": {
|
|
157
|
+
"type": "boolean",
|
|
158
|
+
"description": "the cut's camera on this step: true marks it as its beat's payoff (vos plan --zooms per-beat keeps the zoom on it and only it), false means never zoom here (the planner's proposal on it is rejected); the planner still decides level and focus"
|
|
159
|
+
},
|
|
193
160
|
"read": {
|
|
194
161
|
"type": "string",
|
|
195
162
|
"minLength": 1,
|
|
@@ -280,6 +247,10 @@
|
|
|
280
247
|
"maxLength": 80,
|
|
281
248
|
"description": "a beat's caption: vos deliver lands it as a lower-third at this step's moment on cuts that take words (feed cuts, the demo), never on a loop"
|
|
282
249
|
},
|
|
250
|
+
"zoom": {
|
|
251
|
+
"type": "boolean",
|
|
252
|
+
"description": "the cut's camera on this step: true marks it as its beat's payoff (vos plan --zooms per-beat keeps the zoom on it and only it), false means never zoom here (the planner's proposal on it is rejected); the planner still decides level and focus"
|
|
253
|
+
},
|
|
283
254
|
"read": {
|
|
284
255
|
"type": "string",
|
|
285
256
|
"minLength": 1,
|
|
@@ -357,6 +328,10 @@
|
|
|
357
328
|
"maxLength": 80,
|
|
358
329
|
"description": "a beat's caption: vos deliver lands it as a lower-third at this step's moment on cuts that take words (feed cuts, the demo), never on a loop"
|
|
359
330
|
},
|
|
331
|
+
"zoom": {
|
|
332
|
+
"type": "boolean",
|
|
333
|
+
"description": "the cut's camera on this step: true marks it as its beat's payoff (vos plan --zooms per-beat keeps the zoom on it and only it), false means never zoom here (the planner's proposal on it is rejected); the planner still decides level and focus"
|
|
334
|
+
},
|
|
360
335
|
"read": {
|
|
361
336
|
"type": "string",
|
|
362
337
|
"minLength": 1,
|
|
@@ -448,6 +423,10 @@
|
|
|
448
423
|
"maxLength": 80,
|
|
449
424
|
"description": "a beat's caption: vos deliver lands it as a lower-third at this step's moment on cuts that take words (feed cuts, the demo), never on a loop"
|
|
450
425
|
},
|
|
426
|
+
"zoom": {
|
|
427
|
+
"type": "boolean",
|
|
428
|
+
"description": "the cut's camera on this step: true marks it as its beat's payoff (vos plan --zooms per-beat keeps the zoom on it and only it), false means never zoom here (the planner's proposal on it is rejected); the planner still decides level and focus"
|
|
429
|
+
},
|
|
451
430
|
"read": {
|
|
452
431
|
"type": "string",
|
|
453
432
|
"minLength": 1,
|
|
@@ -617,6 +596,10 @@
|
|
|
617
596
|
"maxLength": 80,
|
|
618
597
|
"description": "a beat's caption: vos deliver lands it as a lower-third at this step's moment on cuts that take words (feed cuts, the demo), never on a loop"
|
|
619
598
|
},
|
|
599
|
+
"zoom": {
|
|
600
|
+
"type": "boolean",
|
|
601
|
+
"description": "the cut's camera on this step: true marks it as its beat's payoff (vos plan --zooms per-beat keeps the zoom on it and only it), false means never zoom here (the planner's proposal on it is rejected); the planner still decides level and focus"
|
|
602
|
+
},
|
|
620
603
|
"read": {
|
|
621
604
|
"type": "string",
|
|
622
605
|
"minLength": 1,
|
|
@@ -639,7 +622,7 @@
|
|
|
639
622
|
"type": "object",
|
|
640
623
|
"required": ["do"],
|
|
641
624
|
"additionalProperties": false,
|
|
642
|
-
"description": "Check the page is as the flow expects, on camera, with no cursor move: exactly one of selector (visible; with enabled, enabled or disabled), text (some visible text contains it)
|
|
625
|
+
"description": "Check the page is as the flow expects, on camera, with no cursor move: exactly one of selector (visible; with enabled, enabled or disabled), text (some visible text contains it), gone (no longer visible) or js (an expression evaluated in the page: holds when its value is truthy, or with equals when it equals that value; for state an app keeps in script, such as a canvas's shapes). A condition that never holds is a skip with its reason (a js check names the value it last got), which --strict turns into exit 2. A js check's last value is kept on meta.steps[].value. A hosted take refuses js.",
|
|
643
626
|
"oneOf": [
|
|
644
627
|
{
|
|
645
628
|
"required": ["selector"]
|
|
@@ -649,6 +632,9 @@
|
|
|
649
632
|
},
|
|
650
633
|
{
|
|
651
634
|
"required": ["gone"]
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
"required": ["js"]
|
|
652
638
|
}
|
|
653
639
|
],
|
|
654
640
|
"properties": {
|
|
@@ -671,6 +657,14 @@
|
|
|
671
657
|
"type": "boolean",
|
|
672
658
|
"description": "with selector: wait for the element to be enabled (true) or disabled (false)"
|
|
673
659
|
},
|
|
660
|
+
"js": {
|
|
661
|
+
"type": "string",
|
|
662
|
+
"minLength": 1,
|
|
663
|
+
"description": "a JavaScript expression evaluated in the page (a promise is awaited): the check holds when its value is truthy, or, with equals, when its value equals that one"
|
|
664
|
+
},
|
|
665
|
+
"equals": {
|
|
666
|
+
"description": "with js: the value the expression must answer, compared as JSON"
|
|
667
|
+
},
|
|
674
668
|
"timeout": {
|
|
675
669
|
"type": "number",
|
|
676
670
|
"exclusiveMinimum": 0,
|
|
@@ -706,8 +700,6 @@
|
|
|
706
700
|
"until": {
|
|
707
701
|
"type": "object",
|
|
708
702
|
"additionalProperties": false,
|
|
709
|
-
"minProperties": 1,
|
|
710
|
-
"maxProperties": 1,
|
|
711
703
|
"properties": {
|
|
712
704
|
"selector": {
|
|
713
705
|
"type": "string",
|
|
@@ -720,8 +712,91 @@
|
|
|
720
712
|
"gone": {
|
|
721
713
|
"type": "string",
|
|
722
714
|
"minLength": 1
|
|
715
|
+
},
|
|
716
|
+
"js": {
|
|
717
|
+
"type": "string",
|
|
718
|
+
"minLength": 1,
|
|
719
|
+
"description": "a JavaScript expression evaluated in the page (a promise is awaited): holds when its value is truthy, or, with equals, when its value equals that one. A hosted take refuses it."
|
|
720
|
+
},
|
|
721
|
+
"equals": {
|
|
722
|
+
"description": "with js: the value the expression must answer, compared as JSON"
|
|
723
723
|
}
|
|
724
|
-
}
|
|
724
|
+
},
|
|
725
|
+
"oneOf": [
|
|
726
|
+
{
|
|
727
|
+
"required": ["selector"],
|
|
728
|
+
"not": {
|
|
729
|
+
"anyOf": [
|
|
730
|
+
{
|
|
731
|
+
"required": ["text"]
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
"required": ["gone"]
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
"required": ["js"]
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
"required": ["equals"]
|
|
741
|
+
}
|
|
742
|
+
]
|
|
743
|
+
}
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
"required": ["text"],
|
|
747
|
+
"not": {
|
|
748
|
+
"anyOf": [
|
|
749
|
+
{
|
|
750
|
+
"required": ["selector"]
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
"required": ["gone"]
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
"required": ["js"]
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
"required": ["equals"]
|
|
760
|
+
}
|
|
761
|
+
]
|
|
762
|
+
}
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
"required": ["gone"],
|
|
766
|
+
"not": {
|
|
767
|
+
"anyOf": [
|
|
768
|
+
{
|
|
769
|
+
"required": ["selector"]
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
"required": ["text"]
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
"required": ["js"]
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
"required": ["equals"]
|
|
779
|
+
}
|
|
780
|
+
]
|
|
781
|
+
}
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
"required": ["js"],
|
|
785
|
+
"not": {
|
|
786
|
+
"anyOf": [
|
|
787
|
+
{
|
|
788
|
+
"required": ["selector"]
|
|
789
|
+
},
|
|
790
|
+
{
|
|
791
|
+
"required": ["text"]
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
"required": ["gone"]
|
|
795
|
+
}
|
|
796
|
+
]
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
]
|
|
725
800
|
},
|
|
726
801
|
"timeout": {
|
|
727
802
|
"type": "number",
|
|
@@ -862,6 +937,10 @@
|
|
|
862
937
|
"maxLength": 80,
|
|
863
938
|
"description": "a beat's caption: vos deliver lands it as a lower-third at this step's moment on cuts that take words (feed cuts, the demo), never on a loop"
|
|
864
939
|
},
|
|
940
|
+
"zoom": {
|
|
941
|
+
"type": "boolean",
|
|
942
|
+
"description": "the cut's camera on this step: true marks it as its beat's payoff (vos plan --zooms per-beat keeps the zoom on it and only it), false means never zoom here (the planner's proposal on it is rejected); the planner still decides level and focus"
|
|
943
|
+
},
|
|
865
944
|
"read": {
|
|
866
945
|
"type": "string",
|
|
867
946
|
"minLength": 1,
|
|
@@ -871,6 +950,86 @@
|
|
|
871
950
|
}
|
|
872
951
|
]
|
|
873
952
|
}
|
|
953
|
+
},
|
|
954
|
+
"peers": {
|
|
955
|
+
"type": "array",
|
|
956
|
+
"maxItems": 4,
|
|
957
|
+
"description": "Other people on the page, OFF CAMERA: each a browser context of its own (its own storage state, so its own user, or signed out) on the same url, driven by its own steps. Only the take's page is filmed and only the take's steps make the cursor track; what a peer does reaches the footage through the app's own sync (its presence, its remote cursors, the shape it drew). A peer step starts with a take step (`with`, as it starts) or after one (`after`, as it ends), named by id, else right after the peer's previous step (the first, as the camera rolls). The take never waits on a peer: wait for what the peer's work shows with the take's own expect or await. A hosted take refuses peers.",
|
|
958
|
+
"items": {
|
|
959
|
+
"type": "object",
|
|
960
|
+
"required": ["id", "steps"],
|
|
961
|
+
"additionalProperties": false,
|
|
962
|
+
"properties": {
|
|
963
|
+
"id": {
|
|
964
|
+
"type": "string",
|
|
965
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$",
|
|
966
|
+
"description": "a short word lines and meta.peers name the peer by"
|
|
967
|
+
},
|
|
968
|
+
"url": {
|
|
969
|
+
"type": "string",
|
|
970
|
+
"description": "the page the peer opens (http or https); absent, the take's url"
|
|
971
|
+
},
|
|
972
|
+
"viewport": {
|
|
973
|
+
"type": "object",
|
|
974
|
+
"required": ["width", "height"],
|
|
975
|
+
"properties": {
|
|
976
|
+
"width": {
|
|
977
|
+
"type": "number"
|
|
978
|
+
},
|
|
979
|
+
"height": {
|
|
980
|
+
"type": "number"
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
},
|
|
984
|
+
"storageState": {
|
|
985
|
+
"type": "string",
|
|
986
|
+
"minLength": 1,
|
|
987
|
+
"description": "a Playwright storage state file (the second person's sign-in), by a path from the actions file's directory or an absolute one. Opened by the browser only: never copied into the take, never uploaded; a pushed script keeps its file name only. Absent, the peer is signed out. A peer that lands on a sign-in refuses the take."
|
|
988
|
+
},
|
|
989
|
+
"steps": {
|
|
990
|
+
"type": "array",
|
|
991
|
+
"minItems": 1,
|
|
992
|
+
"maxItems": 200,
|
|
993
|
+
"description": "The peer's steps: the take's own step shapes (see steps), for wait, move, hover, click, type, press, scroll, drag (path inline only) and expect, without guard, read, caption or settle, plus when each starts. Every step is bounded like a take step; a miss is a skip listed with the take's (peer set), which --strict fails on.",
|
|
994
|
+
"items": {
|
|
995
|
+
"type": "object",
|
|
996
|
+
"required": ["do"],
|
|
997
|
+
"properties": {
|
|
998
|
+
"do": {
|
|
999
|
+
"enum": [
|
|
1000
|
+
"wait",
|
|
1001
|
+
"move",
|
|
1002
|
+
"hover",
|
|
1003
|
+
"click",
|
|
1004
|
+
"type",
|
|
1005
|
+
"press",
|
|
1006
|
+
"scroll",
|
|
1007
|
+
"drag",
|
|
1008
|
+
"expect"
|
|
1009
|
+
]
|
|
1010
|
+
},
|
|
1011
|
+
"with": {
|
|
1012
|
+
"type": "string",
|
|
1013
|
+
"minLength": 1,
|
|
1014
|
+
"description": "start as the take's step with this id starts: the two run together"
|
|
1015
|
+
},
|
|
1016
|
+
"after": {
|
|
1017
|
+
"type": "string",
|
|
1018
|
+
"minLength": 1,
|
|
1019
|
+
"description": "start once the take's step with this id has ended"
|
|
1020
|
+
},
|
|
1021
|
+
"id": {
|
|
1022
|
+
"type": "string",
|
|
1023
|
+
"minLength": 1
|
|
1024
|
+
}
|
|
1025
|
+
},
|
|
1026
|
+
"not": {
|
|
1027
|
+
"required": ["with", "after"]
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
874
1033
|
}
|
|
875
1034
|
},
|
|
876
1035
|
"$defs": {
|
|
@@ -911,6 +1070,54 @@
|
|
|
911
1070
|
}
|
|
912
1071
|
}
|
|
913
1072
|
]
|
|
1073
|
+
},
|
|
1074
|
+
"offCameraStep": {
|
|
1075
|
+
"type": "object",
|
|
1076
|
+
"required": ["do"],
|
|
1077
|
+
"additionalProperties": false,
|
|
1078
|
+
"properties": {
|
|
1079
|
+
"do": {
|
|
1080
|
+
"type": "string",
|
|
1081
|
+
"enum": ["wait", "click", "type", "press", "goto"]
|
|
1082
|
+
},
|
|
1083
|
+
"id": {
|
|
1084
|
+
"type": "string"
|
|
1085
|
+
},
|
|
1086
|
+
"selector": {
|
|
1087
|
+
"type": "string"
|
|
1088
|
+
},
|
|
1089
|
+
"text": {
|
|
1090
|
+
"oneOf": [
|
|
1091
|
+
{
|
|
1092
|
+
"type": "string"
|
|
1093
|
+
},
|
|
1094
|
+
{
|
|
1095
|
+
"type": "object",
|
|
1096
|
+
"required": ["env"],
|
|
1097
|
+
"additionalProperties": false,
|
|
1098
|
+
"properties": {
|
|
1099
|
+
"env": {
|
|
1100
|
+
"type": "string",
|
|
1101
|
+
"description": "the environment variable holding the value"
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
]
|
|
1106
|
+
},
|
|
1107
|
+
"key": {
|
|
1108
|
+
"type": "string",
|
|
1109
|
+
"description": "press: a Playwright key name (Enter, Tab, Escape)"
|
|
1110
|
+
},
|
|
1111
|
+
"url": {
|
|
1112
|
+
"type": "string",
|
|
1113
|
+
"description": "goto: a page to open, for a sign-in that lives elsewhere"
|
|
1114
|
+
},
|
|
1115
|
+
"ms": {
|
|
1116
|
+
"type": "number",
|
|
1117
|
+
"description": "wait: the pause; click: the read after the page settled, held from its last visual change (default 150); type and scroll: the settle after (defaults 150/200)"
|
|
1118
|
+
}
|
|
1119
|
+
},
|
|
1120
|
+
"description": "One setup or teardown step: a plain action, with no cursor and no frames."
|
|
914
1121
|
}
|
|
915
1122
|
}
|
|
916
1123
|
}
|
package/schema/doc.schema.json
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"segments": {
|
|
29
29
|
"type": "array",
|
|
30
|
-
"description": "Kept SOURCE-time footage spans; the output timeline is their concatenation. Trim dead time by shrinking spans. Empty array = untrimmed. Never carry `rate` here (only lowered/derived segments do).",
|
|
30
|
+
"description": "Kept SOURCE-time footage spans; the output timeline is their concatenation, in list order. Trim dead time by shrinking spans. Empty array = untrimmed. Never carry `rate` here (only lowered/derived segments do). Unlike every other span list, segments may overlap: a segment may play seconds an earlier one already played (a cold open showing the end state, then the whole recording from its start), and every source-anchored span over those seconds (zoom, tilt, camMotion, speed, freeze, the cursor's clicks) plays in each occurrence. A layer anchored to a step starts in the pass where the step plays the longest. A segment with `camera: rest` keeps the camera at rest through its pass.",
|
|
31
31
|
"items": {
|
|
32
32
|
"type": "object",
|
|
33
33
|
"required": ["in", "out"],
|
|
@@ -55,6 +55,10 @@
|
|
|
55
55
|
"media": {
|
|
56
56
|
"type": "string",
|
|
57
57
|
"description": "the media this belongs to: a `media[].id`; absent = the primary, `source`"
|
|
58
|
+
},
|
|
59
|
+
"camera": {
|
|
60
|
+
"enum": ["rest"],
|
|
61
|
+
"description": "`rest`: the camera rests through this clip's pass. No zoom, tilt, cam move or follow recenter applies there, even over seconds another pass zooms on (a cold open of the settled whole before the recording plays with its zooms); the cursor, the click effects, the speed spans and the freezes still play. A span that runs into or out of the clip ramps at its edge as at any span's. Absent = the camera plays here as everywhere"
|
|
58
62
|
}
|
|
59
63
|
},
|
|
60
64
|
"additionalProperties": true
|
|
@@ -330,7 +334,33 @@
|
|
|
330
334
|
},
|
|
331
335
|
"cursor": {
|
|
332
336
|
"type": "object",
|
|
333
|
-
"description": "Cursor rendering (visible, hideWhenIdle, smoothing, size, style, click effects). See CursorStyle in @vosjs/studio-core. `style: \"arrow\"` (the default for new takes) draws an OS-style pointer with its tip on the recorded point; `\"default\"` (every earlier take) draws the white dot. `visible: false` hides the drawn pointer only \u2014 the track still drives cursor-follow zoom, and click effects keep drawing (silence those with clickFx.style: \"none\"). `hideWhenIdle` (default true) fades the dot out after ~1s parked and back in when it moves; scrolling is not movement, and it is always back at full opacity on a click.",
|
|
337
|
+
"description": "Cursor rendering (visible, hideWhenIdle, smoothing, size, style, click effects). See CursorStyle in @vosjs/studio-core. `style: \"arrow\"` (the default for new takes) draws an OS-style pointer with its tip on the recorded point; `\"default\"` (every earlier take) draws the white dot. `visible: false` hides the drawn pointer only \u2014 the track still drives cursor-follow zoom, and click effects keep drawing (silence those with clickFx.style: \"none\"). `hideWhenIdle` (default true) fades the dot out after ~1s parked and back in when it moves; scrolling is not movement, and it is always back at full opacity on a click. `off` hides the drawn pointer over time ranges.",
|
|
338
|
+
"properties": {
|
|
339
|
+
"off": {
|
|
340
|
+
"type": "array",
|
|
341
|
+
"description": "Time ranges in which the drawn pointer is hidden, in OUTPUT seconds (the timeline the viewer sees, like overlays, so a range stays put through trims and speed changes). `{ in, out?, from? }`: an absent `out` hides it to the end of the film. The pointer fades out over 0.15 s before `in` and back in over 0.15 s after `out`, so the range itself is fully hidden; overlapping ranges read as their union. The track still drives cursor-follow zoom and click effects, as with `visible: false`. `from` marks a range something else placed (`endcard`: the end card's, written by `vos plan` and replaced or removed with the end card); a range without one is yours and a re-plan never touches it.",
|
|
342
|
+
"items": {
|
|
343
|
+
"type": "object",
|
|
344
|
+
"required": ["in"],
|
|
345
|
+
"properties": {
|
|
346
|
+
"in": {
|
|
347
|
+
"type": "number",
|
|
348
|
+
"minimum": 0,
|
|
349
|
+
"description": "OUTPUT seconds the pointer is gone from."
|
|
350
|
+
},
|
|
351
|
+
"out": {
|
|
352
|
+
"type": "number",
|
|
353
|
+
"description": "OUTPUT seconds it returns at, past `in`. Absent = to the end of the film."
|
|
354
|
+
},
|
|
355
|
+
"from": {
|
|
356
|
+
"type": "string",
|
|
357
|
+
"description": "What placed it (e.g. `endcard`). Absent = a person's own."
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
"additionalProperties": false
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
},
|
|
334
364
|
"additionalProperties": true
|
|
335
365
|
},
|
|
336
366
|
"cam": {
|
|
@@ -848,7 +878,8 @@
|
|
|
848
878
|
"description": "Screen-space layers drawn ABOVE the card, outside the zoom transform. OUTPUT-anchored (start is final-cut seconds \u2014 trims/speed never retime a title). A layer about something on the page names it with `pin` and is placed beside it, following it as the camera moves; a layer with no referent is a caption and sits in the margin. Optional; absent = none.",
|
|
849
879
|
"items": {
|
|
850
880
|
"type": "object",
|
|
851
|
-
"required": ["id", "kind", "
|
|
881
|
+
"required": ["id", "kind", "duration", "transform"],
|
|
882
|
+
"anyOf": [{ "required": ["start"] }, { "required": ["anchor"] }],
|
|
852
883
|
"additionalProperties": true,
|
|
853
884
|
"properties": {
|
|
854
885
|
"id": {
|
|
@@ -957,13 +988,13 @@
|
|
|
957
988
|
"start": {
|
|
958
989
|
"type": "number",
|
|
959
990
|
"minimum": 0,
|
|
960
|
-
"description": "OUTPUT seconds.
|
|
991
|
+
"description": "OUTPUT seconds. Required unless the layer carries an `anchor`; with one it is the fallback, used when the anchor cannot resolve, and may be left out (a layer whose anchor resolves nowhere and that has no start is refused by validate)"
|
|
961
992
|
},
|
|
962
993
|
"anchor": {
|
|
963
994
|
"type": "object",
|
|
964
995
|
"required": ["step"],
|
|
965
996
|
"additionalProperties": false,
|
|
966
|
-
"description": "When the layer starts, as a recorder step: the lowering maps the step's moment (meta.steps, SOURCE seconds of its media) through the segments, speed spans and freezes exactly as a source-anchored span is mapped, and adds `offset`. A trim, a speed change or a re-record moves the layer with its step; `start` is the fallback when the step is missing, skipped or cut out (validate says which), and `duration` stays the layer's own. A text layer's {elapsed} fills with the anchored step's off-camera wait (the meta.skips entry naming the step, else its offCameraMs; a step that waited for nothing reads the nearest earlier wait on its media) as a rounded duration, like \"12 minutes\". Not the re-record `anchor` on spans, which the lowering never reads",
|
|
997
|
+
"description": "When the layer starts, as a recorder step: the lowering maps the step's moment (meta.steps, SOURCE seconds of its media) through the segments, speed spans and freezes exactly as a source-anchored span is mapped, and adds `offset`. A trim, a speed change or a re-record moves the layer with its step; `start` is the fallback when the step is missing, skipped or cut out (validate says which) and may be left out, and `duration` stays the layer's own. On a cut that replays the step's seconds the layer starts in the pass where the step plays the longest (the first on a tie). A text layer's {elapsed} fills with the anchored step's off-camera wait (the meta.skips entry naming the step, else its offCameraMs; a step that waited for nothing reads the nearest earlier wait on its media) as a rounded duration, like \"12 minutes\". Not the re-record `anchor` on spans, which the lowering never reads",
|
|
967
998
|
"properties": {
|
|
968
999
|
"step": {
|
|
969
1000
|
"type": ["string", "integer"],
|
package/skills/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.26.0
|
|
@@ -91,7 +91,10 @@ labels typed, real-looking data, the feature mid-story.
|
|
|
91
91
|
**Write the story into `actions.json`.** Give steps an `id` (the re-render
|
|
92
92
|
loop and the template anchors address them by it) and a `caption` where a
|
|
93
93
|
beat deserves one (two to eight words; `plan` proposes it as a lower-third
|
|
94
|
-
at that step's moment on the cuts that take words)
|
|
94
|
+
at that step's moment on the cuts that take words), on the step whose
|
|
95
|
+
action it describes, and mark each beat's payoff `"zoom": true` so
|
|
96
|
+
`--zooms per-beat` keeps that zoom and no other (the `product-video`
|
|
97
|
+
skill, step 2, has the rule and the state check). After a click that
|
|
95
98
|
navigates, put a `wait` for the load, so the cut never dwells on a spinner.
|
|
96
99
|
|
|
97
100
|
- **Fresh recording**: the `product-video` skill's loop (explore →
|
|
@@ -231,7 +234,8 @@ by data. Never spread before the seed is signed off.
|
|
|
231
234
|
a frame near the start, not on what shipped.
|
|
232
235
|
- A cold open (`firstlast`): the first frame is the marketing page's
|
|
233
236
|
headline with the product nowhere; trim to the story or let the entrance
|
|
234
|
-
carry it.
|
|
237
|
+
carry it. A cold open of the RESULT is a repeated segment at camera rest
|
|
238
|
+
(the `vos-cut` skill, "Assemble").
|
|
235
239
|
- The loading plane on screen: a click that navigates needs the `wait`
|
|
236
240
|
after it.
|
|
237
241
|
- White words on a paper ground: the end card takes the brand's ink; a
|
|
@@ -134,6 +134,27 @@ Per-channel dimensions and byte budgets: `references/destinations.md`.
|
|
|
134
134
|
from hover-triggered menus (taste.md, flow rules). Check with
|
|
135
135
|
`vos validate actions.json`.
|
|
136
136
|
|
|
137
|
+
**Mark where the camera goes** (`@vosjs/cli` 0.80 and later). A step
|
|
138
|
+
with a `caption` starts a beat; put the caption on the step whose action
|
|
139
|
+
it describes. Mark each beat's PAYOFF `"zoom": true`: the moment the
|
|
140
|
+
viewer should see up close (the drag whose result is the point, the
|
|
141
|
+
click that opens the thing, the typing that names it). Mark a gesture
|
|
142
|
+
that must stay wide `"zoom": false`. Give every marked step an `id`.
|
|
143
|
+
Then `vos plan take --zooms per-beat` keeps exactly the marked zooms
|
|
144
|
+
and rejects the rest for good; the planner still picks level and
|
|
145
|
+
focus. Why marks: no rule inferred from a script matched a person's
|
|
146
|
+
zoom choices (the best got 3 of 5); marks reproduced all 5 and nothing
|
|
147
|
+
else.
|
|
148
|
+
|
|
149
|
+
**Check the app's own state** after any step that sets up the scene (a
|
|
150
|
+
document loaded, a count of items): `{ "do": "expect", "js":
|
|
151
|
+
"window.app.count()", "equals": 14 }`. A take can pass `--strict` with
|
|
152
|
+
the product in the wrong state (loaded twice, a duplicate on screen),
|
|
153
|
+
because the DOM says nothing about what a canvas app holds; with
|
|
154
|
+
`equals` a miss says what the app had. Read state only, never change
|
|
155
|
+
it. A collaboration product wants a second person on the page: `peers`
|
|
156
|
+
in `actions.json` (https://vos.so/llms-full.txt).
|
|
157
|
+
|
|
137
158
|
Then REHEARSE it (`@vosjs/cli` 0.39 and later):
|
|
138
159
|
`vos record --actions actions.json --out take --dry-run`. Every step runs
|
|
139
160
|
against the real page, in order, because a later selector usually exists
|
|
@@ -180,6 +201,8 @@ Per-channel dimensions and byte budgets: `references/destinations.md`.
|
|
|
180
201
|
- export: `{"resolution": "720p|1080p|2k|4k", "fps": 30}` — never above
|
|
181
202
|
the footage (validate warns).
|
|
182
203
|
Then `vos validate take --json` — lints must pass.
|
|
204
|
+
Several takes in one film, a cold open, an end card: the `vos-cut`
|
|
205
|
+
skill, "Assemble".
|
|
183
206
|
|
|
184
207
|
5. **Look before you render** (the taste.md quality loop):
|
|
185
208
|
- `vos frames take --at-zooms --times 0,25%,50%,75%,100% --json` → judge
|
|
@@ -268,3 +291,7 @@ when the ask is a release, not one video.
|
|
|
268
291
|
`EXPOSED` list, and `mask` what is left.
|
|
269
292
|
- A `mask` with `as: "text"` over product copy or a number: that is no
|
|
270
293
|
longer a recording of the product.
|
|
294
|
+
- Picking zooms by hand from a long proposal list (a canvas take proposes
|
|
295
|
+
one per gesture): mark the payoff steps and plan `--zooms per-beat`.
|
|
296
|
+
- Trusting `--strict` for the scene: it proves the steps ran, not that the
|
|
297
|
+
app holds what the beat shows. Check with `expect` + `js` + `equals`.
|