tapline-host 1.4.0 → 1.5.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/dist/audit.d.ts +2 -0
- package/dist/audit.js +1 -1
- package/dist/audit.js.map +1 -1
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -1
- package/dist/format.d.ts +4 -0
- package/dist/format.js +61 -20
- package/dist/format.js.map +1 -1
- package/dist/images.d.ts +27 -0
- package/dist/images.js +50 -0
- package/dist/images.js.map +1 -1
- package/dist/journey.d.ts +259 -0
- package/dist/journey.js +336 -0
- package/dist/journey.js.map +1 -0
- package/dist/journeyexport.d.ts +58 -0
- package/dist/journeyexport.js +318 -0
- package/dist/journeyexport.js.map +1 -0
- package/dist/journeygif.d.ts +62 -0
- package/dist/journeygif.js +192 -0
- package/dist/journeygif.js.map +1 -0
- package/dist/journeyreport.d.ts +202 -0
- package/dist/journeyreport.js +664 -0
- package/dist/journeyreport.js.map +1 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +27 -1
- package/dist/mcp.js.map +1 -1
- package/dist/media.d.ts +86 -0
- package/dist/media.js +244 -0
- package/dist/media.js.map +1 -0
- package/dist/protocol.d.ts +4 -0
- package/dist/protocol.js +2 -0
- package/dist/protocol.js.map +1 -1
- package/dist/run.js +1 -1
- package/dist/run.js.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.js +8 -0
- package/dist/server.js.map +1 -1
- package/dist/tool-text-data.d.ts +3 -0
- package/dist/tool-text-data.js +3 -0
- package/dist/tool-text-data.js.map +1 -1
- package/dist/tools.d.ts +213 -0
- package/dist/tools.js +1075 -28
- package/dist/tools.js.map +1 -1
- package/dist/uxcheck.d.ts +102 -0
- package/dist/uxcheck.js +128 -0
- package/dist/uxcheck.js.map +1 -0
- package/package.json +2 -1
- package/skills/tapline/SKILL.md +7 -0
- package/skills/tapline/references/personas.md +45 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import gifencDefault, * as gifencNamespace from "gifenc";
|
|
2
|
+
import { markRing, scaleToWidth } from "./images.js";
|
|
3
|
+
function isGifenc(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && "GIFEncoder" in value && typeof value.GIFEncoder === "function" && "quantize" in value && "applyPalette" in value;
|
|
5
|
+
}
|
|
6
|
+
/** gifenc is CommonJS with a `default` of its own: Node hands over its exports as the default, bundlers and test runners as the module. */
|
|
7
|
+
function loadGifenc() {
|
|
8
|
+
const inner = typeof gifencDefault === "object" && gifencDefault !== null && "default" in gifencDefault ? gifencDefault.default : undefined;
|
|
9
|
+
for (const candidate of [gifencDefault, inner, gifencNamespace])
|
|
10
|
+
if (isGifenc(candidate))
|
|
11
|
+
return candidate;
|
|
12
|
+
throw new Error("The GIF encoder (gifenc) could not be loaded.");
|
|
13
|
+
}
|
|
14
|
+
const gifenc = loadGifenc();
|
|
15
|
+
export const GIF_WIDTH = 360;
|
|
16
|
+
export const GIF_MAX_BYTES = 3 * 1024 * 1024;
|
|
17
|
+
/** A still stretch shows its frame this long at most (two frames a second); closer frames keep their real gap. */
|
|
18
|
+
const STILL_MS = 500;
|
|
19
|
+
const MIN_DELAY_MS = 40;
|
|
20
|
+
const RING_MS = 350;
|
|
21
|
+
const LAST_MS = 1500;
|
|
22
|
+
/** Time handed to the frame before when a frame is dropped, at most. */
|
|
23
|
+
const MERGED_MS = 1200;
|
|
24
|
+
const COLOURS = 128;
|
|
25
|
+
/** The frames made the same size as the first one. */
|
|
26
|
+
export function fitted(frames, width) {
|
|
27
|
+
const first = frames[0];
|
|
28
|
+
if (first === undefined)
|
|
29
|
+
return [];
|
|
30
|
+
const scaledFirst = scaleToWidth(first.pixels, width);
|
|
31
|
+
const height = scaledFirst.height;
|
|
32
|
+
return frames.map((frame, i) => {
|
|
33
|
+
const scaled = i === 0 ? scaledFirst : scaleToWidth(frame.pixels, width);
|
|
34
|
+
if (scaled.height === height)
|
|
35
|
+
return { ...frame, pixels: scaled };
|
|
36
|
+
// Another shape (the phone turned): the whole picture is fitted inside the canvas, centred, never cropped.
|
|
37
|
+
const k = Math.min(width / frame.pixels.width, height / frame.pixels.height);
|
|
38
|
+
const inner = scaleToWidth(frame.pixels, Math.max(1, Math.round(frame.pixels.width * k)));
|
|
39
|
+
const data = new Uint8Array(width * height * 4).fill(255);
|
|
40
|
+
const left = Math.max(0, Math.floor((width - inner.width) / 2));
|
|
41
|
+
const top = Math.max(0, Math.floor((height - inner.height) / 2));
|
|
42
|
+
const rows = Math.min(inner.height, height - top);
|
|
43
|
+
const columns = Math.min(inner.width, width - left);
|
|
44
|
+
for (let y = 0; y < rows; y++) {
|
|
45
|
+
data.set(inner.data.subarray(y * inner.width * 4, (y * inner.width + columns) * 4), ((top + y) * width + left) * 4);
|
|
46
|
+
}
|
|
47
|
+
// Where the picture sits in the canvas (shares of it), so a tap's share of the screen can be moved to match.
|
|
48
|
+
return { ...frame, pixels: { width, height, data }, fit: { x: left / width, y: top / height, w: inner.width / width, h: inner.height / height } };
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/** A tap (a share of the screen) as a share of the canvas the screen was fitted in. */
|
|
52
|
+
function tapOnCanvas(tap, fit) {
|
|
53
|
+
return fit === undefined ? tap : { x: fit.x + tap.x * fit.w, y: fit.y + tap.y * fit.h };
|
|
54
|
+
}
|
|
55
|
+
export function slidesOf(frames) {
|
|
56
|
+
const slides = [];
|
|
57
|
+
frames.forEach((frame, i) => {
|
|
58
|
+
const previous = frames[i - 1];
|
|
59
|
+
if (frame.tap !== undefined) {
|
|
60
|
+
// The tap was made on the screen before it: the ring goes there, a moment before the result.
|
|
61
|
+
// The ring sits on the picture it is drawn on, so it follows that picture's scale and offset in the canvas.
|
|
62
|
+
const shown = previous ?? frame;
|
|
63
|
+
slides.push({ pixels: markRing(shown.pixels, tapOnCanvas(frame.tap, shown.fit)), delay: RING_MS, still: false, ring: true });
|
|
64
|
+
}
|
|
65
|
+
const next = frames[i + 1];
|
|
66
|
+
const gap = next === undefined ? LAST_MS : Math.max(0, next.at - frame.at);
|
|
67
|
+
slides.push({
|
|
68
|
+
pixels: frame.pixels,
|
|
69
|
+
delay: next === undefined ? LAST_MS : Math.min(STILL_MS, Math.max(MIN_DELAY_MS, gap)),
|
|
70
|
+
still: next !== undefined && gap > STILL_MS,
|
|
71
|
+
ring: false,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
return slides;
|
|
75
|
+
}
|
|
76
|
+
/** Every second one of `indexes` (or the only one), dropped; the time goes to the picture before. */
|
|
77
|
+
function dropHalf(slides, indexes) {
|
|
78
|
+
if (indexes.length === 0)
|
|
79
|
+
return null;
|
|
80
|
+
const gone = new Set(indexes.length === 1 ? indexes : indexes.filter((_, n) => n % 2 === 1));
|
|
81
|
+
const kept = [];
|
|
82
|
+
slides.forEach((slide, i) => {
|
|
83
|
+
if (!gone.has(i)) {
|
|
84
|
+
kept.push(slide);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const before = kept[kept.length - 1];
|
|
88
|
+
if (before !== undefined)
|
|
89
|
+
kept[kept.length - 1] = { ...before, delay: Math.min(MERGED_MS, before.delay + slide.delay) };
|
|
90
|
+
});
|
|
91
|
+
return kept;
|
|
92
|
+
}
|
|
93
|
+
/** Fewer pictures: the still ones first, then every second one but the first and the last; null when there is nothing left to drop. */
|
|
94
|
+
function shrink(slides) {
|
|
95
|
+
const inner = slides.map((_, i) => i).filter((i) => i > 0 && i < slides.length - 1);
|
|
96
|
+
const still = inner.filter((i) => slides[i]?.still === true);
|
|
97
|
+
return dropHalf(slides, still) ?? dropHalf(slides, inner);
|
|
98
|
+
}
|
|
99
|
+
/** How the frames are laid out in time (for tests): each picture's delay in ms, whether it is a still stretch, whether it is a tap ring. */
|
|
100
|
+
export function slidePlan(frames) {
|
|
101
|
+
return slidesOf([...frames].sort((a, b) => a.at - b.at)).map(({ delay, still, ring }) => ({ delay, still, ring }));
|
|
102
|
+
}
|
|
103
|
+
function encode(slides, coded) {
|
|
104
|
+
const first = slides[0];
|
|
105
|
+
if (first === undefined)
|
|
106
|
+
throw new Error("There are no frames for a GIF.");
|
|
107
|
+
const encoder = gifenc.GIFEncoder();
|
|
108
|
+
const { width, height } = first.pixels;
|
|
109
|
+
for (const slide of slides) {
|
|
110
|
+
let entry = coded.get(slide.pixels);
|
|
111
|
+
if (entry === undefined) {
|
|
112
|
+
const palette = gifenc.quantize(slide.pixels.data, COLOURS, { format: "rgb444" });
|
|
113
|
+
entry = { palette, index: gifenc.applyPalette(slide.pixels.data, palette, "rgb444") };
|
|
114
|
+
coded.set(slide.pixels, entry);
|
|
115
|
+
}
|
|
116
|
+
encoder.writeFrame(entry.index, width, height, { palette: entry.palette, delay: slide.delay, repeat: 0 });
|
|
117
|
+
}
|
|
118
|
+
encoder.finish();
|
|
119
|
+
return encoder.bytes();
|
|
120
|
+
}
|
|
121
|
+
/** The GIF of a journey. `maxBytes` and `width` are for tests; the defaults are the spec's (3 MB, 360 px). */
|
|
122
|
+
export function encodeJourneyGif(frames, options = {}) {
|
|
123
|
+
if (frames.length === 0)
|
|
124
|
+
throw new Error("There are no frames for a GIF.");
|
|
125
|
+
const maxBytes = options.maxBytes ?? GIF_MAX_BYTES;
|
|
126
|
+
let width = options.width ?? GIF_WIDTH;
|
|
127
|
+
let shrunk = false;
|
|
128
|
+
let ordered = [...frames].sort((a, b) => a.at - b.at);
|
|
129
|
+
for (;;) {
|
|
130
|
+
let slides = slidesOf(fitted(ordered, width));
|
|
131
|
+
const total = slides.length;
|
|
132
|
+
const coded = new Map();
|
|
133
|
+
let bytes = encode(slides, coded);
|
|
134
|
+
while (bytes.length > maxBytes) {
|
|
135
|
+
const fewer = shrink(slides);
|
|
136
|
+
if (fewer === null)
|
|
137
|
+
break;
|
|
138
|
+
slides = fewer;
|
|
139
|
+
bytes = encode(slides, coded);
|
|
140
|
+
}
|
|
141
|
+
const first = slides[0];
|
|
142
|
+
if (bytes.length <= maxBytes || width <= 90 || first === undefined) {
|
|
143
|
+
return { bytes, frames: slides.length, width: first?.pixels.width ?? width, height: first?.pixels.height ?? 0, dropped: total - slides.length, shrunk };
|
|
144
|
+
}
|
|
145
|
+
// Even two pictures are over the cap: the same pictures at half the size.
|
|
146
|
+
width = Math.max(90, Math.round(width / 2));
|
|
147
|
+
shrunk = true;
|
|
148
|
+
ordered = ordered.map((frame) => ({ ...frame, pixels: scaleToWidth(frame.pixels, width) }));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Reads a GIF's header and counts its pictures by walking its blocks; throws when it isn't one. */
|
|
152
|
+
export function gifInfo(bytes) {
|
|
153
|
+
const text = (from, to) => String.fromCharCode(...bytes.subarray(from, to));
|
|
154
|
+
const head = text(0, 6);
|
|
155
|
+
if (!/^GIF8[79]a$/.test(head))
|
|
156
|
+
throw new Error("not a GIF");
|
|
157
|
+
const u16 = (at) => (bytes[at] ?? 0) | ((bytes[at + 1] ?? 0) << 8);
|
|
158
|
+
const width = u16(6);
|
|
159
|
+
const height = u16(8);
|
|
160
|
+
const packed = bytes[10] ?? 0;
|
|
161
|
+
let at = 13 + ((packed & 0x80) !== 0 ? 3 * 2 ** ((packed & 7) + 1) : 0);
|
|
162
|
+
let frames = 0;
|
|
163
|
+
let loops;
|
|
164
|
+
const skipBlocks = () => {
|
|
165
|
+
while (at < bytes.length && (bytes[at] ?? 0) !== 0)
|
|
166
|
+
at += (bytes[at] ?? 0) + 1;
|
|
167
|
+
at += 1;
|
|
168
|
+
};
|
|
169
|
+
while (at < bytes.length) {
|
|
170
|
+
const marker = bytes[at];
|
|
171
|
+
if (marker === 0x3b)
|
|
172
|
+
break;
|
|
173
|
+
if (marker === 0x21) {
|
|
174
|
+
const label = bytes[at + 1];
|
|
175
|
+
if (label === 0xff && text(at + 3, at + 14) === "NETSCAPE2.0")
|
|
176
|
+
loops = u16(at + 16);
|
|
177
|
+
at += 2;
|
|
178
|
+
skipBlocks();
|
|
179
|
+
}
|
|
180
|
+
else if (marker === 0x2c) {
|
|
181
|
+
const flags = bytes[at + 9] ?? 0;
|
|
182
|
+
at += 10 + ((flags & 0x80) !== 0 ? 3 * 2 ** ((flags & 7) + 1) : 0);
|
|
183
|
+
at += 1; // the LZW minimum code size
|
|
184
|
+
skipBlocks();
|
|
185
|
+
frames++;
|
|
186
|
+
}
|
|
187
|
+
else
|
|
188
|
+
throw new Error("not a GIF");
|
|
189
|
+
}
|
|
190
|
+
return { version: head, width, height, frames, loops };
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=journeygif.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journeygif.js","sourceRoot":"","sources":["../src/journeygif.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,EAAE,KAAK,eAAe,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAe,MAAM,aAAa,CAAC;AASlE,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,YAAY,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,UAAU,KAAK,UAAU,IAAI,UAAU,IAAI,KAAK,IAAI,cAAc,IAAI,KAAK,CAAC;AAC1K,CAAC;AAED,2IAA2I;AAC3I,SAAS,UAAU;IACjB,MAAM,KAAK,GAAY,OAAO,aAAa,KAAK,QAAQ,IAAI,aAAa,KAAK,IAAI,IAAI,SAAS,IAAI,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IACrJ,KAAK,MAAM,SAAS,IAAI,CAAC,aAAa,EAAE,KAAK,EAAE,eAAe,CAAc;QAAE,IAAI,QAAQ,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IACxH,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;AAE5B,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,CAAC;AAC7B,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAC7C,kHAAkH;AAClH,MAAM,QAAQ,GAAG,GAAG,CAAC;AACrB,MAAM,YAAY,GAAG,EAAE,CAAC;AACxB,MAAM,OAAO,GAAG,GAAG,CAAC;AACpB,MAAM,OAAO,GAAG,IAAI,CAAC;AACrB,wEAAwE;AACxE,MAAM,SAAS,GAAG,IAAI,CAAC;AACvB,MAAM,OAAO,GAAG,GAAG,CAAC;AAqCpB,sDAAsD;AACtD,MAAM,UAAU,MAAM,CAAC,MAAkB,EAAE,KAAa;IACtD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACnC,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC;IAClC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACzE,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM;YAAE,OAAO,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAClE,2GAA2G;QAC3G,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC7E,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1F,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAChE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACjE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,CAAC;QAClD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC,CAAC;QACpD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9B,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACtH,CAAC;QACD,6GAA6G;QAC7G,OAAO,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,IAAI,GAAG,KAAK,EAAE,CAAC,EAAE,GAAG,GAAG,MAAM,EAAE,CAAC,EAAE,KAAK,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,EAAE,EAAE,CAAC;IACpJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,uFAAuF;AACvF,SAAS,WAAW,CAAC,GAA6B,EAAE,GAAoB;IACtE,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC;AAC1F,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAkB;IACzC,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAC1B,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/B,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;YAC5B,6FAA6F;YAC7F,4GAA4G;YAC5G,MAAM,KAAK,GAAG,QAAQ,IAAI,KAAK,CAAC;YAChC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/H,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC3B,MAAM,GAAG,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;QAC3E,MAAM,CAAC,IAAI,CAAC;YACV,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,KAAK,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;YACrF,KAAK,EAAE,IAAI,KAAK,SAAS,IAAI,GAAG,GAAG,QAAQ;YAC3C,IAAI,EAAE,KAAK;SACZ,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,qGAAqG;AACrG,SAAS,QAAQ,CAAC,MAAe,EAAE,OAAiB;IAClD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7F,MAAM,IAAI,GAAY,EAAE,CAAC;IACzB,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAC1B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACjB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjB,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACrC,IAAI,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;IAC1H,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACd,CAAC;AAED,uIAAuI;AACvI,SAAS,MAAM,CAAC,MAAe;IAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACpF,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC,CAAC;IAC7D,OAAO,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAC5D,CAAC;AAED,4IAA4I;AAC5I,MAAM,UAAU,SAAS,CAAC,MAAkB;IAC1C,OAAO,QAAQ,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACrH,CAAC;AAED,SAAS,MAAM,CAAC,MAAe,EAAE,KAAyB;IACxD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,KAAK,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAC3E,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IACpC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC;IACvC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;YAClF,KAAK,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC;YACtF,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACjC,CAAC;QACD,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5G,CAAC;IACD,OAAO,CAAC,MAAM,EAAE,CAAC;IACjB,OAAO,OAAO,CAAC,KAAK,EAAE,CAAC;AACzB,CAAC;AAED,8GAA8G;AAC9G,MAAM,UAAU,gBAAgB,CAAC,MAAkB,EAAE,UAAiD,EAAE;IACtG,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAC3E,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,aAAa,CAAC;IACnD,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,SAAS,CAAC;IACvC,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;IACtD,SAAS,CAAC;QACR,IAAI,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;QAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC;QAC5B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAiB,CAAC;QACvC,IAAI,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAClC,OAAO,KAAK,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;YAC7B,IAAI,KAAK,KAAK,IAAI;gBAAE,MAAM;YAC1B,MAAM,GAAG,KAAK,CAAC;YACf,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACxB,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ,IAAI,KAAK,IAAI,EAAE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACnE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QAC1J,CAAC;QACD,0EAA0E;QAC1E,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;QAC5C,MAAM,GAAG,IAAI,CAAC;QACd,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9F,CAAC;AACH,CAAC;AAWD,oGAAoG;AACpG,MAAM,UAAU,OAAO,CAAC,KAAiB;IACvC,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAU,EAAU,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;IACpG,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACxB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC;IAC5D,MAAM,GAAG,GAAG,CAAC,EAAU,EAAU,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACnF,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACrB,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACtB,MAAM,MAAM,GAAG,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxE,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,KAAyB,CAAC;IAC9B,MAAM,UAAU,GAAG,GAAS,EAAE;QAC5B,OAAO,EAAE,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YAAE,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAC/E,EAAE,IAAI,CAAC,CAAC;IACV,CAAC,CAAC;IACF,OAAO,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;QACzB,IAAI,MAAM,KAAK,IAAI;YAAE,MAAM;QAC3B,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YAC5B,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,KAAK,aAAa;gBAAE,KAAK,GAAG,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;YACpF,EAAE,IAAI,CAAC,CAAC;YACR,UAAU,EAAE,CAAC;QACf,CAAC;aAAM,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;YACjC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACnE,EAAE,IAAI,CAAC,CAAC,CAAC,4BAA4B;YACrC,UAAU,EAAE,CAAC;YACb,MAAM,EAAE,CAAC;QACX,CAAC;;YAAM,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { ShotImage } from "./annotate.js";
|
|
2
|
+
import { type ShareBox } from "./images.js";
|
|
3
|
+
import { type JourneyState } from "./journey.js";
|
|
4
|
+
import type { UxAction, UxFindingSummary } from "./uxcheck.js";
|
|
5
|
+
/**
|
|
6
|
+
* The journey's visual report (AGENT-TOOLS 1.12 §17.5): the frames kept after each counted step, the findings, and
|
|
7
|
+
* the folder written at `stop`: `report.md`, `report.html`, `frames/`, `findings/`, `journey.gif`, `key-1.jpg`,
|
|
8
|
+
* `key-2.jpg`. Pictures stay in files; only the two key pictures reach the agent.
|
|
9
|
+
*/
|
|
10
|
+
export declare const FRAME_WIDTH = 360;
|
|
11
|
+
/** The report lists this many findings at most (the worst first) and says how many more there were; it never blocks `stop`. */
|
|
12
|
+
export declare const MAX_FINDINGS = 100;
|
|
13
|
+
/** What the app showed at one moment, from a cheap read that touches nothing. */
|
|
14
|
+
export interface Peek {
|
|
15
|
+
/** Hash of what is on screen: same hash, same screen. */
|
|
16
|
+
signature: string;
|
|
17
|
+
screen?: {
|
|
18
|
+
w: number;
|
|
19
|
+
h: number;
|
|
20
|
+
};
|
|
21
|
+
route: string[];
|
|
22
|
+
}
|
|
23
|
+
/** Where the recorder gets its pictures: the host's requests to the app, or a fake in tests. */
|
|
24
|
+
export interface FrameSource {
|
|
25
|
+
/** Whether the journey's app is connected and in front: frames are only taken then. */
|
|
26
|
+
where(): "front" | "background" | "disconnected" | "other";
|
|
27
|
+
peek(): Promise<Peek | undefined>;
|
|
28
|
+
shot(width: number, quality: number): Promise<ShotImage | undefined>;
|
|
29
|
+
}
|
|
30
|
+
/** The screen's elements as one hash: texts, values, positions and the route, not refs. */
|
|
31
|
+
export declare function screenSignature(elements: ReadonlyArray<{
|
|
32
|
+
role?: string | null;
|
|
33
|
+
text?: string | null;
|
|
34
|
+
label?: string | null;
|
|
35
|
+
value?: unknown;
|
|
36
|
+
frame?: {
|
|
37
|
+
x: number;
|
|
38
|
+
y: number;
|
|
39
|
+
width: number;
|
|
40
|
+
height: number;
|
|
41
|
+
} | null;
|
|
42
|
+
}>, route: string[], alerts: number): string;
|
|
43
|
+
/** Where a step touched, in points, and the frame of the element it touched. */
|
|
44
|
+
export interface Touch {
|
|
45
|
+
point?: {
|
|
46
|
+
x: number;
|
|
47
|
+
y: number;
|
|
48
|
+
};
|
|
49
|
+
frame?: {
|
|
50
|
+
x: number;
|
|
51
|
+
y: number;
|
|
52
|
+
width: number;
|
|
53
|
+
height: number;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export interface FrameRecord {
|
|
57
|
+
/** 1-based, in the order kept. */
|
|
58
|
+
n: number;
|
|
59
|
+
/** The step this frame follows; 0 is the screen at the start. */
|
|
60
|
+
step: number;
|
|
61
|
+
/** Relative to the report folder. */
|
|
62
|
+
file: string;
|
|
63
|
+
at: number;
|
|
64
|
+
/** The journey's screen number. */
|
|
65
|
+
screen: number | undefined;
|
|
66
|
+
signature: string;
|
|
67
|
+
/** Where the step touched and the touched element's box, as shares of the screen. */
|
|
68
|
+
tap?: {
|
|
69
|
+
x: number;
|
|
70
|
+
y: number;
|
|
71
|
+
};
|
|
72
|
+
box?: ShareBox;
|
|
73
|
+
}
|
|
74
|
+
/** One finding of a `ux_check` the agent made during the journey, with its marked-up picture. */
|
|
75
|
+
export interface SeenFinding extends UxFindingSummary {
|
|
76
|
+
screen: number | undefined;
|
|
77
|
+
title: string;
|
|
78
|
+
/** Relative path of the picture with the red box. */
|
|
79
|
+
shot?: string;
|
|
80
|
+
}
|
|
81
|
+
export declare class JourneyRecorder {
|
|
82
|
+
readonly folder: string;
|
|
83
|
+
private readonly source;
|
|
84
|
+
private readonly clock;
|
|
85
|
+
readonly frames: FrameRecord[];
|
|
86
|
+
readonly seen: SeenFinding[];
|
|
87
|
+
/** Skipped frames: the list hadn't changed and nothing animated. */
|
|
88
|
+
skipped: number;
|
|
89
|
+
/** Frames not taken because the journey's app was not connected and in front, by where it was. */
|
|
90
|
+
readonly away: Map<string, number>;
|
|
91
|
+
private readonly actions;
|
|
92
|
+
private queue;
|
|
93
|
+
private lastSignature;
|
|
94
|
+
private uxCount;
|
|
95
|
+
/** Times the app's photo picker was skipped (`media` with `picker`) during the journey. */
|
|
96
|
+
pickerSkips: number;
|
|
97
|
+
pickerPictures: number;
|
|
98
|
+
constructor(folder: string, source: FrameSource, clock: () => number);
|
|
99
|
+
get name(): string;
|
|
100
|
+
/** Runs `work` after the ones before it: the pictures come in the order of the steps. */
|
|
101
|
+
private later;
|
|
102
|
+
/**
|
|
103
|
+
* Keeps a small picture of the screen after a step. Skipped when the element list is what it was and nothing
|
|
104
|
+
* animates (AGENTS.md: don't look twice). Never fails the step.
|
|
105
|
+
*/
|
|
106
|
+
frame(step: number, screen: number | undefined, touch: Touch | undefined, animating: boolean): Promise<void>;
|
|
107
|
+
/** The action labels of the screens seen before `current`, for `ux_check`'s naming check. */
|
|
108
|
+
earlier(current: number | undefined): UxAction[];
|
|
109
|
+
/** A `ux_check` was made: keep its actions, and a picture per finding with the element boxed in red. */
|
|
110
|
+
uxSeen(screen: number | undefined, title: string, actions: UxAction[], findings: UxFindingSummary[]): Promise<void>;
|
|
111
|
+
/** Everything asked for is on disk. */
|
|
112
|
+
done(): Promise<void>;
|
|
113
|
+
}
|
|
114
|
+
export type Severity = "high" | "medium" | "low";
|
|
115
|
+
export interface Finding {
|
|
116
|
+
severity: Severity;
|
|
117
|
+
kind: string;
|
|
118
|
+
/** One line: what is wrong. */
|
|
119
|
+
title: string;
|
|
120
|
+
screen: number | undefined;
|
|
121
|
+
basis: string;
|
|
122
|
+
/** Relative path of the marked-up picture. */
|
|
123
|
+
shot?: string;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Simple rules, written in the report's footer. A `ux_check` kind not named here is low.
|
|
127
|
+
*/
|
|
128
|
+
export declare const SEVERITY_RULES: string[];
|
|
129
|
+
/** The findings of a finished journey, worst first. `steps` pictures are filled in by `markSteps`. */
|
|
130
|
+
export declare function buildFindings(state: JourneyState, seen: readonly SeenFinding[], maxTaps: number | undefined): Finding[];
|
|
131
|
+
export declare function countFindings(findings: readonly Finding[]): Record<Severity, number>;
|
|
132
|
+
/** `Findings: 2 high, 3 medium, 1 low.` or `Findings: none.` */
|
|
133
|
+
export declare function findingsLine(counts: Record<Severity, number>): string;
|
|
134
|
+
/** `2026-11-12-1530-log-breakfast-by-scanning-its-barcode`, in the computer's local time. */
|
|
135
|
+
export declare function reportFolderName(goal: string, at: number): string;
|
|
136
|
+
/** A folder name that doesn't exist yet in `root` (`-2`, `-3` when the same goal ran in the same minute). */
|
|
137
|
+
/** How many journey reports a host keeps in its journeys folder (1.5 review, finding 6). */
|
|
138
|
+
export declare const JOURNEYS_KEPT = 20;
|
|
139
|
+
/**
|
|
140
|
+
* Keeps the newest `keep` report folders in `root` (by when they were last changed) and removes the rest. Only folders
|
|
141
|
+
* that hold a report or frames (what a journey writes) are counted or removed; anything else in `root` is left alone.
|
|
142
|
+
*/
|
|
143
|
+
export declare function pruneJourneys(root: string, keep: number): number;
|
|
144
|
+
export declare function freshFolder(root: string, goal: string, at: number): string;
|
|
145
|
+
export interface ReportResult {
|
|
146
|
+
folder: string;
|
|
147
|
+
name: string;
|
|
148
|
+
findings: Finding[];
|
|
149
|
+
counts: Record<Severity, number>;
|
|
150
|
+
/** Files of the folder, relative, for the export. */
|
|
151
|
+
files: string[];
|
|
152
|
+
/** What an export uploads: the report, the GIF and the pictures the report uses; not every raw frame. */
|
|
153
|
+
exportFiles: string[];
|
|
154
|
+
/** The first picture of the path, for an export's comment when there is no GIF. */
|
|
155
|
+
firstPicture: string | undefined;
|
|
156
|
+
/** Findings beyond the 100 the report lists. */
|
|
157
|
+
omitted: number;
|
|
158
|
+
gif: {
|
|
159
|
+
frames: number;
|
|
160
|
+
bytes: number;
|
|
161
|
+
dropped: number;
|
|
162
|
+
} | null;
|
|
163
|
+
/** The key pictures, relative: where the journey ended, and the worst finding. */
|
|
164
|
+
keys: string[];
|
|
165
|
+
notes: string[];
|
|
166
|
+
}
|
|
167
|
+
/** At most this many pictures are decoded for the GIF (each is about 1 MB as pixels): 1.5 review, finding 6. */
|
|
168
|
+
export declare const GIF_MAX_SOURCE_FRAMES = 150;
|
|
169
|
+
/**
|
|
170
|
+
* The frames the GIF is made of, at most `max`: every tap and every first picture of a screen stays (they tell the
|
|
171
|
+
* story); the other, still pictures are thinned evenly. Chosen before anything is decoded.
|
|
172
|
+
*/
|
|
173
|
+
export declare function framesForGif(frames: readonly FrameRecord[], max?: number): FrameRecord[];
|
|
174
|
+
/**
|
|
175
|
+
* The report's own folder, in whatever slash style a step's text carries it, replaced by `<report>`: an export
|
|
176
|
+
* must not publish this computer's user name and folders.
|
|
177
|
+
*/
|
|
178
|
+
export declare function scrubFolder(text: string, folder: string): string;
|
|
179
|
+
export interface Ctx {
|
|
180
|
+
state: JourneyState;
|
|
181
|
+
rec: JourneyRecorder;
|
|
182
|
+
findings: Finding[];
|
|
183
|
+
counts: Record<Severity, number>;
|
|
184
|
+
/** Findings past the cap, not listed. */
|
|
185
|
+
omitted: number;
|
|
186
|
+
gif: ReportResult["gif"];
|
|
187
|
+
maxTaps: number | undefined;
|
|
188
|
+
textRules: readonly string[];
|
|
189
|
+
}
|
|
190
|
+
export declare function renderMarkdown(ctx: Ctx): string;
|
|
191
|
+
export declare function renderHtml(ctx: Ctx): string;
|
|
192
|
+
export interface WriteOptions {
|
|
193
|
+
state: JourneyState;
|
|
194
|
+
rec: JourneyRecorder;
|
|
195
|
+
maxTaps: number | undefined;
|
|
196
|
+
textRules?: readonly string[];
|
|
197
|
+
gifMaxBytes?: number;
|
|
198
|
+
}
|
|
199
|
+
/** Writes the report folder: waits for the pictures asked for, then the findings, GIF, key pictures, `report.md` and `report.html`. */
|
|
200
|
+
export declare function writeReport(options: WriteOptions): Promise<ReportResult>;
|
|
201
|
+
/** A picture inside the report folder: a relative path of plain names, no `..`, ending .png, .jpg or .gif. */
|
|
202
|
+
export declare function isReportPicture(path: string): boolean;
|