waygraph 0.15.25 → 0.15.26
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/dist/highlights/css.d.ts +14 -0
- package/dist/highlights/css.js +58 -0
- package/dist/highlights/demo-pace.d.ts +56 -0
- package/dist/highlights/demo-pace.js +184 -0
- package/dist/highlights/device.d.ts +29 -0
- package/dist/highlights/device.js +171 -0
- package/dist/highlights/merge.d.ts +8 -0
- package/dist/highlights/merge.js +173 -0
- package/dist/highlights/run-phase.d.ts +43 -0
- package/dist/highlights/run-phase.js +211 -0
- package/dist/highlights/stub-ctx.d.ts +10 -0
- package/dist/highlights/stub-ctx.js +281 -0
- package/dist/highlights/style.d.ts +27 -0
- package/dist/highlights/style.js +84 -0
- package/dist/highlights/todo-dock.d.ts +52 -0
- package/dist/highlights/todo-dock.js +248 -0
- package/dist/highlights/types.d.ts +536 -0
- package/dist/highlights/types.js +1 -0
- package/dist/highlights.d.ts +9 -734
- package/dist/highlights.js +10 -1416
- package/examples/saucedemo/.waygraph-auto/008f4e6e.log +0 -0
- package/examples/saucedemo/.waygraph-auto/22fefa13.log +0 -0
- package/package.json +1 -1
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { normalizeHighlightSize, normalizeHighlightTone, normalizeHighlightWeight } from "./style.js";
|
|
2
|
+
export function isStubPhase(v) {
|
|
3
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
4
|
+
}
|
|
5
|
+
function pickDurationFields(base, patch) {
|
|
6
|
+
const out = {};
|
|
7
|
+
const duration = patch?.duration !== undefined ? patch.duration : base?.duration;
|
|
8
|
+
const fastMode = patch?.fastMode !== undefined ? patch.fastMode : base?.fastMode;
|
|
9
|
+
if (duration !== undefined)
|
|
10
|
+
out.duration = duration;
|
|
11
|
+
if (fastMode !== undefined)
|
|
12
|
+
out.fastMode = fastMode;
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
function pickStyleFields(base, patch) {
|
|
16
|
+
const out = {};
|
|
17
|
+
const tone = patch?.tone !== undefined ? patch.tone : base?.tone;
|
|
18
|
+
const size = patch?.size !== undefined ? patch.size : base?.size;
|
|
19
|
+
const weight = patch?.weight !== undefined ? patch.weight : base?.weight;
|
|
20
|
+
if (tone !== undefined)
|
|
21
|
+
out.tone = normalizeHighlightTone(tone);
|
|
22
|
+
if (size !== undefined)
|
|
23
|
+
out.size = normalizeHighlightSize(size);
|
|
24
|
+
if (weight !== undefined)
|
|
25
|
+
out.weight = normalizeHighlightWeight(weight);
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
function pickZoom(base, patch) {
|
|
29
|
+
const zoom = patch?.zoom !== undefined ? patch.zoom : base?.zoom;
|
|
30
|
+
if (zoom !== undefined && Number.isFinite(zoom) && zoom > 0)
|
|
31
|
+
return { zoom };
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
function pickFxFields(base, patch) {
|
|
35
|
+
const out = {};
|
|
36
|
+
const focus = patch?.focus !== undefined ? patch.focus : base?.focus;
|
|
37
|
+
const color = patch?.color !== undefined ? patch.color : base?.color;
|
|
38
|
+
const cursor = patch?.cursor !== undefined ? patch.cursor : base?.cursor;
|
|
39
|
+
const todo = patch?.todo !== undefined ? patch.todo : base?.todo;
|
|
40
|
+
const gesture = patch?.gesture !== undefined ? patch.gesture : base?.gesture;
|
|
41
|
+
if (focus !== undefined)
|
|
42
|
+
out.focus = !!focus;
|
|
43
|
+
if (color !== undefined && String(color).trim())
|
|
44
|
+
out.color = String(color).trim();
|
|
45
|
+
if (cursor !== undefined)
|
|
46
|
+
out.cursor = !!cursor;
|
|
47
|
+
if (todo !== undefined && String(todo).trim())
|
|
48
|
+
out.todo = String(todo).trim();
|
|
49
|
+
if (gesture === "tap" || gesture === "hold" || gesture === "click" || gesture === "swipe") {
|
|
50
|
+
out.gesture = gesture;
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
function mergeSlot(base, patch) {
|
|
55
|
+
if (!base && !patch)
|
|
56
|
+
return null;
|
|
57
|
+
const dwell = pickDurationFields(base, patch);
|
|
58
|
+
const style = pickStyleFields(base, patch);
|
|
59
|
+
const zoom = pickZoom(base, patch);
|
|
60
|
+
const fx = pickFxFields(base, patch);
|
|
61
|
+
if (!base && patch) {
|
|
62
|
+
if (!patch.selector)
|
|
63
|
+
return null;
|
|
64
|
+
return {
|
|
65
|
+
selector: patch.selector,
|
|
66
|
+
label: patch.label,
|
|
67
|
+
...(patch.detail ? { detail: patch.detail } : {}),
|
|
68
|
+
...(patch.tag ? { tag: patch.tag } : {}),
|
|
69
|
+
...style,
|
|
70
|
+
...dwell,
|
|
71
|
+
...zoom,
|
|
72
|
+
...fx,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (base && !patch) {
|
|
76
|
+
return {
|
|
77
|
+
...base,
|
|
78
|
+
...(base.tone ? { tone: normalizeHighlightTone(base.tone) } : {}),
|
|
79
|
+
...(base.size ? { size: normalizeHighlightSize(base.size) } : {}),
|
|
80
|
+
...(base.weight ? { weight: normalizeHighlightWeight(base.weight) } : {}),
|
|
81
|
+
...pickZoom(base, undefined),
|
|
82
|
+
...pickFxFields(base, undefined),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
selector: patch.selector ?? base.selector,
|
|
87
|
+
label: patch.label,
|
|
88
|
+
...(patch.detail !== undefined
|
|
89
|
+
? { detail: patch.detail }
|
|
90
|
+
: base.detail
|
|
91
|
+
? { detail: base.detail }
|
|
92
|
+
: {}),
|
|
93
|
+
...(patch.tag !== undefined ? { tag: patch.tag } : base.tag ? { tag: base.tag } : {}),
|
|
94
|
+
...style,
|
|
95
|
+
...dwell,
|
|
96
|
+
...zoom,
|
|
97
|
+
...fx,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function applyDefaultZoom(slots, defaultZoom) {
|
|
101
|
+
if (defaultZoom === undefined || !(defaultZoom > 0))
|
|
102
|
+
return slots;
|
|
103
|
+
return slots.map((h) => (h.zoom !== undefined && h.zoom > 0 ? h : { ...h, zoom: defaultZoom }));
|
|
104
|
+
}
|
|
105
|
+
export function applyDefaultZoomOut(slots, defaultZoomOut) {
|
|
106
|
+
if (defaultZoomOut === undefined)
|
|
107
|
+
return slots;
|
|
108
|
+
return slots.map((h) => h.zoomOut !== undefined ? h : { ...h, zoomOut: defaultZoomOut });
|
|
109
|
+
}
|
|
110
|
+
export function mergePhaseMaps(phaseMap, fixturePhase) {
|
|
111
|
+
const phaseKeys = Object.keys(phaseMap);
|
|
112
|
+
const fixtureKeys = Object.keys(fixturePhase || {});
|
|
113
|
+
const allKeys = [...new Set([...phaseKeys, ...fixtureKeys])];
|
|
114
|
+
const allNumeric = allKeys.length > 0 && allKeys.every((k) => Number.isFinite(Number(k)) && String(Number(k)) === k);
|
|
115
|
+
// Numeric stub ids (legacy "0","1") sort by number. Named stubs keep author
|
|
116
|
+
// insertion order - localeCompare used to reorder password/submit/username
|
|
117
|
+
// and advance the wrong todo index on fill.
|
|
118
|
+
const ordered = allNumeric
|
|
119
|
+
? [...allKeys].sort((a, b) => Number(a) - Number(b))
|
|
120
|
+
: [...phaseKeys, ...fixtureKeys.filter((k) => !phaseKeys.includes(k))];
|
|
121
|
+
const resolved = [];
|
|
122
|
+
for (const id of ordered) {
|
|
123
|
+
const merged = mergeSlot(phaseMap[id], fixturePhase?.[id]);
|
|
124
|
+
if (merged) {
|
|
125
|
+
resolved.push({
|
|
126
|
+
...merged,
|
|
127
|
+
slotId: id,
|
|
128
|
+
...(merged.todo ? { todo: merged.todo } : {}),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return resolved;
|
|
133
|
+
}
|
|
134
|
+
function phaseFromLegacyHighlights(highlights) {
|
|
135
|
+
const out = {};
|
|
136
|
+
if (!highlights)
|
|
137
|
+
return out;
|
|
138
|
+
highlights.forEach((h, i) => {
|
|
139
|
+
if (h && typeof h.selector === "string" && typeof h.label === "string") {
|
|
140
|
+
out[String(i)] = {
|
|
141
|
+
selector: h.selector,
|
|
142
|
+
label: h.label,
|
|
143
|
+
...(h.tone ? { tone: normalizeHighlightTone(h.tone) } : {}),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
export function readStubRaw(block, phase) {
|
|
150
|
+
const instr = block.instruction;
|
|
151
|
+
if (phase === "stubBefore")
|
|
152
|
+
return instr.stubBefore;
|
|
153
|
+
if (phase === "stubAfter")
|
|
154
|
+
return instr.stubAfter;
|
|
155
|
+
return instr.stubOnError;
|
|
156
|
+
}
|
|
157
|
+
export function applyLegacyHighlightsShim(block, phase, phaseMap, out) {
|
|
158
|
+
if (phase !== "stubAfter" || Object.keys(phaseMap).length > 0)
|
|
159
|
+
return phaseMap;
|
|
160
|
+
const instr = block.instruction;
|
|
161
|
+
if (!instr.highlights)
|
|
162
|
+
return phaseMap;
|
|
163
|
+
let legacy = instr.highlights;
|
|
164
|
+
if (typeof legacy === "function") {
|
|
165
|
+
try {
|
|
166
|
+
legacy = legacy(out ?? { __state: "" });
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
legacy = [];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return phaseFromLegacyHighlights(Array.isArray(legacy) ? legacy : []);
|
|
173
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { HighlightFixtureMap, HighlightStubPhaseName, ResolvedHighlight, StubPhaseResult, WaygraphSlide } from "./types.js";
|
|
2
|
+
import type { Block, Checkpoint } from "../types.js";
|
|
3
|
+
import type { MemPage } from "../mem-page.js";
|
|
4
|
+
/**
|
|
5
|
+
* Run an open stub lifecycle (preferred) or resolve a static slot map.
|
|
6
|
+
* Returns rings + episode fixtures (todos / todoIndex / zoom).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const before = await runStubPhase(block, "stubBefore", { fixtures });
|
|
10
|
+
* // before.todos, before.highlights
|
|
11
|
+
*/
|
|
12
|
+
export declare function runStubPhase(block: Block<any, any>, phase: HighlightStubPhaseName, opts?: {
|
|
13
|
+
out?: Checkpoint<string>;
|
|
14
|
+
error?: unknown;
|
|
15
|
+
mem?: MemPage;
|
|
16
|
+
fixtures?: HighlightFixtureMap;
|
|
17
|
+
}): Promise<StubPhaseResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Sync resolve of highlight slots. Prefer {@link runStubPhase} for open
|
|
20
|
+
* async lifecycles + episode fixtures (todos/zoom).
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveHighlightSlots(block: Block<any, any>, phase: HighlightStubPhaseName, opts?: {
|
|
23
|
+
out?: Checkpoint<string>;
|
|
24
|
+
fixtures?: HighlightFixtureMap;
|
|
25
|
+
}): ResolvedHighlight[];
|
|
26
|
+
/**
|
|
27
|
+
* Resolve multi-step yap slides: flow fixture `slides` wins when present,
|
|
28
|
+
* else `instruction.slides` (function form allowed).
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveSlides(block: Block<any, any>, opts?: {
|
|
31
|
+
out?: Checkpoint<string>;
|
|
32
|
+
fixtures?: HighlightFixtureMap;
|
|
33
|
+
}): WaygraphSlide[];
|
|
34
|
+
/**
|
|
35
|
+
* True when the block authored stubAfter (lifecycle fn, slots, fixtures, or legacy highlights).
|
|
36
|
+
* When true, demo should not use verify-trait fallback.
|
|
37
|
+
*/
|
|
38
|
+
export declare function hasAuthoredStubAfter(block: Block<any, any>, out?: Checkpoint<string>, fixtures?: HighlightFixtureMap): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* True when stubOnError is authored (lifecycle fn and/or rings).
|
|
41
|
+
* Demo fail path only - success never consults this.
|
|
42
|
+
*/
|
|
43
|
+
export declare function hasAuthoredStubOnError(block: Block<any, any>, fixtures?: HighlightFixtureMap): boolean;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { createStubCtx, liftLegacySlotTodos } from "./stub-ctx.js";
|
|
2
|
+
import { applyDefaultZoom, applyDefaultZoomOut, applyLegacyHighlightsShim, isStubPhase, mergePhaseMaps, readStubRaw } from "./merge.js";
|
|
3
|
+
import { buildTodoDock, normalizeTodoStyle, normalizeTodos } from "./todo-dock.js";
|
|
4
|
+
import { resolveDeviceState } from "./device.js";
|
|
5
|
+
/**
|
|
6
|
+
* Run an open stub lifecycle (preferred) or resolve a static slot map.
|
|
7
|
+
* Returns rings + episode fixtures (todos / todoIndex / zoom).
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const before = await runStubPhase(block, "stubBefore", { fixtures });
|
|
11
|
+
* // before.todos, before.highlights
|
|
12
|
+
*/
|
|
13
|
+
export async function runStubPhase(block, phase, opts) {
|
|
14
|
+
const bag = { highlights: {} };
|
|
15
|
+
const ctx = createStubCtx(bag, {
|
|
16
|
+
...(opts?.out !== undefined ? { out: opts.out } : {}),
|
|
17
|
+
...(opts?.error !== undefined ? { error: opts.error } : {}),
|
|
18
|
+
...(opts?.mem !== undefined ? { mem: opts.mem } : {}),
|
|
19
|
+
});
|
|
20
|
+
const raw = readStubRaw(block, phase);
|
|
21
|
+
if (typeof raw === "function") {
|
|
22
|
+
try {
|
|
23
|
+
const ret = await raw(ctx);
|
|
24
|
+
if (isStubPhase(ret)) {
|
|
25
|
+
bag.highlights = { ...bag.highlights, ...ret };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
/* best-effort */
|
|
30
|
+
}
|
|
31
|
+
// Compat: legacy `(out) => map` that expected Checkpoint (__state on arg)
|
|
32
|
+
if (Object.keys(bag.highlights).length === 0 && !bag.todos) {
|
|
33
|
+
try {
|
|
34
|
+
const legacyOut = (opts?.out ?? { __state: "" });
|
|
35
|
+
const ret2 = raw(legacyOut);
|
|
36
|
+
if (isStubPhase(ret2))
|
|
37
|
+
bag.highlights = { ...ret2 };
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
/* ignore */
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else if (isStubPhase(raw)) {
|
|
45
|
+
bag.highlights = { ...raw };
|
|
46
|
+
}
|
|
47
|
+
bag.highlights = applyLegacyHighlightsShim(block, phase, bag.highlights, opts?.out);
|
|
48
|
+
liftLegacySlotTodos(bag);
|
|
49
|
+
const fixturePhase = opts?.fixtures?.[block.name]?.[phase];
|
|
50
|
+
let highlights = mergePhaseMaps(bag.highlights, fixturePhase);
|
|
51
|
+
highlights = applyDefaultZoom(highlights, bag.zoom);
|
|
52
|
+
highlights = applyDefaultZoomOut(highlights, bag.zoomOut);
|
|
53
|
+
const todoDock = bag.hideTodos
|
|
54
|
+
? undefined
|
|
55
|
+
: buildTodoDock({
|
|
56
|
+
todos: bag.todos,
|
|
57
|
+
todoIndex: bag.todoIndex,
|
|
58
|
+
todoTitle: bag.todoTitle,
|
|
59
|
+
todoId: bag.todoId,
|
|
60
|
+
todoStyle: bag.todoStyle,
|
|
61
|
+
todoGroups: bag.todoGroups,
|
|
62
|
+
todoPos: bag.todoPos,
|
|
63
|
+
});
|
|
64
|
+
const todos = todoDock?.groups[0]?.items ||
|
|
65
|
+
(bag.hideTodos
|
|
66
|
+
? []
|
|
67
|
+
: normalizeTodos(bag.todos, bag.todoIndex, normalizeTodoStyle(bag.todoStyle) || "sequential"));
|
|
68
|
+
// Omit todos entirely => keep previous dock. Explicit empty / hideTodos => clear.
|
|
69
|
+
const todoSync = bag.hideTodos
|
|
70
|
+
? "clear"
|
|
71
|
+
: bag.todoTouched && todoDock
|
|
72
|
+
? "set"
|
|
73
|
+
: bag.todoTouched && !todoDock
|
|
74
|
+
? "clear"
|
|
75
|
+
: "keep";
|
|
76
|
+
let deviceState;
|
|
77
|
+
if (bag.hideDevice) {
|
|
78
|
+
deviceState = resolveDeviceState("desktop", false);
|
|
79
|
+
}
|
|
80
|
+
else if (bag.device) {
|
|
81
|
+
deviceState = bag.touchOverride !== undefined
|
|
82
|
+
? {
|
|
83
|
+
...bag.device,
|
|
84
|
+
touchMode: bag.touchOverride,
|
|
85
|
+
hasTouch: bag.device.hasTouch || bag.touchOverride,
|
|
86
|
+
}
|
|
87
|
+
: bag.device;
|
|
88
|
+
}
|
|
89
|
+
else if (bag.touchOverride !== undefined) {
|
|
90
|
+
deviceState = resolveDeviceState("desktop", bag.touchOverride);
|
|
91
|
+
}
|
|
92
|
+
const deviceSync = bag.hideDevice
|
|
93
|
+
? "clear"
|
|
94
|
+
: bag.deviceTouched && deviceState
|
|
95
|
+
? "set"
|
|
96
|
+
: bag.deviceTouched && !deviceState
|
|
97
|
+
? "clear"
|
|
98
|
+
: "keep";
|
|
99
|
+
return {
|
|
100
|
+
highlights,
|
|
101
|
+
todos,
|
|
102
|
+
todoSync,
|
|
103
|
+
deviceSync,
|
|
104
|
+
...(bag.todoIndex !== undefined ? { todoIndex: bag.todoIndex } : {}),
|
|
105
|
+
...(todoDock ? { todoDock } : {}),
|
|
106
|
+
...(deviceState ? { device: deviceState } : {}),
|
|
107
|
+
...(bag.zoom !== undefined ? { zoom: bag.zoom } : {}),
|
|
108
|
+
...(bag.zoomOut !== undefined ? { zoomOut: bag.zoomOut } : {}),
|
|
109
|
+
...(bag.title !== undefined && bag.title !== "" ? { title: bag.title } : {}),
|
|
110
|
+
...(bag.todoPos !== undefined ? { todoPos: bag.todoPos } : {}),
|
|
111
|
+
...(bag.todoDockUi !== undefined ? { todoDockUi: bag.todoDockUi } : {}),
|
|
112
|
+
...(bag.todoParallel === true ? { todoParallel: true } : {}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Sync resolve of highlight slots. Prefer {@link runStubPhase} for open
|
|
117
|
+
* async lifecycles + episode fixtures (todos/zoom).
|
|
118
|
+
*/
|
|
119
|
+
export function resolveHighlightSlots(block, phase, opts) {
|
|
120
|
+
const bag = { highlights: {} };
|
|
121
|
+
const raw = readStubRaw(block, phase);
|
|
122
|
+
if (typeof raw === "function") {
|
|
123
|
+
try {
|
|
124
|
+
const ctx = createStubCtx(bag, {
|
|
125
|
+
...(opts?.out !== undefined ? { out: opts.out } : {}),
|
|
126
|
+
});
|
|
127
|
+
const ret = raw(ctx);
|
|
128
|
+
if (isStubPhase(ret)) {
|
|
129
|
+
bag.highlights = { ...bag.highlights, ...ret };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
try {
|
|
134
|
+
const legacyOut = (opts?.out ?? { __state: "" });
|
|
135
|
+
const ret2 = raw(legacyOut);
|
|
136
|
+
if (isStubPhase(ret2))
|
|
137
|
+
bag.highlights = { ...ret2 };
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
bag.highlights = {};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else if (isStubPhase(raw)) {
|
|
145
|
+
bag.highlights = { ...raw };
|
|
146
|
+
}
|
|
147
|
+
bag.highlights = applyLegacyHighlightsShim(block, phase, bag.highlights, opts?.out);
|
|
148
|
+
liftLegacySlotTodos(bag);
|
|
149
|
+
const fixturePhase = opts?.fixtures?.[block.name]?.[phase];
|
|
150
|
+
return applyDefaultZoom(mergePhaseMaps(bag.highlights, fixturePhase), bag.zoom);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Resolve multi-step yap slides: flow fixture `slides` wins when present,
|
|
154
|
+
* else `instruction.slides` (function form allowed).
|
|
155
|
+
*/
|
|
156
|
+
export function resolveSlides(block, opts) {
|
|
157
|
+
const fromFixture = opts?.fixtures?.[block.name]?.slides;
|
|
158
|
+
if (fromFixture && fromFixture.length > 0) {
|
|
159
|
+
return [...fromFixture];
|
|
160
|
+
}
|
|
161
|
+
const instr = block.instruction;
|
|
162
|
+
let raw = instr.slides;
|
|
163
|
+
if (typeof raw === "function") {
|
|
164
|
+
try {
|
|
165
|
+
raw = raw(opts?.out ?? { __state: "" });
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
raw = [];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (!Array.isArray(raw))
|
|
172
|
+
return [];
|
|
173
|
+
return raw.filter((s) => s && typeof s.caption === "string" && s.caption.length > 0);
|
|
174
|
+
}
|
|
175
|
+
function stubPhaseLooksAuthored(block, phase, fixtures) {
|
|
176
|
+
const raw = readStubRaw(block, phase);
|
|
177
|
+
if (typeof raw === "function")
|
|
178
|
+
return true;
|
|
179
|
+
if (isStubPhase(raw) && Object.keys(raw).length > 0)
|
|
180
|
+
return true;
|
|
181
|
+
const fx = fixtures?.[block.name]?.[phase];
|
|
182
|
+
if (fx && Object.keys(fx).length > 0)
|
|
183
|
+
return true;
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* True when the block authored stubAfter (lifecycle fn, slots, fixtures, or legacy highlights).
|
|
188
|
+
* When true, demo should not use verify-trait fallback.
|
|
189
|
+
*/
|
|
190
|
+
export function hasAuthoredStubAfter(block, out, fixtures) {
|
|
191
|
+
if (stubPhaseLooksAuthored(block, "stubAfter", fixtures))
|
|
192
|
+
return true;
|
|
193
|
+
const instr = block.instruction;
|
|
194
|
+
if (instr.highlights)
|
|
195
|
+
return true;
|
|
196
|
+
return resolveHighlightSlots(block, "stubAfter", {
|
|
197
|
+
...(out !== undefined ? { out } : {}),
|
|
198
|
+
...(fixtures !== undefined ? { fixtures } : {}),
|
|
199
|
+
}).length > 0;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* True when stubOnError is authored (lifecycle fn and/or rings).
|
|
203
|
+
* Demo fail path only - success never consults this.
|
|
204
|
+
*/
|
|
205
|
+
export function hasAuthoredStubOnError(block, fixtures) {
|
|
206
|
+
if (stubPhaseLooksAuthored(block, "stubOnError", fixtures))
|
|
207
|
+
return true;
|
|
208
|
+
return (resolveHighlightSlots(block, "stubOnError", {
|
|
209
|
+
...(fixtures !== undefined ? { fixtures } : {}),
|
|
210
|
+
}).length > 0);
|
|
211
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { StubBagState, StubCtx } from "./types.js";
|
|
2
|
+
import type { Checkpoint } from "../types.js";
|
|
3
|
+
import type { MemPage } from "../mem-page.js";
|
|
4
|
+
export declare function createStubCtx<Out extends Checkpoint<string>>(bag: StubBagState, opts: {
|
|
5
|
+
out?: Out;
|
|
6
|
+
error?: unknown;
|
|
7
|
+
mem?: MemPage;
|
|
8
|
+
}): StubCtx<Out>;
|
|
9
|
+
/** Lift legacy per-slot todos (0.12.23) onto the phase bag once. */
|
|
10
|
+
export declare function liftLegacySlotTodos(bag: StubBagState): void;
|