@qaick/runner 0.31.2 → 0.32.1
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-dist/package.json +9 -5
- package/package-dist/src/ScreenshotButton.d.ts +5 -0
- package/package-dist/src/ScreenshotButton.d.ts.map +1 -0
- package/package-dist/src/ScreenshotButton.js +54 -0
- package/package-dist/src/ScreenshotButton.js.map +1 -0
- package/package-dist/src/ThemeToggle.d.ts +6 -0
- package/package-dist/src/ThemeToggle.d.ts.map +1 -0
- package/package-dist/src/ThemeToggle.js +6 -0
- package/package-dist/src/ThemeToggle.js.map +1 -0
- package/package-dist/src/runner/HeaderHelp.d.ts +7 -0
- package/package-dist/src/runner/HeaderHelp.d.ts.map +1 -0
- package/package-dist/src/runner/HeaderHelp.js +33 -0
- package/package-dist/src/runner/HeaderHelp.js.map +1 -0
- package/package-dist/src/runner/RunnerModule.d.ts +16 -1
- package/package-dist/src/runner/RunnerModule.d.ts.map +1 -1
- package/package-dist/src/runner/RunnerModule.js +67 -15
- package/package-dist/src/runner/RunnerModule.js.map +1 -1
- package/package-dist/src/runner/catalog.d.ts +6 -0
- package/package-dist/src/runner/catalog.d.ts.map +1 -1
- package/package-dist/src/runner/catalog.js +38 -8
- package/package-dist/src/runner/catalog.js.map +1 -1
- package/package-dist/src/runner/loaded-target.d.ts +13 -2
- package/package-dist/src/runner/loaded-target.d.ts.map +1 -1
- package/package-dist/src/runner/loaded-target.js +20 -4
- package/package-dist/src/runner/loaded-target.js.map +1 -1
- package/package-dist/src/runner/studio-hierarchy-loader.d.ts.map +1 -1
- package/package-dist/src/runner/studio-hierarchy-loader.js +17 -3
- package/package-dist/src/runner/studio-hierarchy-loader.js.map +1 -1
- package/package-dist/src/shared/application-menu.d.ts +63 -0
- package/package-dist/src/shared/application-menu.d.ts.map +1 -0
- package/package-dist/src/shared/application-menu.js +16 -0
- package/package-dist/src/shared/application-menu.js.map +1 -0
- package/package-dist/styles.css +217 -73
- package/package.json +9 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qaick/runner",
|
|
3
3
|
"description": "Reusable QAick Runner module and standalone execution application.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.32.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"package-dist",
|
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
"./studio-artifacts": {
|
|
32
32
|
"types": "./package-dist/src/runner/studio-artifact-loader.d.ts",
|
|
33
33
|
"import": "./package-dist/src/runner/studio-artifact-loader.js"
|
|
34
|
+
},
|
|
35
|
+
"./application-menu": {
|
|
36
|
+
"types": "./package-dist/src/shared/application-menu.d.ts",
|
|
37
|
+
"import": "./package-dist/src/shared/application-menu.js"
|
|
34
38
|
}
|
|
35
39
|
},
|
|
36
40
|
"scripts": {
|
|
@@ -58,10 +62,10 @@
|
|
|
58
62
|
"publish:release": "npm run check:package && npm publish --tag latest"
|
|
59
63
|
},
|
|
60
64
|
"dependencies": {
|
|
61
|
-
"@qaick/contract": "0.
|
|
62
|
-
"@qaick/executor": "0.2.
|
|
63
|
-
"@qaick/http-executor": "0.2.
|
|
64
|
-
"@qaick/runtime": "0.2.
|
|
65
|
+
"@qaick/contract": "0.5.0",
|
|
66
|
+
"@qaick/executor": "0.2.6",
|
|
67
|
+
"@qaick/http-executor": "0.2.6",
|
|
68
|
+
"@qaick/runtime": "0.2.7",
|
|
65
69
|
"zustand": "^5.0.15"
|
|
66
70
|
},
|
|
67
71
|
"peerDependencies": {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScreenshotButton.d.ts","sourceRoot":"","sources":["../../src/ScreenshotButton.tsx"],"names":[],"mappings":"AA0BA,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAMpD;AAED,wBAAgB,gBAAgB,CAAC,EAAC,SAAS,EAAC,EAAE;IAAC,SAAS,EAAC,MAAM,CAAA;CAAC,+BAe/D"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
async function captureBrowserView() {
|
|
4
|
+
if (!navigator.mediaDevices?.getDisplayMedia)
|
|
5
|
+
throw new Error('Screen capture is unavailable in this browser.');
|
|
6
|
+
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false, preferCurrentTab: true });
|
|
7
|
+
const video = document.createElement('video');
|
|
8
|
+
try {
|
|
9
|
+
video.muted = true;
|
|
10
|
+
video.srcObject = stream;
|
|
11
|
+
await video.play();
|
|
12
|
+
if (!video.videoWidth || !video.videoHeight)
|
|
13
|
+
throw new Error('The selected view could not be captured.');
|
|
14
|
+
const canvas = document.createElement('canvas');
|
|
15
|
+
canvas.width = video.videoWidth;
|
|
16
|
+
canvas.height = video.videoHeight;
|
|
17
|
+
const context = canvas.getContext('2d');
|
|
18
|
+
if (!context)
|
|
19
|
+
throw new Error('Screenshot rendering is unavailable.');
|
|
20
|
+
context.drawImage(video, 0, 0);
|
|
21
|
+
return await new Promise((resolve, reject) => canvas.toBlob(blob => blob ? resolve(blob) : reject(new Error('Screenshot could not be created.')), 'image/png'));
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
stream.getTracks().forEach(track => track.stop());
|
|
25
|
+
video.srcObject = null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export async function copyScreenshot() {
|
|
29
|
+
const desktop = window.qaickScreenshot;
|
|
30
|
+
if (desktop)
|
|
31
|
+
return desktop.copy();
|
|
32
|
+
if (!navigator.clipboard?.write || typeof ClipboardItem === 'undefined')
|
|
33
|
+
throw new Error('Image clipboard access is unavailable in this browser.');
|
|
34
|
+
// Start clipboard access in the click gesture; resolve its PNG after capture permission.
|
|
35
|
+
await navigator.clipboard.write([new ClipboardItem({ 'image/png': captureBrowserView() })]);
|
|
36
|
+
}
|
|
37
|
+
export function ScreenshotButton({ className }) {
|
|
38
|
+
const [state, setState] = useState('idle');
|
|
39
|
+
const [error, setError] = useState('');
|
|
40
|
+
async function copy() {
|
|
41
|
+
setError('');
|
|
42
|
+
setState('copying');
|
|
43
|
+
try {
|
|
44
|
+
await copyScreenshot();
|
|
45
|
+
setState('copied');
|
|
46
|
+
}
|
|
47
|
+
catch (cause) {
|
|
48
|
+
setState('idle');
|
|
49
|
+
setError(cause instanceof DOMException && cause.name === 'NotAllowedError' ? 'Screenshot cancelled or permission denied.' : cause instanceof Error ? cause.message : 'Screenshot could not be copied.');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return _jsxs("span", { style: { display: 'inline-flex', alignItems: 'center', gap: 6 }, children: [_jsx("button", { type: "button", className: className, onClick: () => void copy(), disabled: state === 'copying', title: "Copy the visible application view as an image. In a browser, select the current QAick tab.", "aria-label": "Copy screenshot", children: state === 'copying' ? 'Copying…' : state === 'copied' ? 'Copied!' : 'Copy screenshot' }), _jsx("span", { role: "status", style: { fontSize: 11, maxWidth: 200 }, children: error })] });
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=ScreenshotButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScreenshotButton.js","sourceRoot":"","sources":["../../src/ScreenshotButton.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,OAAO,CAAC;AAI/B,KAAK,UAAU,kBAAkB;IAC/B,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,eAAe;QAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IAChH,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,eAAe,CAAC,EAAC,KAAK,EAAC,IAAI,EAAC,KAAK,EAAC,KAAK,EAAC,gBAAgB,EAAC,IAAI,EAA8B,CAAC,CAAC;IACzI,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9C,IAAI,CAAC;QACH,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC;QACnB,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC;QACzB,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,IAAI,CAAC,KAAK,CAAC,UAAU,IAAI,CAAC,KAAK,CAAC,WAAW;YAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QACzG,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC;QAChC,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC;QAClC,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QACtE,OAAO,CAAC,SAAS,CAAC,KAAK,EAAC,CAAC,EAAC,CAAC,CAAC,CAAC;QAC7B,OAAO,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAC,MAAM,EAAC,EAAE,CAAA,MAAM,CAAC,MAAM,CAAC,IAAI,CAAA,EAAE,CAAA,IAAI,CAAA,CAAC,CAAA,OAAO,CAAC,IAAI,CAAC,CAAA,CAAC,CAAA,MAAM,CAAC,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC,EAAC,WAAW,CAAC,CAAC,CAAC;IAC9J,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,KAAK,CAAA,EAAE,CAAA,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QAChD,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC;IACzB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,OAAO,GAAI,MAA2B,CAAC,eAAe,CAAC;IAC7D,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,KAAK,IAAI,OAAO,aAAa,KAAK,WAAW;QAAE,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IACnJ,yFAAyF;IACzF,MAAM,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,aAAa,CAAC,EAAC,WAAW,EAAC,kBAAkB,EAAE,EAAC,CAAC,CAAC,CAAC,CAAC;AAC3F,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAC,SAAS,EAAqB;IAC9D,MAAM,CAAC,KAAK,EAAC,QAAQ,CAAC,GAAG,QAAQ,CAA4B,MAAM,CAAC,CAAC;IACrE,MAAM,CAAC,KAAK,EAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACtC,KAAK,UAAU,IAAI;QACjB,QAAQ,CAAC,EAAE,CAAC,CAAC;QAAA,QAAQ,CAAC,SAAS,CAAC,CAAC;QACjC,IAAI,CAAC;YAAA,MAAM,cAAc,EAAE,CAAC;YAAA,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAAA,CAAC;QAChD,OAAM,KAAK,EAAE,CAAC;YACZ,QAAQ,CAAC,MAAM,CAAC,CAAC;YACjB,QAAQ,CAAC,KAAK,YAAY,YAAY,IAAI,KAAK,CAAC,IAAI,KAAG,iBAAiB,CAAC,CAAC,CAAC,4CAA4C,CAAC,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC;QACxM,CAAC;IACH,CAAC;IACD,OAAO,gBAAM,KAAK,EAAE,EAAC,OAAO,EAAC,aAAa,EAAC,UAAU,EAAC,QAAQ,EAAC,GAAG,EAAC,CAAC,EAAC,aACnE,iBAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,GAAE,EAAE,CAAA,KAAK,IAAI,EAAE,EAAE,QAAQ,EAAE,KAAK,KAAG,SAAS,EAAE,KAAK,EAAC,4FAA4F,gBAAY,iBAAiB,YAAE,KAAK,KAAG,SAAS,CAAA,CAAC,CAAA,UAAU,CAAA,CAAC,CAAA,KAAK,KAAG,QAAQ,CAAA,CAAC,CAAA,SAAS,CAAA,CAAC,CAAA,iBAAiB,GAAU,EACvT,eAAM,IAAI,EAAC,QAAQ,EAAC,KAAK,EAAE,EAAC,QAAQ,EAAC,EAAE,EAAC,QAAQ,EAAC,GAAG,EAAC,YAAG,KAAK,GAAQ,IAChE,CAAC;AACV,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThemeToggle.d.ts","sourceRoot":"","sources":["../../src/ThemeToggle.tsx"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,EAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,EAAE;IAAC,KAAK,EAAC,OAAO,GAAC,MAAM,CAAC;IAAA,QAAQ,EAAC,CAAC,KAAK,EAAC,OAAO,GAAC,MAAM,KAAG,IAAI,CAAC;IAAA,SAAS,EAAC,MAAM,CAAA;CAAC,+BAGpI"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
export function ThemeToggle({ theme, onChange, className }) {
|
|
3
|
+
const next = theme === 'dark' ? 'light' : 'dark';
|
|
4
|
+
return _jsx("button", { type: "button", className: className, "aria-label": `Switch to ${next} theme`, title: `Switch to ${next} theme`, onClick: () => onChange(next), children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: theme === 'dark' ? _jsxs(_Fragment, { children: [_jsx("circle", { cx: "12", cy: "12", r: "4" }), _jsx("path", { d: "M12 2v2m0 16v2M2 12h2m16 0h2M5 5l1.5 1.5m11 11L19 19M5 19l1.5-1.5m11-11L19 5" })] }) : _jsx("path", { d: "M20.5 14A8.5 8.5 0 0 1 10 3.5 8.5 8.5 0 1 0 20.5 14Z" }) }) });
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=ThemeToggle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThemeToggle.js","sourceRoot":"","sources":["../../src/ThemeToggle.tsx"],"names":[],"mappings":";AAAA,MAAM,UAAU,WAAW,CAAC,EAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAgF;IACpI,MAAM,IAAI,GAAC,KAAK,KAAG,MAAM,CAAA,CAAC,CAAA,OAAO,CAAA,CAAC,CAAA,MAAM,CAAC;IACzC,OAAO,iBAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAE,SAAS,gBAAc,aAAa,IAAI,QAAQ,EAAE,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,OAAO,EAAE,GAAE,EAAE,CAAA,QAAQ,CAAC,IAAI,CAAC,YAAE,cAAK,KAAK,EAAC,IAAI,EAAC,MAAM,EAAC,IAAI,EAAC,OAAO,EAAC,WAAW,EAAC,IAAI,EAAC,MAAM,EAAC,MAAM,EAAC,cAAc,EAAC,WAAW,EAAC,KAAK,EAAC,aAAa,EAAC,OAAO,EAAC,cAAc,EAAC,OAAO,iBAAa,MAAM,YAAE,KAAK,KAAG,MAAM,CAAA,CAAC,CAAA,8BAAE,iBAAQ,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,EAAC,CAAC,EAAC,GAAG,GAAE,EAAA,eAAM,CAAC,EAAC,8EAA8E,GAAE,IAAG,CAAA,CAAC,CAAA,eAAM,CAAC,EAAC,sDAAsD,GAAE,GAAO,GAAS,CAAC;AAC5hB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type CommandId } from '../shared/application-menu.js';
|
|
2
|
+
export declare function HeaderHelp({ onAbout, onOpenProject, enabled }: {
|
|
3
|
+
onAbout: () => void;
|
|
4
|
+
onOpenProject: () => void;
|
|
5
|
+
enabled?: (id: CommandId) => boolean;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=HeaderHelp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HeaderHelp.d.ts","sourceRoot":"","sources":["../../../src/runner/HeaderHelp.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,+BAA4B,CAAC;AAoBvF,wBAAgB,UAAU,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,OAAoB,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,IAAI,CAAC;IAAC,aAAa,EAAE,MAAM,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,OAAO,CAAA;CAAE,+BAOpK"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { runnerMenus, commandLinks } from "../shared/application-menu.js";
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
function HeaderMenu({ label, children }) {
|
|
5
|
+
const [open, setOpen] = useState(false);
|
|
6
|
+
const root = useRef(null);
|
|
7
|
+
const trigger = useRef(null);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (!open)
|
|
10
|
+
return;
|
|
11
|
+
const close = (event) => { if (event.target instanceof Node && !root.current?.contains(event.target))
|
|
12
|
+
setOpen(false); };
|
|
13
|
+
const key = (event) => { if (event.key === 'Escape') {
|
|
14
|
+
setOpen(false);
|
|
15
|
+
trigger.current?.focus();
|
|
16
|
+
} };
|
|
17
|
+
window.addEventListener('pointerdown', close);
|
|
18
|
+
window.addEventListener('keydown', key);
|
|
19
|
+
return () => { window.removeEventListener('pointerdown', close); window.removeEventListener('keydown', key); };
|
|
20
|
+
}, [open]);
|
|
21
|
+
return _jsxs("div", { ref: root, className: "runner-help", children: [_jsx("button", { type: "button", ref: trigger, className: "about-button", "aria-haspopup": "menu", "aria-expanded": open, onClick: () => setOpen(!open), children: label }), open && _jsx("div", { role: "menu", "aria-label": label, className: "runner-help-menu", onClick: () => setOpen(false), children: children })] });
|
|
22
|
+
}
|
|
23
|
+
export function HeaderHelp({ onAbout, onOpenProject, enabled = () => true }) {
|
|
24
|
+
const execute = (id) => { if (id === 'project.open')
|
|
25
|
+
onOpenProject();
|
|
26
|
+
else if (id === 'help.about')
|
|
27
|
+
onAbout(); };
|
|
28
|
+
return _jsx(_Fragment, { children: runnerMenus.map(menu => _jsx(HeaderMenu, { label: menu.label, children: menu.items.map(([id, label]) => id === 'help.demo'
|
|
29
|
+
? _jsx("a", { href: commandLinks[id], "aria-disabled": !enabled(id), onClick: event => { if (!enabled(id))
|
|
30
|
+
event.preventDefault(); }, children: label }, id)
|
|
31
|
+
: _jsx("button", { type: "button", disabled: !enabled(id), onClick: () => execute(id), children: label }, id)) }, menu.label)) });
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=HeaderHelp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HeaderHelp.js","sourceRoot":"","sources":["../../../src/runner/HeaderHelp.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,27 @@
|
|
|
1
|
+
import { type CommandId } from '../shared/application-menu.js';
|
|
1
2
|
import type { HierarchyAddress } from '@qaick/contract';
|
|
2
3
|
import type { ExecutionPackage, InvocationState, StepResult } from './types.js';
|
|
3
4
|
import type { RunnerRuntimeClient } from './runtime-client.js';
|
|
5
|
+
export interface RunnerMenuActions {
|
|
6
|
+
enabled(id: CommandId): boolean;
|
|
7
|
+
openProject(file: {
|
|
8
|
+
name: string;
|
|
9
|
+
text: string;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
about(): void;
|
|
12
|
+
demo(): void;
|
|
13
|
+
}
|
|
14
|
+
export interface RunnerApplicationMenu {
|
|
15
|
+
native: boolean;
|
|
16
|
+
register(actions: RunnerMenuActions): () => void;
|
|
17
|
+
}
|
|
4
18
|
export interface RunnerModuleProps {
|
|
19
|
+
applicationMenu?: RunnerApplicationMenu;
|
|
5
20
|
client: RunnerRuntimeClient;
|
|
6
21
|
showDemo?: boolean;
|
|
7
22
|
onExit?: () => void;
|
|
8
23
|
}
|
|
9
|
-
export declare function RunnerModule({ client, showDemo, onExit }: RunnerModuleProps): import("react").JSX.Element;
|
|
24
|
+
export declare function RunnerModule({ client, showDemo, onExit, applicationMenu }: RunnerModuleProps): import("react").JSX.Element;
|
|
10
25
|
type DisplayStep = {
|
|
11
26
|
structuralAddress?: HierarchyAddress;
|
|
12
27
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RunnerModule.d.ts","sourceRoot":"","sources":["../../../src/runner/RunnerModule.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RunnerModule.d.ts","sourceRoot":"","sources":["../../../src/runner/RunnerModule.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,+BAA4B,CAAC;AAOvF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iBAAiB,CAAC;AAQtD,OAAO,KAAK,EAAkB,gBAAgB,EAAmB,eAAe,EAAE,UAAU,EAAE,MAAM,YAAS,CAAC;AAC9G,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAkB,CAAC;AAkB5D,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,CAAC;IAChC,WAAW,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,KAAK,IAAI,IAAI,CAAC;IACd,IAAI,IAAI,IAAI,CAAC;CACd;AACD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAAC;CAClD;AACD,MAAM,WAAW,iBAAiB;IAChC,eAAe,CAAC,EAAE,qBAAqB,CAAC;IACxC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AASD,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,QAAoC,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,iBAAiB,+BAExH;AAoKD,KAAK,WAAW,GAAG;IAAE,iBAAiB,CAAC,EAAC,gBAAgB,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAC,OAAO,CAAC;IAAC,YAAY,CAAC,EAAC,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAC,gBAAgB,CAAC;IAAC,cAAc,CAAC,EAAC,MAAM,CAAA;CAAE,CAAC;AAkBvW,wBAAgB,yBAAyB,CAAC,KAAK,EAAC,WAAW,EAAE,EAAC,QAAQ,EAAC,UAAU,EAAE,GAAE,WAAW,EAAE,CAAe;AACjH,eAAO,MAAM,aAAa,GAAE,UAAS,gBAAgB,WAA8D,CAAC;AACpH,wBAAgB,oBAAoB,CAAC,OAAO,EAAC,UAAU,EAAE,EAAC,OAAO,CAAC,EAAC,gBAAgB,GAAE,UAAU,EAAE,CAAkG;AACnM,wBAAgB,mBAAmB,CAAC,OAAO,EAAC,UAAU,EAAE,EAAC,OAAO,CAAC,EAAC,gBAAgB,GAAE,UAAU,GAAC,SAAS,CAAsG;AAC9M,wBAAgB,kBAAkB,CAAC,OAAO,EAAC,UAAU,EAAE,EAAC,OAAO,CAAC,EAAC,gBAAgB,GAAE,eAAe,GAAC,SAAS,CAS3G;AAID,wBAAgB,mBAAmB,CAAC,EAAC,OAAO,EAAC,UAAU,EAAC,EAAC;IAAC,OAAO,EAAC,UAAU,EAAE,CAAC;IAAA,UAAU,CAAC,EAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAA;CAAC,+BAK5H"}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { commandLinks, runnerMenus } from "../shared/application-menu.js";
|
|
3
|
+
import { ThemeToggle } from "../ThemeToggle.js";
|
|
4
|
+
import { HeaderHelp } from "./HeaderHelp.js";
|
|
5
|
+
import { ScreenshotButton } from "../ScreenshotButton.js";
|
|
2
6
|
import { invocationBranches } from "./invocation-branches.js";
|
|
3
7
|
import { loopPresentation } from "./loop-presentation.js";
|
|
4
8
|
import { presentationState, eventPresentationEvidence } from "./presentation-state.js";
|
|
@@ -50,15 +54,17 @@ packageCatalog.addPackage(checkoutProjectPackage);
|
|
|
50
54
|
packageCatalog.addPackage(checkoutNestedPackage);
|
|
51
55
|
const restoredImportedPackage = readImportedPackage();
|
|
52
56
|
if (restoredImportedPackage)
|
|
53
|
-
packageCatalog.
|
|
54
|
-
export function RunnerModule({ client, showDemo = standaloneDemoRequested(), onExit }) {
|
|
55
|
-
return showDemo ? _jsx(RunnerWalkthrough, {}) : _jsx(RunnerApp, { client: client, onExit: onExit });
|
|
57
|
+
packageCatalog.addLoadedTarget(restoredImportedPackage);
|
|
58
|
+
export function RunnerModule({ client, showDemo = standaloneDemoRequested(), onExit, applicationMenu }) {
|
|
59
|
+
return showDemo ? _jsx(RunnerWalkthrough, {}) : _jsx(RunnerApp, { client: client, onExit: onExit, applicationMenu: applicationMenu });
|
|
56
60
|
}
|
|
57
61
|
function standaloneDemoRequested() { return typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('demo'); }
|
|
58
|
-
function RunnerApp({ client, onExit }) {
|
|
62
|
+
function RunnerApp({ client, onExit, applicationMenu }) {
|
|
59
63
|
const [theme, setTheme] = useState(readTheme);
|
|
60
64
|
const [showAbout, setShowAbout] = useState(false);
|
|
61
65
|
const [environment, setEnvironment] = useState('test');
|
|
66
|
+
const [selectedTargetId, setSelectedTargetId] = useState(restoredImportedPackage?.executionPackage?.packageId ?? restoredImportedPackage?.authoringSource?.targets[0]?.packageId ?? checkoutPackage.packageId);
|
|
67
|
+
const selectedTarget = packageCatalog.targets().find(t => t.packageId === selectedTargetId);
|
|
62
68
|
const [executionPackage, setExecutionPackage] = useState(restoredImportedPackage?.executionPackage ?? checkoutPackage);
|
|
63
69
|
const [inputEdits, setInputEdits] = useState(() => inputDrafts(inspectDeclaredInputs(restoredImportedPackage?.executionPackage ?? checkoutPackage).declarations));
|
|
64
70
|
const { declarations, conflict, unresolved } = inspectDeclaredInputs(executionPackage);
|
|
@@ -102,9 +108,27 @@ function RunnerApp({ client, onExit }) {
|
|
|
102
108
|
const displayResults = displaySteps.map(step => resultForOccurrence(allResults, step.structuralAddress));
|
|
103
109
|
const selectedInvocations = resultsForOccurrence(allResults, displaySteps[selectedStep]?.structuralAddress);
|
|
104
110
|
const visibleEvents = events.filter(event => { const categoryMatches = eventFilter === 'all' || eventFilter === 'parallel' && event.parallel || eventFilter === 'steps' && event.stepId || eventFilter === 'lifecycle' && !event.stepId && !event.parallel || eventFilter === 'failures' && (event.state === 'failed' || event.type.includes('error')); const searchMatches = !eventSearch.trim() || `${event.type} ${event.state} ${event.message} ${event.stepName ?? ''}`.toLowerCase().includes(eventSearch.trim().toLowerCase()); return categoryMatches && searchMatches; });
|
|
111
|
+
function selectTarget(id) { setSelectedTargetId(id); const descriptor = packageCatalog.targets().find(t => t.packageId === id); if (descriptor?.issues?.length) {
|
|
112
|
+
setRequestId(undefined);
|
|
113
|
+
setViewingHistoryEntry(undefined);
|
|
114
|
+
setControlState("idle");
|
|
115
|
+
setRunState("ready");
|
|
116
|
+
return;
|
|
117
|
+
} const next = packageCatalog.get(id); setExecutionPackage(next); if (!inspectDeclaredInputs(next).conflict && !inspectDeclaredInputs(next).unresolved)
|
|
118
|
+
setInputEdits(inputDrafts(inspectDeclaredInputs(next).declarations)); setBreakpoints(new Set()); breakpointsRef.current = new Set(); setStopAfterStepId(undefined); setLoadError(undefined); setLoadIssues([]); setRuntimeValues(Object.fromEntries((next.runtimeRequirements ?? []).map(name => [name, '']))); setStepStates((next.target.kind === 'project' ? next.flows?.flatMap(flow => flattenDisplaySteps(flow.steps)) ?? [] : flattenDisplaySteps(next.steps)).map(() => 'pending')); setSelectedStep(0); setResult(undefined); setEvents([]); setRequestId(undefined); setViewingHistoryEntry(undefined); setControlState('idle'); setRunState('ready'); }
|
|
105
119
|
async function run(dryRun = false) {
|
|
106
120
|
if (inputBlocker || inputError)
|
|
107
121
|
return;
|
|
122
|
+
let currentPackage;
|
|
123
|
+
try {
|
|
124
|
+
if (selectedTarget?.issues?.length)
|
|
125
|
+
throw new Error(selectedTarget.issues.join("\n"));
|
|
126
|
+
currentPackage = packageCatalog.prepare(executionPackage);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
setLoadError(error instanceof Error ? error.message : "Source target cannot execute.");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
108
132
|
const id = crypto.randomUUID();
|
|
109
133
|
setViewingHistoryEntry(undefined);
|
|
110
134
|
setRequestId(id);
|
|
@@ -114,7 +138,7 @@ function RunnerApp({ client, onExit }) {
|
|
|
114
138
|
setStepStates(displaySteps.map(() => 'pending'));
|
|
115
139
|
setRunState('preflighting');
|
|
116
140
|
setControlState('running');
|
|
117
|
-
await client.submit({ requestId: id, executionPackage, inputs, environment, runtimeValues, debugSession: { breakpoints: [...breakpointsRef.current], ...(stopAfterStepId ? { stopAfterStepId } : {}) }, options: { ...(timeoutMs === 'none' ? {} : { timeoutMs: Number(timeoutMs) }), dryRun } });
|
|
141
|
+
await client.submit({ requestId: id, executionPackage: currentPackage, inputs, environment, runtimeValues, debugSession: { breakpoints: [...breakpointsRef.current], ...(stopAfterStepId ? { stopAfterStepId } : {}) }, options: { ...(timeoutMs === 'none' ? {} : { timeoutMs: Number(timeoutMs) }), dryRun } });
|
|
118
142
|
void (async () => {
|
|
119
143
|
for await (const event of client.events(id)) {
|
|
120
144
|
setEvents(previous => [...previous, redactExecutionEvent(event, collectSecretValues(inputs, runtimeValues))]);
|
|
@@ -171,6 +195,7 @@ function RunnerApp({ client, onExit }) {
|
|
|
171
195
|
try {
|
|
172
196
|
loadedPackage = packageCatalog.get(entry.packageId);
|
|
173
197
|
setExecutionPackage(loadedPackage);
|
|
198
|
+
setSelectedTargetId(loadedPackage.packageId);
|
|
174
199
|
}
|
|
175
200
|
catch { /* The result remains viewable when its package is no longer loaded. */ }
|
|
176
201
|
} const steps = loadedPackage.target.kind === 'project' ? loadedPackage.flows?.flatMap(flow => flow.steps) ?? [] : loadedPackage.steps; const storedSteps = flattenResultSteps(entry.result.children?.flatMap(child => child.steps) ?? entry.result.steps); const states = new Map(flattenDisplaySteps(steps).map(step => [occurrenceKey(step.structuralAddress), stateForOccurrence(storedSteps, step.structuralAddress) ?? 'pending'])); setRequestId(entry.requestId); setEnvironment(entry.environment); setResult(entry.result); setViewingHistoryEntry(entry.requestId); setEvents([]); setSelectedStep(0); setStepStates(flattenDisplaySteps(steps).map(step => states.get(occurrenceKey(step.structuralAddress)) ?? 'pending')); setRunState(entry.state); document.querySelector('.runner-workspace')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); }
|
|
@@ -190,7 +215,19 @@ function RunnerApp({ client, onExit }) {
|
|
|
190
215
|
try {
|
|
191
216
|
const target = loadTarget(await file.text());
|
|
192
217
|
const loaded = target.executionPackage;
|
|
193
|
-
packageCatalog.
|
|
218
|
+
const selected = packageCatalog.addLoadedTarget(target);
|
|
219
|
+
setSelectedTargetId(selected);
|
|
220
|
+
if (!loaded) {
|
|
221
|
+
localStorage.setItem(IMPORTED_PACKAGE_KEY, JSON.stringify(target));
|
|
222
|
+
setCatalogVersion(v => v + 1);
|
|
223
|
+
setRequestId(undefined);
|
|
224
|
+
setViewingHistoryEntry(undefined);
|
|
225
|
+
setControlState("idle");
|
|
226
|
+
setRunState("ready");
|
|
227
|
+
setLoadError(undefined);
|
|
228
|
+
setLoadIssues([]);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
194
231
|
localStorage.setItem(IMPORTED_PACKAGE_KEY, JSON.stringify(target));
|
|
195
232
|
setCatalogVersion(version => version + 1);
|
|
196
233
|
setExecutionPackage(loaded);
|
|
@@ -232,17 +269,32 @@ function RunnerApp({ client, onExit }) {
|
|
|
232
269
|
close(); }; document.addEventListener('click', close); document.addEventListener('keydown', keydown); return () => { document.removeEventListener('click', close); document.removeEventListener('keydown', keydown); }; }, []);
|
|
233
270
|
useEffect(() => { const navigateIssue = (event) => { const target = event.target; const issue = target.closest('.validation-issue'); const stepId = issue?.dataset.stepId; const eventRow = target.closest('.event-row'); const index = stepId ? displaySteps.findIndex(step => step.id === stepId || step.id.endsWith(`.${stepId}`)) : eventRow ? displaySteps.findIndex(step => eventRow.textContent?.includes(step.name)) : -1; if (index >= 0)
|
|
234
271
|
setSelectedStep(index); }; document.addEventListener('click', navigateIssue); return () => document.removeEventListener('click', navigateIssue); }, [displaySteps]);
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
} }, onClick: () => setSelectedStep(index), onContextMenu: event => { event.preventDefault(); setSelectedStep(index); setContextMenu({ x: event.clientX, y: event.clientY, stepId: step.id }); }, children: [_jsx("td", { children: index + 1 }), _jsxs("td", { children: [_jsx("span", { className: `step-icon ${isLoop ? 'loop-icon' : displayState}`, children: isLoop ? '↻' : displayState === 'finished' ? '✓' : displayState === 'waiting' ? '◷' : displayState === 'skipped' ? '–' : '' }), _jsxs("span", { className: "hierarchy-name", style: { paddingLeft: `${step.depth * 18}px` }, children: [step.depth > 0 && _jsx("span", { className: "branch-marker", children: "\u2514" }), step.hasChildren && _jsx("button", { className: "collapse-toggle", "aria-label": `${collapsedSteps.has(step.id) ? 'Expand' : 'Collapse'} ${step.name}`, onClick: event => { event.stopPropagation(); toggleStep(step.id); }, children: collapsedSteps.has(step.id) ? '+' : '−' }), _jsx("span", { children: step.name }), breakpoints.has(step.id) && _jsx("small", { className: "breakpoint-marker", "aria-label": "Breakpoint armed", title: "Breakpoint armed", children: "STOP" }), isLoop && _jsx("small", { className: "kind-label loop-label", children: "LOOP" }), step.depth > 0 ? _jsx("small", { className: "kind-label nested-label", children: "NESTED STEP" }) : !isLoop && _jsx("small", { className: "kind-label", children: step.kind.toUpperCase() })] }), selectedStep === index && _jsx("small", { className: "selected-label", children: "SELECTED" })] }), _jsxs("td", { children: [loopContexts.map(context => _jsxs("div", { className: "loop-progress", children: [context.context.length > 0 && _jsxs("small", { children: ["Enclosing iterations: ", context.context.map(s => s.iteration).join(' / ')] }), _jsx("strong", { children: context.label }), _jsx("small", { children: context.state.toUpperCase() })] }, JSON.stringify(context.context))), _jsx("span", { className: `status ${displayState}`, children: displayState.toUpperCase() }), latestQualifier && _jsx("small", { className: "latest-invocation", children: "Latest invocation" })] }), _jsx("td", { children: displayResults[index] ? new Date(result?.finishedAt ?? '').toLocaleTimeString() : '—' }), _jsx("td", { children: displayResults[index] ? `${displayResults[index].durationMs} ms` : '—' })] }, `${step.id}-${index}`); }) })] }), contextMenu && _jsxs("div", { className: "runner-context-menu", style: { left: contextMenu.x, top: contextMenu.y }, onClick: event => event.stopPropagation(), onContextMenu: event => event.preventDefault(), children: [_jsx("strong", { children: "Breakpoint" }), breakpoints.has(contextMenu.stepId) ? _jsx("button", { onClick: () => { toggleBreakpoint(contextMenu.stepId); setContextMenu(undefined); }, children: "Remove breakpoint" }) : _jsx("button", { onClick: () => { toggleBreakpoint(contextMenu.stepId); setContextMenu(undefined); }, children: "Add breakpoint" })] })] }), _jsxs("section", { className: "panel event-panel", children: [_jsxs("div", { className: "panel-heading", children: [_jsx("h2", { children: "Event Log" }), _jsx("span", { className: "panel-spacer" }), _jsx("input", { className: "event-search", "aria-label": "Search event log", placeholder: "Search events", value: eventSearch, onChange: event => setEventSearch(event.target.value) }), _jsxs("select", { className: "event-filter", value: eventFilter, onChange: event => setEventFilter(event.target.value), children: [_jsx("option", { value: "all", children: "All events" }), _jsx("option", { value: "steps", children: "Step events" }), _jsx("option", { value: "parallel", children: "Parallel groups" }), _jsx("option", { value: "lifecycle", children: "Lifecycle" }), _jsx("option", { value: "failures", children: "Failures" })] })] }), _jsx("table", { children: _jsx("tbody", { children: visibleEvents.map(event => _jsxs("tr", { className: "event-row", children: [_jsx("td", { children: new Date(event.timestamp).toLocaleTimeString() }), _jsx("td", { children: event.state.toUpperCase() }), _jsx("td", { children: _jsx("code", { children: event.type.toUpperCase() }) }), _jsxs("td", { children: [event.message, event.parallel && _jsxs("small", { className: "parallel-event", children: [" \u00B7 Parallel ", event.parallel.completed, "/", event.parallel.total, " (", event.parallel.join, ")"] })] })] }, event.sequence)) }) }), events.length > 0 && visibleEvents.length === 0 && _jsx("p", { className: "empty-state", children: "No events match the current filter." })] }), _jsx(HistoryPanel, { entries: runHistory, onReload: reloadHistoryEntry, retention: historyRetention, onRetentionChange: changeHistoryRetention, onClear: clearHistory, onDelete: deleteHistoryEntry, onExport: exportResult })] }), _jsxs("aside", { className: "inspector", children: [_jsx("h2", { children: "Selected execution" }), _jsx("p", { className: "selected-detail-name", children: _jsx("strong", { children: displaySteps[selectedStep]?.name ?? 'No step selected' }) }), _jsxs("p", { className: "run-id", children: ["Run ID: ", requestId ?? 'No run started'] }), _jsx(InspectorSection, { title: "Artifact summary", children: _jsx(KeyValues, { values: ["Target", executionPackage.target.name, "Type", executionPackage.target.kind, "Package", executionPackage.packageId, "Execution format", `Execution v${executionPackage.schemaVersion}`, "Definitions", String(executionPackage.definitions.length), "Required inputs", (executionPackage.requiredInputs ?? []).join(", ") || "None", "Runtime values", (executionPackage.runtimeRequirements ?? []).join(", ") || "None"] }) }), _jsx(InspectorSection, { title: "Run outcome", children: _jsxs("div", { className: "notice", children: [_jsx("strong", { children: result?.state.toUpperCase() ?? 'NOT STARTED' }), _jsx("br", {}), result ? `${result.environment} · ${result.state}` : 'Run the package to inspect its terminal outcome.'] }) }), _jsx(InspectorSection, { title: "Selected step inputs", children: _jsx(KeyValues, { values: Object.entries(displayResults[selectedStep]?.inputs ?? (selectedInvocations.length > 1 ? {} : inputs)).flatMap(([key, value]) => [key, String(value)]) }) }), _jsx(InspectorSection, { title: "Selected step outputs", children: _jsx(KeyValues, { values: displayResults[selectedStep]?.outputs ? Object.entries(displayResults[selectedStep]?.outputs ?? {}).flatMap(([key, value]) => [key, String(value)]) : ['—', selectedInvocations.length > 1 ? `${selectedInvocations.length} recorded invocations; see iteration history` : 'Available after execution'] }) }), (result?.error || displayResults[selectedStep]?.error) && _jsx(InspectorSection, { title: "Error", children: _jsxs("div", { className: "error-notice", children: [_jsx("strong", { children: displayResults[selectedStep]?.error?.code ?? result?.error?.code }), _jsx("br", {}), displayResults[selectedStep]?.error?.message ?? result?.error?.message] }) }), result?.issues !== undefined && _jsx(InspectorSection, { title: "Pre-run validation", children: _jsx(PreRunValidation, { issues: result.issues }) }), _jsx(InspectorSection, { title: "Invocation path", children: _jsx("div", { className: "notice", children: displayResults[selectedStep]?.invocationPath?.join(" › ") ?? "No invocation path available." }) }), _jsx(InspectorSection, { title: "Iteration history", children: _jsx(RecordedInvocations, { results: selectedInvocations, definition: findInspectionDefinition(packageSteps, displaySteps[selectedStep]?.structuralAddress) }, JSON.stringify([requestId, displaySteps[selectedStep]?.structuralAddress])) }), _jsx(InspectorSection, { title: "Execution timing", children: _jsxs("div", { className: "timing-details", children: [_jsxs("span", { children: ["Wall ", result?.executionTiming?.wallDurationMs ?? "—", " ms"] }), _jsxs("span", { children: ["Active ", result?.executionTiming?.activeDurationMs ?? "—", " ms"] }), _jsxs("span", { children: ["Paused ", result?.executionTiming?.pausedDurationMs ?? "—", " ms"] })] }) }), _jsx(InspectorSection, { title: "Selected step attempts", children: _jsx("div", { className: "attempt-history", children: displayResults[selectedStep]?.attempts?.map(attempt => _jsxs("div", { className: "iteration-entry", children: [_jsxs("strong", { children: ["Attempt ", attempt.attempt] }), _jsxs("span", { children: [(attempt.kind ?? 'automatic').toUpperCase(), " \u00B7 ", attempt.state.toUpperCase(), " \u00B7 ", attempt.durationMs, " ms"] })] }, `${attempt.attempt}-${attempt.kind}`)) ?? _jsx("div", { className: "notice", children: "No retry attempts recorded." }) }) }), _jsxs(InspectorSection, { title: "Selected step", children: [_jsxs("div", { className: "notice", children: [_jsx("strong", { children: displaySteps[selectedStep]?.name ?? 'No step selected' }), _jsx("br", {}), displayResults[selectedStep] ? displayResults[selectedStep].error ? 'This step failed. See the error details above.' : `Completed in ${displayResults[selectedStep].durationMs} ms.` : selectedInvocations.length > 1 ? `${selectedInvocations.length} invocations recorded. No single invocation selected.` : 'Run the package to inspect execution details.', _jsxs("div", { className: "inspector-actions", children: [_jsx("button", { className: "button", onClick: () => void retrySelected(), disabled: !displayResults[selectedStep] || (!displayResults[selectedStep]?.error && runState !== "completed"), children: "Retry Action" }), _jsx("button", { className: `button ${stopAfterStepId === displaySteps[selectedStep]?.id ? "active" : ""}`, onClick: () => displaySteps[selectedStep] && toggleStopAfter(displaySteps[selectedStep].id), disabled: !displaySteps[selectedStep], children: "Stop After" }), _jsx("button", { className: "button", onClick: () => void runFromSelected(), disabled: !displayResults[selectedStep] || runState === "running", children: "Run From Here" })] })] }), _jsxs("div", { className: "selection-nav", children: [_jsx("button", { className: "button", disabled: selectedStep <= 0, onClick: () => setSelectedStep(index => Math.max(0, index - 1)), children: "\u2039 Previous" }), _jsx("span", { children: displaySteps.length ? `${selectedStep + 1} / ${displaySteps.length}` : '0 / 0' }), _jsx("button", { className: "button", disabled: selectedStep >= displaySteps.length - 1, onClick: () => setSelectedStep(index => Math.min(displaySteps.length - 1, index + 1)), children: "Next \u203A" })] })] })] })] }), showAbout && _jsx("div", { className: "about-backdrop", onClick: () => setShowAbout(false), children: _jsxs("section", { className: "about-dialog", role: "dialog", "aria-modal": "true", "aria-labelledby": "about-title", onClick: event => event.stopPropagation(), children: [_jsx("button", { className: "about-close", onClick: () => setShowAbout(false), "aria-label": "Close About", children: "\u00D7" }), _jsx("div", { className: "runner-logo", children: "\u25B6" }), _jsx("h2", { id: "about-title", children: "QAick Runner" }), _jsx("p", { children: "Standalone execution and evidence viewer for QAICK packages." }), _jsxs("small", { children: [RUNNER_UI_VERSION, " \u00B7 ", RUNNER_BUILD_LABEL, " \u00B7 QAick contract"] }), _jsx("footer", { children: "\u00A9 2006\u20132026 QAick. All rights reserved." })] }) })] });
|
|
272
|
+
const projectFileInput = useRef(null);
|
|
273
|
+
const menuActions = useRef({ enabled: () => false, openProject: async () => { }, about: () => { }, demo: () => { } });
|
|
274
|
+
menuActions.current = {
|
|
275
|
+
enabled: id => runnerMenus.some(menu => menu.items.some(([command]) => command === id)) && (!showAbout || id.startsWith('help.')),
|
|
276
|
+
openProject: file => loadPackage(new File([file.text], file.name, { type: 'application/json' })),
|
|
277
|
+
about: () => setShowAbout(true),
|
|
278
|
+
// Preserve the current standalone/combined destination exactly.
|
|
279
|
+
demo: () => { window.location.href = commandLinks['help.demo']; },
|
|
280
|
+
};
|
|
281
|
+
const registerMenu = applicationMenu?.register;
|
|
282
|
+
useEffect(() => registerMenu?.({
|
|
283
|
+
enabled: id => menuActions.current.enabled(id),
|
|
284
|
+
openProject: file => menuActions.current.openProject(file),
|
|
285
|
+
about: () => menuActions.current.about(),
|
|
286
|
+
demo: () => menuActions.current.demo(),
|
|
287
|
+
}), [registerMenu]);
|
|
288
|
+
return _jsxs("div", { className: "runner-shell", children: [_jsxs("header", { className: "runner-header", children: [_jsxs("div", { className: "runner-brand", children: [_jsx("span", { className: "runner-brand-mark", children: "QAICK" }), _jsx("span", { className: "runner-brand-product", children: "Runner" }), _jsx("span", { className: "alpha-label", children: RUNNER_UI_VERSION }), onExit && _jsx("button", { className: "about-button", onClick: onExit, children: "Studio" })] }), _jsx("span", { className: "header-spacer" }), _jsx(ScreenshotButton, { className: "about-button" }), _jsx("a", { className: "demo-entry", href: "./examples/qaick-local-http-regression.qaick.json", download: true, title: "Download QAick Local HTTP Regression. Start the local fixture on port 5197, then load this file. Runner executes both Flows. Editing is restricted in Studio.", children: "HTTP example" }), !applicationMenu?.native && _jsx(HeaderHelp, { enabled: id => menuActions.current.enabled(id), onAbout: () => menuActions.current.about(), onOpenProject: () => projectFileInput.current?.click() }), _jsx(ThemeToggle, { theme: theme, onChange: setTheme, className: "runner-theme-toggle" })] }), _jsx("input", { ref: projectFileInput, type: "file", accept: "application/json,.json", hidden: true, "aria-label": "Open Project\u2026", onChange: event => { const file = event.target.files?.[0]; event.target.value = ''; void loadPackage(file); } }), _jsxs("main", { className: "runner-main", children: [selectedTarget?.issues?.length ? _jsxs("section", { className: "runner-workspace runner-authoring-source", children: [_jsx("div", { className: "eyebrow", children: "AUTHORING SOURCE" }), _jsx("h1", { children: selectedTarget.target.name }), _jsx("div", { className: "run-settings", children: _jsxs("label", { children: ["Target", _jsx("select", { value: selectedTargetId, onChange: event => selectTarget(event.target.value), children: packageCatalog.targets().map(t => _jsx("option", { value: t.packageId, children: t.target.name }, t.packageId)) })] }) }), _jsxs("div", { className: "notice", children: [_jsx("strong", { children: "Not executable" }), _jsx("p", { children: "This source has no validated executable structure. Complete and validate it in Studio, then reload the saved Project. Valid sibling Flows remain available in Target." })] }), _jsx("ul", { role: "alert", children: selectedTarget.issues.map((issue, index) => _jsx("li", { children: issue }, index)) }), loadError && _jsx("p", { role: "alert", children: loadError })] }) : _jsxs(_Fragment, { children: [_jsxs("nav", { className: "runner-nav", children: [_jsx("p", { className: "nav-label", children: "Workspace" }), _jsx(NavItem, { active: true, icon: "\u25C8", text: "Execution Package" }), _jsx(NavItem, { icon: "\u25A1", text: "Projects" }), _jsx(NavItem, { icon: "\u2318", text: "Flows" }), _jsx(NavItem, { icon: "\u21B6", text: "Run History" }), _jsxs("div", { className: "package-card", children: [_jsx("small", { children: "Loaded package" }), _jsxs("strong", { children: [executionPackage.packageId, ".qaick.json"] }), _jsxs("small", { children: ["Execution v", executionPackage.schemaVersion, " \u00B7 ", executionPackage.definitions.length, " definitions"] })] })] }), _jsxs("section", { className: "runner-workspace", children: [_jsx("div", { className: "eyebrow", children: "EXECUTION PACKAGE" }), _jsx("h1", { children: executionPackage.target.name }), _jsxs("p", { className: "description", children: ["Execute the selected QAick ", executionPackage.target.kind === 'project' ? 'Project' : 'Flow', " and capture structured evidence."] }), _jsxs("div", { className: "run-controls", children: [_jsxs("div", { className: "run-settings", role: "group", "aria-label": "Execution setup", children: [_jsxs("label", { children: ["Target", _jsx("select", { value: selectedTargetId, onChange: event => selectTarget(event.target.value), children: packageCatalog.targets().map(item => _jsx("option", { value: item.packageId, children: item.target.name }, item.packageId)) })] }), _jsxs("label", { children: ["Environment", _jsxs("select", { value: environment, onChange: event => setEnvironment(event.target.value), children: [_jsx("option", { children: "test" }), _jsx("option", { children: "local" }), _jsx("option", { children: "staging" })] })] }), _jsxs("label", { children: ["Timeout", _jsxs("select", { value: timeoutMs, onChange: event => setTimeoutMs(event.target.value), children: [_jsx("option", { value: "none", children: "None" }), _jsx("option", { value: "1000", children: "1 second" }), _jsx("option", { value: "3000", children: "3 seconds" }), _jsx("option", { value: "10000", children: "10 seconds" })] })] }), _jsxs("label", { children: ["Run configuration", _jsxs("select", { defaultValue: "Default", children: [_jsx("option", { children: "Default" }), _jsx("option", { children: "Debug" })] })] })] }), _jsxs("div", { className: "run-actions", role: "group", "aria-label": "Execution actions", children: [_jsx("button", { className: "button", onClick: () => setEditingInputs(value => !value), children: editingInputs ? 'Done inputs' : 'Edit inputs' }), _jsx("button", { className: "button primary", onClick: () => void run(), disabled: !!inputBlocker || runState === 'preflighting' || runState === 'running', children: "\u25B6\u00A0 Run" }), _jsx("button", { className: "button dry-run", onClick: () => void run(true), disabled: !!inputBlocker || runState === "preflighting" || runState === "running", children: "\u25CE\u00A0 Pre-run validation" }), _jsx("button", { className: "button stop", onClick: () => void stop(), disabled: runState !== 'preflighting' && runState !== 'running', children: "\u25A0\u00A0 Stop" }), _jsx("button", { className: "button", onClick: () => void pause(), disabled: controlState !== 'running', children: "\u2161\u00A0 Pause" }), _jsx("button", { className: `button ${controlState === 'paused' ? 'resume-highlight' : ''}`, onClick: () => void resume(), disabled: controlState !== 'paused', children: "\u25B6\u00A0 Resume" }), _jsx("button", { className: "button", onClick: () => void stepExecution(), disabled: controlState !== 'paused', children: "\u21A6\u00A0 Step" }), breakpoints.size > 0 && _jsxs("span", { className: "debug-session-badge", children: [breakpoints.size, " breakpoint", breakpoints.size === 1 ? "" : "s", " armed"] })] })] }), viewingHistoryEntry && _jsxs("div", { className: "history-view-banner", children: ["Viewing stored results \u00B7 ", viewingHistoryEntry] }), conflict && _jsxs("section", { role: "alert", children: [_jsx("p", { children: conflict.message }), _jsx("ul", { children: conflict.entries.map((entry, index) => _jsxs("li", { children: [entry.owner, " \u00B7 ", entry.declaration.name, " \u00B7 ", entry.declaration.type, " \u00B7 ", entry.declaration.required ? 'required' : 'optional', " \u00B7 default: ", Object.hasOwn(entry.declaration, 'defaultValue') ? JSON.stringify(entry.declaration.defaultValue) : 'none'] }, index)) }), _jsx("p", { children: "Retained input drafts:" }), _jsx("ul", { children: Object.entries(inputEdits).map(([name, draft]) => _jsxs("li", { children: [name, " \u00B7 ", draft.supplied ? 'supplied' : 'omitted'] }, name)) })] }), unresolved && _jsxs("section", { role: "alert", children: [_jsx("p", { children: unresolved.message }), _jsx("ul", { children: unresolved.entries.map((entry, index) => _jsxs("li", { children: [entry.owner, " \u00B7 ", entry.name, " \u00B7 required \u00B7 type unresolved"] }, index)) }), _jsx("p", { children: "Retained input drafts:" }), _jsx("ul", { children: Object.entries(inputEdits).map(([name, draft]) => _jsxs("li", { children: [name, " \u00B7 ", draft.supplied ? 'supplied' : 'omitted'] }, name)) })] }), inputError && _jsx("p", { role: "alert", children: inputError }), loadError && _jsx("p", { className: "load-error", children: loadError }), loadIssues.length > 0 && _jsx(PreRunValidation, { issues: loadIssues, title: "Artifact validation" }), result?.issues !== undefined && _jsx(PreRunValidation, { issues: result.issues }), result?.warnings !== undefined && result.warnings.length > 0 && _jsx(PreRunValidation, { issues: result.warnings, title: "Browser execution warnings" }), editingInputs && _jsxs("section", { className: "inputs-panel panel", children: [_jsxs("div", { className: "panel-heading", children: [_jsx("h2", { children: "Invocation Inputs" }), _jsx("span", { className: "panel-spacer" }), _jsx("span", { className: "input-hint", children: "Values are sent with the next run" })] }), _jsxs("div", { className: "input-grid", children: [inputBlocker ? _jsx("p", { children: "Invocation input editing is unavailable until requirements are resolved." }) : declarations.map(declaration => { const draft = inputEdits[declaration.name] ?? { supplied: false, text: '' }; const change = (text) => setInputEdits(previous => ({ ...previous, [declaration.name]: { supplied: true, text } })); return _jsxs("div", { children: [_jsxs("label", { children: [_jsx("input", { type: "checkbox", "aria-label": `Supply ${declaration.name}`, checked: draft.supplied, onChange: event => setInputEdits(previous => ({ ...previous, [declaration.name]: { ...draft, supplied: event.target.checked } })) }), "Supply ", declaration.name] }), declaration.type === 'boolean' ? _jsxs("label", { className: "input-field", children: [declaration.name, _jsxs("select", { value: draft.text, onChange: event => change(event.target.value), children: [_jsx("option", { value: "false", children: "false" }), _jsx("option", { value: "true", children: "true" })] })] }) : _jsx(InputField, { label: declaration.name, value: draft.text, onChange: change }), _jsxs("small", { children: [declaration.type, " \u00B7 ", draft.supplied ? 'supplied' : Object.hasOwn(declaration, 'defaultValue') ? 'omitted — declared default applies' : 'omitted', declaration.required ? ' · required' : ''] })] }, declaration.name); }), (executionPackage.runtimeRequirements ?? []).map(name => _jsx(InputField, { label: `Runtime · ${name}`, value: runtimeValues[name] ?? '', onChange: value => setRuntimeValues(previous => ({ ...previous, [name]: value })) }, name))] })] }), _jsxs("section", { className: "panel", children: [_jsxs("div", { className: "panel-heading", children: [_jsx("h2", { children: "Live Execution" }), _jsx("span", { className: "panel-spacer" }), _jsxs("span", { className: "execution-menu-label", children: ["Right-click a row to manage its breakpoint \u00B7 Selected: ", displaySteps[selectedStep]?.name ?? 'None'] }), _jsxs("span", { className: runState === 'running' ? 'running' : runState === 'failed' ? 'stopped' : 'run-status', children: ["\u25CF ", statusLabel] })] }), hasNestedSteps && _jsxs("div", { className: "hierarchy-hint", children: [_jsx("strong", { children: "Nested execution" }), _jsx("span", { children: "Use \u2212 / + to collapse or expand nested Operations, Control Blocks, and loops." }), _jsx("button", { className: "hierarchy-action", onClick: () => setAllCollapsed(true), children: "Collapse all" }), _jsx("button", { className: "hierarchy-action", onClick: () => setAllCollapsed(false), children: "Expand all" })] }), _jsx("div", { className: "table-scroll", role: "region", "aria-label": "Execution steps", tabIndex: 0, children: _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "#" }), _jsx("th", { children: "Step hierarchy" }), _jsx("th", { children: "Status" }), _jsx("th", { children: "Start time" }), _jsx("th", { children: "Duration" })] }) }), _jsx("tbody", { children: displaySteps.map((step, index) => { const isLoop = step.loopRepeat; const loopContexts = isLoop ? loopPresentation(step.structuralAddress, step.loopTotal, step.loopDuration, result ? allResults : eventPresentationEvidence(events), Boolean(result)) : []; const rowEvidence = result ? allResults : eventPresentationEvidence(events); const repeatedMatches = rowEvidence.filter(r => occurrenceKey(r.structuralAddress) === occurrenceKey(step.structuralAddress)); const latestQualifier = step.branchRow ? rowEvidence.filter(r => occurrenceKey(r.structuralAddress) === occurrenceKey(step.branchOwner)).length > 1 : repeatedMatches.length > 1; const displayState = presentationState(step, step.branchRow ? rowEvidence : latestQualifier ? [repeatedMatches.at(-1)] : rowEvidence, Boolean(result), step.branchRow ? 'pending' : stepStates[index] ?? 'pending'); const isLoopContent = Boolean(step.loopId); const isLoopEnd = isLoopContent && displaySteps[index + 1]?.loopId !== step.loopId; return _jsxs("tr", { className: `${selectedStep === index ? 'selected ' : ''}${isLoop ? 'loop-row ' : ''}${isLoopContent ? 'loop-content ' : ''}${isLoopEnd ? 'loop-end ' : ''}${step.depth > 0 ? 'nested-row' : 'parent-row'}`, tabIndex: 0, onKeyDown: event => { if (event.key === "ArrowDown") {
|
|
289
|
+
event.preventDefault();
|
|
290
|
+
setSelectedStep(Math.min(displaySteps.length - 1, index + 1));
|
|
291
|
+
} if (event.key === "ArrowUp") {
|
|
292
|
+
event.preventDefault();
|
|
293
|
+
setSelectedStep(Math.max(0, index - 1));
|
|
294
|
+
} }, onClick: () => setSelectedStep(index), onContextMenu: event => { event.preventDefault(); setSelectedStep(index); setContextMenu({ x: event.clientX, y: event.clientY, stepId: step.id }); }, children: [_jsx("td", { children: index + 1 }), _jsxs("td", { children: [_jsx("span", { className: `step-icon ${isLoop ? 'loop-icon' : displayState}`, children: isLoop ? '↻' : displayState === 'finished' ? '✓' : displayState === 'waiting' ? '◷' : displayState === 'skipped' ? '–' : '' }), _jsxs("span", { className: "hierarchy-name", style: { paddingLeft: `${step.depth * 18}px` }, children: [step.depth > 0 && _jsx("span", { className: "branch-marker", children: "\u2514" }), step.hasChildren && _jsx("button", { className: "collapse-toggle", "aria-label": `${collapsedSteps.has(step.id) ? 'Expand' : 'Collapse'} ${step.name}`, onClick: event => { event.stopPropagation(); toggleStep(step.id); }, children: collapsedSteps.has(step.id) ? '+' : '−' }), _jsx("span", { children: step.name }), breakpoints.has(step.id) && _jsx("small", { className: "breakpoint-marker", "aria-label": "Breakpoint armed", title: "Breakpoint armed", children: "STOP" }), isLoop && _jsx("small", { className: "kind-label loop-label", children: "LOOP" }), step.depth > 0 ? _jsx("small", { className: "kind-label nested-label", children: "NESTED STEP" }) : !isLoop && _jsx("small", { className: "kind-label", children: step.kind.toUpperCase() })] }), selectedStep === index && _jsx("small", { className: "selected-label", children: "SELECTED" })] }), _jsxs("td", { children: [loopContexts.map(context => _jsxs("div", { className: "loop-progress", children: [context.context.length > 0 && _jsxs("small", { children: ["Enclosing iterations: ", context.context.map(s => s.iteration).join(' / ')] }), _jsx("strong", { children: context.label }), _jsx("small", { children: context.state.toUpperCase() })] }, JSON.stringify(context.context))), _jsx("span", { className: `status ${displayState}`, children: displayState.toUpperCase() }), latestQualifier && _jsx("small", { className: "latest-invocation", children: "Latest invocation" })] }), _jsx("td", { children: displayResults[index] ? new Date(result?.finishedAt ?? '').toLocaleTimeString() : '—' }), _jsx("td", { children: displayResults[index] ? `${displayResults[index].durationMs} ms` : '—' })] }, `${step.id}-${index}`); }) })] }) }), contextMenu && _jsxs("div", { className: "runner-context-menu", style: { left: contextMenu.x, top: contextMenu.y }, onClick: event => event.stopPropagation(), onContextMenu: event => event.preventDefault(), children: [_jsx("strong", { children: "Breakpoint" }), breakpoints.has(contextMenu.stepId) ? _jsx("button", { onClick: () => { toggleBreakpoint(contextMenu.stepId); setContextMenu(undefined); }, children: "Remove breakpoint" }) : _jsx("button", { onClick: () => { toggleBreakpoint(contextMenu.stepId); setContextMenu(undefined); }, children: "Add breakpoint" })] })] }), _jsxs("section", { className: "panel event-panel", children: [_jsxs("div", { className: "panel-heading", children: [_jsx("h2", { children: "Event Log" }), _jsx("span", { className: "panel-spacer" }), _jsx("input", { className: "event-search", "aria-label": "Search event log", placeholder: "Search events", value: eventSearch, onChange: event => setEventSearch(event.target.value) }), _jsxs("select", { className: "event-filter", value: eventFilter, onChange: event => setEventFilter(event.target.value), children: [_jsx("option", { value: "all", children: "All events" }), _jsx("option", { value: "steps", children: "Step events" }), _jsx("option", { value: "parallel", children: "Parallel groups" }), _jsx("option", { value: "lifecycle", children: "Lifecycle" }), _jsx("option", { value: "failures", children: "Failures" })] })] }), _jsx("div", { className: "table-scroll", role: "region", "aria-label": "Execution events", tabIndex: 0, children: _jsx("table", { children: _jsx("tbody", { children: visibleEvents.map(event => _jsxs("tr", { className: "event-row", children: [_jsx("td", { children: new Date(event.timestamp).toLocaleTimeString() }), _jsx("td", { children: event.state.toUpperCase() }), _jsx("td", { children: _jsx("code", { children: event.type.toUpperCase() }) }), _jsxs("td", { children: [event.message, event.parallel && _jsxs("small", { className: "parallel-event", children: [" \u00B7 Parallel ", event.parallel.completed, "/", event.parallel.total, " (", event.parallel.join, ")"] })] })] }, event.sequence)) }) }) }), events.length > 0 && visibleEvents.length === 0 && _jsx("p", { className: "empty-state", children: "No events match the current filter." })] }), _jsx(HistoryPanel, { entries: runHistory, onReload: reloadHistoryEntry, retention: historyRetention, onRetentionChange: changeHistoryRetention, onClear: clearHistory, onDelete: deleteHistoryEntry, onExport: exportResult })] }), _jsxs("aside", { className: "inspector", children: [_jsx("h2", { children: "Selected execution" }), _jsx("p", { className: "selected-detail-name", children: _jsx("strong", { children: displaySteps[selectedStep]?.name ?? 'No step selected' }) }), _jsxs("p", { className: "run-id", children: ["Run ID: ", requestId ?? 'No run started'] }), _jsx(InspectorSection, { title: "Artifact summary", children: _jsx(KeyValues, { values: ["Target", executionPackage.target.name, "Type", executionPackage.target.kind, "Package", executionPackage.packageId, "Execution format", `Execution v${executionPackage.schemaVersion}`, "Definitions", String(executionPackage.definitions.length), "Required inputs", (executionPackage.requiredInputs ?? []).join(", ") || "None", "Runtime values", (executionPackage.runtimeRequirements ?? []).join(", ") || "None"] }) }), _jsx(InspectorSection, { title: "Run outcome", children: _jsxs("div", { className: "notice", children: [_jsx("strong", { children: result?.state.toUpperCase() ?? 'NOT STARTED' }), _jsx("br", {}), result ? `${result.environment} · ${result.state}` : 'Run the package to inspect its terminal outcome.'] }) }), _jsx(InspectorSection, { title: "Selected step inputs", children: _jsx(KeyValues, { values: Object.entries(displayResults[selectedStep]?.inputs ?? (selectedInvocations.length > 1 ? {} : inputs)).flatMap(([key, value]) => [key, String(value)]) }) }), _jsx(InspectorSection, { title: "Selected step outputs", children: _jsx(KeyValues, { values: displayResults[selectedStep]?.outputs ? Object.entries(displayResults[selectedStep]?.outputs ?? {}).flatMap(([key, value]) => [key, String(value)]) : ['—', selectedInvocations.length > 1 ? `${selectedInvocations.length} recorded invocations; see iteration history` : 'Available after execution'] }) }), (result?.error || displayResults[selectedStep]?.error) && _jsx(InspectorSection, { title: "Error", children: _jsxs("div", { className: "error-notice", children: [_jsx("strong", { children: displayResults[selectedStep]?.error?.code ?? result?.error?.code }), _jsx("br", {}), displayResults[selectedStep]?.error?.message ?? result?.error?.message] }) }), result?.issues !== undefined && _jsx(InspectorSection, { title: "Pre-run validation", children: _jsx(PreRunValidation, { issues: result.issues }) }), _jsx(InspectorSection, { title: "Invocation path", children: _jsx("div", { className: "notice", children: displayResults[selectedStep]?.invocationPath?.join(" › ") ?? "No invocation path available." }) }), _jsx(InspectorSection, { title: "Iteration history", children: _jsx(RecordedInvocations, { results: selectedInvocations, definition: findInspectionDefinition(packageSteps, displaySteps[selectedStep]?.structuralAddress) }, JSON.stringify([requestId, displaySteps[selectedStep]?.structuralAddress])) }), _jsx(InspectorSection, { title: "Execution timing", children: _jsxs("div", { className: "timing-details", children: [_jsxs("span", { children: ["Wall ", result?.executionTiming?.wallDurationMs ?? "—", " ms"] }), _jsxs("span", { children: ["Active ", result?.executionTiming?.activeDurationMs ?? "—", " ms"] }), _jsxs("span", { children: ["Paused ", result?.executionTiming?.pausedDurationMs ?? "—", " ms"] })] }) }), _jsx(InspectorSection, { title: "Selected step attempts", children: _jsx("div", { className: "attempt-history", children: displayResults[selectedStep]?.attempts?.map(attempt => _jsxs("div", { className: "iteration-entry", children: [_jsxs("strong", { children: ["Attempt ", attempt.attempt] }), _jsxs("span", { children: [(attempt.kind ?? 'automatic').toUpperCase(), " \u00B7 ", attempt.state.toUpperCase(), " \u00B7 ", attempt.durationMs, " ms"] })] }, `${attempt.attempt}-${attempt.kind}`)) ?? _jsx("div", { className: "notice", children: "No retry attempts recorded." }) }) }), _jsxs(InspectorSection, { title: "Selected step", children: [_jsxs("div", { className: "notice", children: [_jsx("strong", { children: displaySteps[selectedStep]?.name ?? 'No step selected' }), _jsx("br", {}), displayResults[selectedStep] ? displayResults[selectedStep].error ? 'This step failed. See the error details above.' : `Completed in ${displayResults[selectedStep].durationMs} ms.` : selectedInvocations.length > 1 ? `${selectedInvocations.length} invocations recorded. No single invocation selected.` : 'Run the package to inspect execution details.', _jsxs("div", { className: "inspector-actions", children: [_jsx("button", { className: "button", onClick: () => void retrySelected(), disabled: !displayResults[selectedStep] || (!displayResults[selectedStep]?.error && runState !== "completed"), children: "Retry Action" }), _jsx("button", { className: `button ${stopAfterStepId === displaySteps[selectedStep]?.id ? "active" : ""}`, onClick: () => displaySteps[selectedStep] && toggleStopAfter(displaySteps[selectedStep].id), disabled: !displaySteps[selectedStep], children: "Stop After" }), _jsx("button", { className: "button", onClick: () => void runFromSelected(), disabled: !displayResults[selectedStep] || runState === "running", children: "Run From Here" })] })] }), _jsxs("div", { className: "selection-nav", children: [_jsx("button", { className: "button", disabled: selectedStep <= 0, onClick: () => setSelectedStep(index => Math.max(0, index - 1)), children: "\u2039 Previous" }), _jsx("span", { children: displaySteps.length ? `${selectedStep + 1} / ${displaySteps.length}` : '0 / 0' }), _jsx("button", { className: "button", disabled: selectedStep >= displaySteps.length - 1, onClick: () => setSelectedStep(index => Math.min(displaySteps.length - 1, index + 1)), children: "Next \u203A" })] })] })] })] }), " "] }), showAbout && _jsx("div", { className: "about-backdrop", onClick: () => setShowAbout(false), children: _jsxs("section", { className: "about-dialog", role: "dialog", "aria-modal": "true", "aria-labelledby": "about-title", onClick: event => event.stopPropagation(), children: [_jsx("button", { className: "about-close", onClick: () => setShowAbout(false), "aria-label": "Close About", children: "\u00D7" }), _jsx("div", { className: "runner-logo", children: "\u25B6" }), _jsx("h2", { id: "about-title", children: "QAick Runner" }), _jsx("p", { children: "Standalone execution and evidence viewer for QAICK packages." }), _jsxs("small", { children: [RUNNER_UI_VERSION, " \u00B7 ", RUNNER_BUILD_LABEL, " \u00B7 QAick contract"] }), _jsx("footer", { children: "\u00A9 2006\u20132026 QAick. All rights reserved." })] }) })] });
|
|
243
295
|
}
|
|
244
296
|
function NavItem({ icon, text, active = false }) { const scrollTarget = text === 'Run History' ? '.history-panel' : text === 'Flows' ? '.runner-workspace table' : '.runner-workspace'; return _jsxs("a", { className: `nav-item ${active ? 'active' : ''}`, href: `#${text.toLowerCase().replaceAll(' ', '-')}`, onClick: event => { event.preventDefault(); document.querySelector(scrollTarget)?.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, children: [_jsx("span", { children: icon }), text] }); }
|
|
245
|
-
function HistoryPanel({ entries, retention, onRetentionChange, onClear, onDelete, onExport, onReload }) { return _jsxs("section", { className: "panel history-panel", children: [_jsxs("div", { className: "panel-heading", children: [_jsx("h2", { children: "Run History" }), _jsx("span", { className: "panel-spacer" }), _jsxs("label", { className: "history-retention", children: ["Keep", _jsxs("select", { "aria-label": "Run history retention", value: retention, onChange: event => onRetentionChange(Number(event.target.value)), children: [_jsx("option", { value: "10", children: "10 runs" }), _jsx("option", { value: "20", children: "20 runs" }), _jsx("option", { value: "50", children: "50 runs" }), _jsx("option", { value: "100", children: "100 runs" })] })] }), _jsx("button", { className: "button", onClick: onClear, disabled: !entries.length, children: "Clear history" })] }), entries.length ? _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Target" }), _jsx("th", { children: "Started" }), _jsx("th", { children: "State" }), _jsx("th", { children: "Duration" }), _jsx("th", { children: "Result" })] }) }), _jsx("tbody", { children: entries.map(entry => _jsxs("tr", { children: [_jsxs("td", { children: [entry.target, _jsxs("small", { className: "history-meta", children: [entry.result.provenance ? 'Run From Here' : entry.result.steps.some(step => step.attempts?.some(attempt => attempt.kind === 'interactive')) ? 'Interactive retry' : 'Normal run', " \u00B7 ", entry.packageId ?? 'legacy result', " \u00B7 Artifact v", entry.artifactProvenance?.originalVersion ?? 'unknown', " \u00B7 Execution v", entry.executionSchemaVersion ?? 'unknown', _jsx("br", {}), "hash ", entry.result.artifactHash ?? 'legacy result'] })] }), _jsx("td", { children: new Date(entry.startedAt).toLocaleString() }), _jsx("td", { children: _jsx("span", { className: `status ${entry.state}`, children: entry.state.toUpperCase() }) }), _jsxs("td", { children: [entry.durationMs, " ms"] }), _jsxs("td", { children: [_jsx("button", { className: "button", onClick: () => onReload(entry), children: "View results" }), _jsx("button", { className: "button", onClick: () => onExport(entry.result, entry.packageId, entry.artifactProvenance?.originalVersion, entry.artifactProvenance, entry.executionSchemaVersion), children: "Export JSON" }), _jsx("button", { className: "button history-delete", onClick: () => onDelete(entry.requestId), children: "Delete" })] })] }, entry.requestId)) })] }) : _jsx("p", { className: "empty-state", children: "Completed and failed runs will be kept here in this browser." })] }); }
|
|
297
|
+
function HistoryPanel({ entries, retention, onRetentionChange, onClear, onDelete, onExport, onReload }) { return _jsxs("section", { className: "panel history-panel", children: [_jsxs("div", { className: "panel-heading", children: [_jsx("h2", { children: "Run History" }), _jsx("span", { className: "panel-spacer" }), _jsxs("label", { className: "history-retention", children: ["Keep", _jsxs("select", { "aria-label": "Run history retention", value: retention, onChange: event => onRetentionChange(Number(event.target.value)), children: [_jsx("option", { value: "10", children: "10 runs" }), _jsx("option", { value: "20", children: "20 runs" }), _jsx("option", { value: "50", children: "50 runs" }), _jsx("option", { value: "100", children: "100 runs" })] })] }), _jsx("button", { className: "button", onClick: onClear, disabled: !entries.length, children: "Clear history" })] }), entries.length ? _jsx("div", { className: "table-scroll", role: "region", "aria-label": "Stored runs", tabIndex: 0, children: _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Target" }), _jsx("th", { children: "Started" }), _jsx("th", { children: "State" }), _jsx("th", { children: "Duration" }), _jsx("th", { children: "Result" })] }) }), _jsx("tbody", { children: entries.map(entry => _jsxs("tr", { children: [_jsxs("td", { children: [entry.target, _jsxs("small", { className: "history-meta", children: [entry.result.provenance ? 'Run From Here' : entry.result.steps.some(step => step.attempts?.some(attempt => attempt.kind === 'interactive')) ? 'Interactive retry' : 'Normal run', " \u00B7 ", entry.packageId ?? 'legacy result', " \u00B7 Artifact v", entry.artifactProvenance?.originalVersion ?? 'unknown', " \u00B7 Execution v", entry.executionSchemaVersion ?? 'unknown', _jsx("br", {}), "hash ", entry.result.artifactHash ?? 'legacy result'] })] }), _jsx("td", { children: new Date(entry.startedAt).toLocaleString() }), _jsx("td", { children: _jsx("span", { className: `status ${entry.state}`, children: entry.state.toUpperCase() }) }), _jsxs("td", { children: [entry.durationMs, " ms"] }), _jsxs("td", { children: [_jsx("button", { className: "button", onClick: () => onReload(entry), children: "View results" }), _jsx("button", { className: "button", onClick: () => onExport(entry.result, entry.packageId, entry.artifactProvenance?.originalVersion, entry.artifactProvenance, entry.executionSchemaVersion), children: "Export JSON" }), _jsx("button", { className: "button history-delete", onClick: () => onDelete(entry.requestId), children: "Delete" })] })] }, entry.requestId)) })] }) }) : _jsx("p", { className: "empty-state", children: "Completed and failed runs will be kept here in this browser." })] }); }
|
|
246
298
|
function InspectorSection({ title, children }) { return _jsxs("section", { className: "inspector-section", children: [_jsx("h3", { children: title }), children] }); }
|
|
247
299
|
function KeyValues({ values }) { return _jsx("div", { className: "key-values", children: redactKeyValuePairs(values).map((value, index) => _jsx("span", { children: value }, `${value}-${index}`)) }); }
|
|
248
300
|
function IterationHistory({ result }) { return _jsxs(_Fragment, { children: [_jsx("div", { className: "iteration-history", children: result?.children?.length ? result.children.map((iteration, index) => _jsxs("div", { className: "iteration-entry", children: [_jsx("strong", { children: iteration.name }), _jsxs("span", { children: [iteration.state.toUpperCase(), " \u00B7 ", iteration.durationMs, " ms"] }), _jsx("small", { children: iteration.children?.map(child => `${child.name}: ${Object.entries(child.outputs).map(([key, value]) => `${key}=${String(value)}`).join(', ') || 'no outputs'}`).join(' · ') || 'No nested output.' })] }, `${iteration.id}-${index}`)) : _jsx("div", { className: "notice", children: "No iteration history available." }) }), result?.terminalReason && _jsxs("div", { className: "timing-details", children: [_jsx("strong", { children: "Terminal reason" }), _jsx("span", { children: result.terminalReason })] }), _jsx(TransportTiming, { timing: result?.transportTiming }), _jsx(BranchExecutionPath, { result: result }), _jsx(ParallelGroupDetails, { result: result }), _jsx(ResultSummary, { result: result })] }); }
|