demobite 1.0.3 → 1.0.4
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/scripts/record.mjs +22 -9
- package/skill/SKILL.md +1 -1
package/package.json
CHANGED
package/scripts/record.mjs
CHANGED
|
@@ -87,6 +87,16 @@ const DEFAULT_CLICK_AFTER = 2000;
|
|
|
87
87
|
const CLOSING_BEAT_MS = 2200;
|
|
88
88
|
// LAW (hold your line, founder 2026-09-02): a beat is never shorter than its
|
|
89
89
|
// own narration. Same estimate manifest.mjs uses (words / 2.6 s + 0.4 s).
|
|
90
|
+
// Storyboard durations (`dwell`, `after`, settle `ms`, scroll `ms`) are
|
|
91
|
+
// MILLISECONDS; an author who writes 3.4 means seconds. Anything under 60 is
|
|
92
|
+
// read as seconds (a 59 ms dwell is never intended).
|
|
93
|
+
// The video track starts up to ~2.5 s AFTER the wall clock (startup lead,
|
|
94
|
+
// measured per take by calibrate.mjs). trim.mjs cuts by record_from, so the
|
|
95
|
+
// opening beat loses that lead in the published file. The FIRST narrated beat
|
|
96
|
+
// after the stamp carries this slack so its line always has its full beat.
|
|
97
|
+
const STARTUP_LEAD_SLACK_S = 2.5;
|
|
98
|
+
let openingBeatPending = false;
|
|
99
|
+
const ms = (v, fallback) => (v == null ? fallback : v < 60 ? Math.round(v * 1000) : v);
|
|
90
100
|
const estimateSpeech = (text) => String(text).trim().split(/\s+/).filter(Boolean).length / 2.6 + 0.4;
|
|
91
101
|
|
|
92
102
|
const narrationOf = (s) => {
|
|
@@ -603,13 +613,14 @@ try {
|
|
|
603
613
|
}
|
|
604
614
|
await page.waitForTimeout(600);
|
|
605
615
|
manifest.record_from = t();
|
|
616
|
+
openingBeatPending = true;
|
|
606
617
|
}
|
|
607
618
|
await page.evaluate(([a, b]) => window.__recSetCursor?.(a, b), [cx, cy]);
|
|
608
619
|
} else if (step.action === "settle") {
|
|
609
620
|
// A settle can still carry the camera: `focus` names what to look at.
|
|
610
|
-
const
|
|
621
|
+
const settleMs = ms(step.ms, DEFAULT_SETTLE_MS);
|
|
611
622
|
const shotStart = t();
|
|
612
|
-
await page.waitForTimeout(
|
|
623
|
+
await page.waitForTimeout(settleMs);
|
|
613
624
|
if (step.focus) {
|
|
614
625
|
const box = await visibleBox(step.focus, step.minY ?? 0, 4000);
|
|
615
626
|
pushShot(box, shotStart, t(), step.label, { n: rec.n });
|
|
@@ -617,7 +628,7 @@ try {
|
|
|
617
628
|
} else if (step.action === "scroll") {
|
|
618
629
|
// scrollTo distances are ALSO zoomed-space under CSS zoom (measured:
|
|
619
630
|
// scrollTo(0,600) moves 300 design px) — storyboards speak design px.
|
|
620
|
-
await smoothScroll(step.dy * SUPERSAMPLE, step.ms
|
|
631
|
+
await smoothScroll(step.dy * SUPERSAMPLE, ms(step.ms, 1400), step.within ?? null);
|
|
621
632
|
} else if (step.action === "click" || step.action === "hover") {
|
|
622
633
|
const { box, el } = await visibleTarget(step.selector, step.minY ?? 0);
|
|
623
634
|
if (!box) throw new Error("no visible target for " + step.selector);
|
|
@@ -665,7 +676,7 @@ try {
|
|
|
665
676
|
};
|
|
666
677
|
|
|
667
678
|
if (step.action === "hover") {
|
|
668
|
-
await page.waitForTimeout(step.dwell
|
|
679
|
+
await page.waitForTimeout(ms(step.dwell, DEFAULT_HOVER_DWELL));
|
|
669
680
|
// LAW (camera choreography, measured off the reference 2026-08-09):
|
|
670
681
|
// the camera must NOT travel in lockstep with the cursor. When it
|
|
671
682
|
// does, the cursor sits pinned near frame center while the page
|
|
@@ -675,7 +686,7 @@ try {
|
|
|
675
686
|
// while the cursor sweeps across it, then the camera reframes.
|
|
676
687
|
pushShot(step.focus ? await visibleBox(step.focus, 0, 3000) : box, Math.max(shotStart, arrivalT - 0.3), t(), step.label, { n: rec.n, glide: { t_start: Math.round(shotStart * 100) / 100, t_end: Math.round(arrivalT * 100) / 100 } });
|
|
677
688
|
} else {
|
|
678
|
-
await page.waitForTimeout(step.dwell
|
|
689
|
+
await page.waitForTimeout(ms(step.dwell, DEFAULT_CLICK_DWELL));
|
|
679
690
|
await page.evaluate(([a, b]) => window.__recPulse?.(a, b), [x, y]);
|
|
680
691
|
await page.waitForTimeout(220);
|
|
681
692
|
rec.click_at = t();
|
|
@@ -699,7 +710,7 @@ try {
|
|
|
699
710
|
// pointer. The recorder does not guess: it ASKS the page whether the
|
|
700
711
|
// element it just clicked is still connected, visible, and under the
|
|
701
712
|
// point. The verdict rides the click event as press:'full'|'down'.
|
|
702
|
-
const afterMs = step.after
|
|
713
|
+
const afterMs = ms(step.after, DEFAULT_CLICK_AFTER);
|
|
703
714
|
const early = Math.min(450, afterMs);
|
|
704
715
|
await page.waitForTimeout(Math.min(200, early));
|
|
705
716
|
// TIMING LAW (parity forensics 2026-08-09): this check must not eat
|
|
@@ -796,7 +807,7 @@ try {
|
|
|
796
807
|
w: Math.round(box.width / SUPERSAMPLE), h: Math.round(box.height / SUPERSAMPLE),
|
|
797
808
|
},
|
|
798
809
|
};
|
|
799
|
-
await page.waitForTimeout(step.dwell
|
|
810
|
+
await page.waitForTimeout(ms(step.dwell, DEFAULT_CLICK_DWELL));
|
|
800
811
|
await page.evaluate(([a, b]) => window.__recPulse?.(a, b), [x, y]);
|
|
801
812
|
await page.waitForTimeout(220);
|
|
802
813
|
rec.click_at = t();
|
|
@@ -823,7 +834,7 @@ try {
|
|
|
823
834
|
pushShot(box, Math.max(shotStart, arrivalT - 0.3), t() + 0.3, step.label ?? "type", { n: rec.n, glide: { t_start: Math.round(shotStart * 100) / 100, t_end: Math.round(arrivalT * 100) / 100 } });
|
|
824
835
|
currentCursor = await cursorUnderPoint();
|
|
825
836
|
pushMouse("move", cx, cy);
|
|
826
|
-
await page.waitForTimeout(step.after
|
|
837
|
+
await page.waitForTimeout(ms(step.after, DEFAULT_CLICK_AFTER));
|
|
827
838
|
}
|
|
828
839
|
if (rec.narration && step.action !== "goto" && step.action !== "expect") {
|
|
829
840
|
// Hold the shot until the line would have finished, plus a breath — the
|
|
@@ -831,7 +842,9 @@ try {
|
|
|
831
842
|
// beat (founder 2026-09-02: the last track ran past the end).
|
|
832
843
|
// Measured from ARRIVAL (where manifest.mjs anchors the line), not from
|
|
833
844
|
// the cursor's departure — the glide is not speaking time.
|
|
834
|
-
const
|
|
845
|
+
const narrText = typeof rec.narration === "string" ? rec.narration : rec.narration?.text ?? "";
|
|
846
|
+
let need = estimateSpeech(narrText) + 0.5;
|
|
847
|
+
if (openingBeatPending) { need += STARTUP_LEAD_SLACK_S; openingBeatPending = false; }
|
|
835
848
|
const elapsed = t() - (rec.arrival ?? rec.t_start);
|
|
836
849
|
if (elapsed < need) await page.waitForTimeout(Math.round((need - elapsed) * 1000));
|
|
837
850
|
}
|
package/skill/SKILL.md
CHANGED
|
@@ -130,7 +130,7 @@ Storyboard schema:
|
|
|
130
130
|
}
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
Step fields: `action` is one of `goto | settle | scroll | click | hover`. `goto` needs `url`. `settle` takes `ms` and an optional `focus` selector. `scroll` needs `dy` and takes `ms`. `click`/`hover` need `selector` and take `minY` (minimum Y for the visible instance pick), `dwell`, `after`, `waitLoad`. Every step takes `label` and `narration`.
|
|
133
|
+
Step fields: `action` is one of `goto | settle | scroll | click | hover | type | expect`. **Durations (`dwell`, `after`, settle `ms`, scroll `ms`) are milliseconds; a value under 60 is read as seconds** (write `"dwell": 3400` or `"dwell": 3.4`, never `"dwell": 3` meaning 3 ms). `goto` needs `url`. `settle` takes `ms` and an optional `focus` selector. `scroll` needs `dy` and takes `ms`. `click`/`hover` need `selector` and take `minY` (minimum Y for the visible instance pick), `dwell`, `after`, `waitLoad`. Every step takes `label` and `narration`.
|
|
134
134
|
|
|
135
135
|
Two fields carry the whole advantage of this lane, so fill them in:
|
|
136
136
|
|