@qaick/runner 0.32.2 → 0.32.3
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 +5 -5
- package/package-dist/src/demo/RunnerWalkthrough.js +4 -4
- package/package-dist/src/demo/RunnerWalkthrough.js.map +1 -1
- package/package-dist/src/runner/RunnerModule.d.ts +4 -3
- package/package-dist/src/runner/RunnerModule.d.ts.map +1 -1
- package/package-dist/src/runner/RunnerModule.js +228 -63
- package/package-dist/src/runner/RunnerModule.js.map +1 -1
- package/package-dist/src/runner/catalog.js +1 -1
- package/package-dist/src/runner/execution-context.d.ts +1 -1
- package/package-dist/src/runner/execution-context.d.ts.map +1 -1
- package/package-dist/src/runner/execution-context.js +3 -3
- package/package-dist/src/runner/execution-context.js.map +1 -1
- package/package-dist/src/runner/history.d.ts +20 -1
- package/package-dist/src/runner/history.d.ts.map +1 -1
- package/package-dist/src/runner/history.js +14 -0
- package/package-dist/src/runner/history.js.map +1 -1
- package/package-dist/src/runner/invocation-inputs.d.ts +6 -2
- package/package-dist/src/runner/invocation-inputs.d.ts.map +1 -1
- package/package-dist/src/runner/invocation-inputs.js.map +1 -1
- package/package-dist/src/runner/loop-presentation.d.ts +4 -4
- package/package-dist/src/runner/loop-presentation.d.ts.map +1 -1
- package/package-dist/src/runner/loop-presentation.js +4 -4
- package/package-dist/src/runner/loop-presentation.js.map +1 -1
- package/package-dist/src/runner/presentation-state.d.ts +19 -4
- package/package-dist/src/runner/presentation-state.d.ts.map +1 -1
- package/package-dist/src/runner/presentation-state.js +68 -3
- package/package-dist/src/runner/presentation-state.js.map +1 -1
- package/package-dist/src/runner/runtime-client-adapter.d.ts +1 -1
- package/package-dist/src/runner/studio-execution-document.d.ts +4 -0
- package/package-dist/src/runner/studio-execution-document.d.ts.map +1 -0
- package/package-dist/src/runner/studio-execution-document.js +239 -0
- package/package-dist/src/runner/studio-execution-document.js.map +1 -0
- package/package-dist/src/runner/studio-hierarchy-loader.js +2 -2
- package/package-dist/src/runner/types.d.ts +26 -6
- package/package-dist/src/runner/types.d.ts.map +1 -1
- package/package-dist/styles.css +55 -27
- package/package.json +5 -5
|
@@ -5,7 +5,7 @@ import { HeaderHelp } from "./HeaderHelp.js";
|
|
|
5
5
|
import { ScreenshotButton } from "../ScreenshotButton.js";
|
|
6
6
|
import { invocationBranches } from "./invocation-branches.js";
|
|
7
7
|
import { loopPresentation } from "./loop-presentation.js";
|
|
8
|
-
import {
|
|
8
|
+
import { rowPresentation, scopeIncludes, eventPresentationEvidence } from "./presentation-state.js";
|
|
9
9
|
import { useEffect, useRef, useState } from 'react';
|
|
10
10
|
import { checkoutNestedPackage, checkoutPackage, checkoutProjectPackage } from "./fixture.js";
|
|
11
11
|
import { PackageLoadError } from "./package-loader.js";
|
|
@@ -17,12 +17,30 @@ import { RUNNER_BUILD_LABEL, RUNNER_UI_VERSION } from "../build-info.js";
|
|
|
17
17
|
import { collectSecretValues, redactExecutionEvent, redactExecutionResult, redactKeyValuePairs } from "./redaction.js";
|
|
18
18
|
import { createResultExport } from "./result-export.js";
|
|
19
19
|
import { inspectDeclaredInputs, inputDrafts, resolvedArguments } from "./invocation-inputs.js";
|
|
20
|
-
import { persistRunHistory } from "./history.js";
|
|
20
|
+
import { persistRunHistory, captureHistoryStructure, assertHistoryIdentity } from "./history.js";
|
|
21
|
+
import { prepareStudioDocument } from "./studio-execution-document.js";
|
|
21
22
|
const statusGlyph = { pending: '', 'not executed': '', running: '', finished: '✓', failed: '✕', waiting: '◷', skipped: '–', cancelled: '⊘' };
|
|
22
23
|
const HISTORY_KEY = 'qaick-runner.history.v1';
|
|
23
24
|
const HISTORY_RETENTION_KEY = 'qaick-runner.history-retention.v1';
|
|
24
25
|
const IMPORTED_PACKAGE_KEY = 'qaick-runner.imported-package.v1';
|
|
25
26
|
const THEME_STORAGE_KEY = 'qaick-studio-theme';
|
|
27
|
+
const INSPECTOR_WIDTH_KEY = 'qaick-runner.inspector-width.v1';
|
|
28
|
+
const INSPECTOR_MIN_WIDTH = 280;
|
|
29
|
+
const INSPECTOR_MAX_WIDTH = 640;
|
|
30
|
+
const inspectorWidthWithinBounds = (width) => Math.max(INSPECTOR_MIN_WIDTH, Math.min(INSPECTOR_MAX_WIDTH, width));
|
|
31
|
+
function readInspectorWidth() {
|
|
32
|
+
try {
|
|
33
|
+
const value = Number(localStorage.getItem(INSPECTOR_WIDTH_KEY));
|
|
34
|
+
return Number.isFinite(value) && value > 0 ? inspectorWidthWithinBounds(value) : 320;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return 320;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function saveInspectorWidth(width) { try {
|
|
41
|
+
localStorage.setItem(INSPECTOR_WIDTH_KEY, String(width));
|
|
42
|
+
}
|
|
43
|
+
catch { /* Resizing still works when preferences cannot be stored. */ } }
|
|
26
44
|
function readRunHistory() { try {
|
|
27
45
|
return JSON.parse(localStorage.getItem(HISTORY_KEY) ?? '[]');
|
|
28
46
|
}
|
|
@@ -63,12 +81,68 @@ function standaloneDemoRequested() { return typeof window !== 'undefined' && new
|
|
|
63
81
|
function RunnerApp({ client, onExit, applicationMenu }) {
|
|
64
82
|
const [theme, setTheme] = useState(readTheme);
|
|
65
83
|
const [showAbout, setShowAbout] = useState(false);
|
|
66
|
-
const [
|
|
84
|
+
const [inspectorWidth, setInspectorWidth] = useState(readInspectorWidth);
|
|
85
|
+
const inspectorDrag = useRef(undefined);
|
|
86
|
+
function startInspectorResize(event) {
|
|
87
|
+
if (event.button !== 0)
|
|
88
|
+
return;
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
91
|
+
inspectorDrag.current = { pointerId: event.pointerId, startX: event.clientX, startWidth: inspectorWidth, width: inspectorWidth };
|
|
92
|
+
}
|
|
93
|
+
function moveInspectorResize(event) {
|
|
94
|
+
const drag = inspectorDrag.current;
|
|
95
|
+
if (!drag || drag.pointerId !== event.pointerId)
|
|
96
|
+
return;
|
|
97
|
+
drag.width = inspectorWidthWithinBounds(drag.startWidth + drag.startX - event.clientX);
|
|
98
|
+
setInspectorWidth(drag.width);
|
|
99
|
+
}
|
|
100
|
+
function finishInspectorResize(event) {
|
|
101
|
+
const drag = inspectorDrag.current;
|
|
102
|
+
if (!drag || drag.pointerId !== event.pointerId)
|
|
103
|
+
return;
|
|
104
|
+
inspectorDrag.current = undefined;
|
|
105
|
+
if (event.type === 'pointercancel')
|
|
106
|
+
setInspectorWidth(drag.startWidth);
|
|
107
|
+
else
|
|
108
|
+
saveInspectorWidth(drag.width);
|
|
109
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId))
|
|
110
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
111
|
+
}
|
|
112
|
+
function inspectorResizeKey(event) {
|
|
113
|
+
const delta = event.shiftKey ? 50 : 10;
|
|
114
|
+
const width = event.key === 'Home' ? INSPECTOR_MIN_WIDTH : event.key === 'End' ? INSPECTOR_MAX_WIDTH : event.key === 'ArrowLeft' ? inspectorWidth + delta : event.key === 'ArrowRight' ? inspectorWidth - delta : undefined;
|
|
115
|
+
if (width === undefined)
|
|
116
|
+
return;
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
const next = inspectorWidthWithinBounds(width);
|
|
119
|
+
setInspectorWidth(next);
|
|
120
|
+
saveInspectorWidth(next);
|
|
121
|
+
}
|
|
67
122
|
const [selectedTargetId, setSelectedTargetId] = useState(restoredImportedPackage?.executionPackage?.packageId ?? restoredImportedPackage?.authoringSource?.targets[0]?.packageId ?? checkoutPackage.packageId);
|
|
68
123
|
const selectedTarget = packageCatalog.targets().find(t => t.packageId === selectedTargetId);
|
|
124
|
+
const [authoringSource, setAuthoringSource] = useState(restoredImportedPackage?.authoringSource?.serialized);
|
|
125
|
+
const [selectedRoot, setSelectedRoot] = useState('');
|
|
126
|
+
const [preparedDocument, setPreparedDocument] = useState();
|
|
127
|
+
const [preparing, setPreparing] = useState(Boolean(restoredImportedPackage?.authoringSource));
|
|
128
|
+
const [preparationError, setPreparationError] = useState();
|
|
129
|
+
useEffect(() => { let current = true; if (!authoringSource) {
|
|
130
|
+
setPreparedDocument(undefined);
|
|
131
|
+
setPreparing(false);
|
|
132
|
+
return;
|
|
133
|
+
} setPreparing(true); setPreparationError(undefined); void prepareStudioDocument(authoringSource, selectedRoot ? { kind: 'occurrence', key: selectedRoot } : { kind: 'document' }).then(doc => { if (current) {
|
|
134
|
+
setPreparedDocument(doc);
|
|
135
|
+
setPreparing(false);
|
|
136
|
+
} }).catch(error => { if (current) {
|
|
137
|
+
setPreparationError(error instanceof Error ? error.message : 'Cannot prepare this Project.');
|
|
138
|
+
setPreparing(false);
|
|
139
|
+
} }); return () => { current = false; }; }, [authoringSource, selectedRoot]);
|
|
69
140
|
const [executionPackage, setExecutionPackage] = useState(restoredImportedPackage?.executionPackage ?? checkoutPackage);
|
|
70
141
|
const [inputEdits, setInputEdits] = useState(() => inputDrafts(inspectDeclaredInputs(restoredImportedPackage?.executionPackage ?? checkoutPackage).declarations));
|
|
71
|
-
const {
|
|
142
|
+
const inputContract = preparedDocument ? { target: preparedDocument.target, inputDeclarations: preparedDocument.selection.inputDeclarations, requiredInputs: preparedDocument.selection.requiredInputs, flows: preparedDocument.selection.root.kind === 'document' ? preparedDocument.flows.map(f => ({ ...f, name: preparedDocument.context.nodes.find(n => n.key === f.occurrence).name })) : [] } : executionPackage;
|
|
143
|
+
const { declarations, conflict, unresolved } = inspectDeclaredInputs(inputContract);
|
|
144
|
+
useEffect(() => { if (preparedDocument && !conflict && !unresolved)
|
|
145
|
+
setInputEdits(inputDrafts(declarations)); }, [preparedDocument]);
|
|
72
146
|
const inputBlocker = conflict ?? unresolved;
|
|
73
147
|
let inputError;
|
|
74
148
|
let inputs = {};
|
|
@@ -82,6 +156,8 @@ function RunnerApp({ client, onExit, applicationMenu }) {
|
|
|
82
156
|
const [timeoutMs, setTimeoutMs] = useState('none');
|
|
83
157
|
const [editingInputs, setEditingInputs] = useState(false);
|
|
84
158
|
const [runState, setRunState] = useState('ready');
|
|
159
|
+
const activeInputRequest = useRef(undefined);
|
|
160
|
+
const inputsLocked = activeInputRequest.current !== undefined;
|
|
85
161
|
const [controlState, setControlState] = useState('idle');
|
|
86
162
|
const [breakpoints, setBreakpoints] = useState(new Set());
|
|
87
163
|
const [contextMenu, setContextMenu] = useState();
|
|
@@ -95,7 +171,15 @@ function RunnerApp({ client, onExit, applicationMenu }) {
|
|
|
95
171
|
const [runHistory, setRunHistory] = useState(readRunHistory);
|
|
96
172
|
const [historyRetention, setHistoryRetention] = useState(readHistoryRetention);
|
|
97
173
|
const [result, setResult] = useState();
|
|
98
|
-
const [
|
|
174
|
+
const [workspace, setWorkspace] = useState('execution');
|
|
175
|
+
const [historicalEntry, setHistoricalEntry] = useState();
|
|
176
|
+
const [historySelectedStep, setHistorySelectedStep] = useState(0);
|
|
177
|
+
const [historyCollapsedSteps, setHistoryCollapsedSteps] = useState(new Set());
|
|
178
|
+
const [loadedContexts, setLoadedContexts] = useState({});
|
|
179
|
+
const loadedContext = loadedContexts[selectedTargetId];
|
|
180
|
+
const contextTarget = preparedDocument?.target ?? loadedContext?.owner ?? selectedTarget?.target ?? executionPackage.target;
|
|
181
|
+
const readOnly = workspace === 'history';
|
|
182
|
+
const viewingHistoryEntry = readOnly ? historicalEntry?.requestId : undefined;
|
|
99
183
|
const [selectedStep, setSelectedStep] = useState(0);
|
|
100
184
|
const [collapsedSteps, setCollapsedSteps] = useState(new Set());
|
|
101
185
|
const [requestId, setRequestId] = useState();
|
|
@@ -103,43 +187,75 @@ function RunnerApp({ client, onExit, applicationMenu }) {
|
|
|
103
187
|
const [loadIssues, setLoadIssues] = useState([]);
|
|
104
188
|
const [, setCatalogVersion] = useState(0);
|
|
105
189
|
const packageSteps = executionPackage.target.kind === 'project' ? executionPackage.flows?.flatMap(flow => flow.steps) ?? [] : executionPackage.steps;
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
190
|
+
const legacyDisplaySteps = orderDisplayStepsByResult(flattenDisplaySteps(packageSteps, 0, undefined, undefined, collapsedSteps), result?.children?.flatMap(child => flattenResultSteps(child.steps)) ?? flattenResultSteps(result?.steps ?? []));
|
|
191
|
+
const liveDisplaySteps = preparedDocument ? contextDisplaySteps(preparedDocument.context, collapsedSteps, preparedDocument) : authoringSource ? [] : legacyDisplaySteps;
|
|
192
|
+
const historyPackage = undefined;
|
|
193
|
+
const shownResult = readOnly ? historicalEntry?.result : result;
|
|
194
|
+
const shownPackage = readOnly ? historyPackage : executionPackage;
|
|
195
|
+
const shownPackageSteps = shownPackage ? packageStepsFor(shownPackage) : [];
|
|
196
|
+
const shownSelectedStep = readOnly ? historySelectedStep : selectedStep;
|
|
197
|
+
const shownRequestId = readOnly ? historicalEntry?.requestId : requestId;
|
|
198
|
+
const shownRunState = readOnly ? historicalEntry?.state ?? 'ready' : runState;
|
|
199
|
+
const allResults = shownResult?.children?.flatMap(child => flattenResultSteps(child.steps)) ?? flattenResultSteps(shownResult?.steps ?? []);
|
|
200
|
+
const displaySteps = readOnly ? historicalEntry?.structure ? contextDisplaySteps(historicalEntry.structure.context, historyCollapsedSteps) : recordedDisplaySteps(allResults) : liveDisplaySteps;
|
|
201
|
+
const hasNestedSteps = displaySteps.some(step => step.depth > 0 || step.hasChildren);
|
|
202
|
+
const displayResults = displaySteps.map(step => resultForOccurrence(allResults, step.structuralAddress, step.occurrence));
|
|
203
|
+
const selectedInvocations = resultsForOccurrence(allResults, displaySteps[shownSelectedStep]?.structuralAddress, displaySteps[shownSelectedStep]?.occurrence);
|
|
204
|
+
function selectDisplayedStep(value) { if (readOnly)
|
|
205
|
+
setHistorySelectedStep(value);
|
|
206
|
+
else
|
|
207
|
+
setSelectedStep(value); }
|
|
208
|
+
function switchWorkspace(value) { setContextMenu(undefined); setWorkspace(value); }
|
|
209
|
+
function tabKey(event) {
|
|
210
|
+
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key))
|
|
211
|
+
return;
|
|
212
|
+
event.preventDefault();
|
|
213
|
+
const next = event.key === 'Home' ? 'execution' : event.key === 'End' ? 'history' : workspace === 'execution' ? 'history' : 'execution';
|
|
214
|
+
switchWorkspace(next);
|
|
215
|
+
document.getElementById(`runner-tab-${next}`)?.focus();
|
|
216
|
+
}
|
|
217
|
+
const executionEvents = events.filter(event => event.requestId === requestId);
|
|
218
|
+
const visibleEvents = executionEvents.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; });
|
|
219
|
+
function selectRoot(key) { if (activeInputRequest.current)
|
|
220
|
+
return; setSelectedRoot(key); setSelectedStep(0); stepStateCache.current = {}; setStepStates(liveDisplaySteps.map(() => 'pending')); setResult(undefined); setEvents([]); setRequestId(undefined); setRunState('ready'); setControlState('idle'); setLoadError(undefined); }
|
|
120
221
|
async function run(dryRun = false) {
|
|
121
|
-
if (inputBlocker || inputError)
|
|
222
|
+
if (activeInputRequest.current || inputBlocker || inputError || preparing || preparationError)
|
|
122
223
|
return;
|
|
123
224
|
let currentPackage;
|
|
124
225
|
try {
|
|
125
|
-
if (
|
|
126
|
-
throw new Error(
|
|
127
|
-
currentPackage = packageCatalog.prepare(executionPackage);
|
|
226
|
+
if (authoringSource && !preparedDocument)
|
|
227
|
+
throw new Error('Project preparation is not ready.');
|
|
228
|
+
currentPackage = preparedDocument ?? packageCatalog.prepare(executionPackage);
|
|
128
229
|
}
|
|
129
230
|
catch (error) {
|
|
130
231
|
setLoadError(error instanceof Error ? error.message : "Source target cannot execute.");
|
|
131
232
|
return;
|
|
132
233
|
}
|
|
133
234
|
const id = crypto.randomUUID();
|
|
134
|
-
|
|
235
|
+
activeInputRequest.current = id;
|
|
135
236
|
setRequestId(id);
|
|
136
237
|
setResult(undefined);
|
|
137
238
|
setEvents([]);
|
|
138
239
|
stepStateCache.current = Object.fromEntries(flattenDisplaySteps(packageSteps).filter(step => step.structuralAddress).map(step => [occurrenceKey(step.structuralAddress), 'pending']));
|
|
139
|
-
setStepStates(
|
|
240
|
+
setStepStates(liveDisplaySteps.map(() => 'pending'));
|
|
140
241
|
setRunState('preflighting');
|
|
141
242
|
setControlState('running');
|
|
142
|
-
|
|
243
|
+
let structure;
|
|
244
|
+
try {
|
|
245
|
+
structure = await captureHistoryStructure(currentPackage);
|
|
246
|
+
const common = { requestId: id, inputs, runtimeValues, options: { ...(timeoutMs === 'none' ? {} : { timeoutMs: Number(timeoutMs) }), dryRun } };
|
|
247
|
+
if (currentPackage.schemaVersion === 8)
|
|
248
|
+
await client.submit({ ...common, executionPackage: currentPackage, debugSession: { breakpoints: [...breakpointsRef.current], ...(stopAfterStepId ? { stopAfter: stopAfterStepId } : {}) } });
|
|
249
|
+
else
|
|
250
|
+
await client.submit({ ...common, executionPackage: currentPackage, debugSession: { breakpoints: [...breakpointsRef.current], ...(stopAfterStepId ? { stopAfterStepId } : {}) } });
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
activeInputRequest.current = undefined;
|
|
254
|
+
setRunState('failed');
|
|
255
|
+
setControlState('failed');
|
|
256
|
+
setLoadError(error instanceof Error ? error.message : 'Execution admission failed.');
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
143
259
|
let completed = false;
|
|
144
260
|
void (async () => {
|
|
145
261
|
for await (const event of client.events(id)) {
|
|
@@ -156,25 +272,42 @@ function RunnerApp({ client, onExit, applicationMenu }) {
|
|
|
156
272
|
continue;
|
|
157
273
|
setControlState(state ?? 'running');
|
|
158
274
|
}
|
|
159
|
-
if (event.
|
|
275
|
+
if ('occurrence' in event && event.occurrence) {
|
|
276
|
+
const nextState = event.state;
|
|
277
|
+
stepStateCache.current[event.occurrence] = nextState;
|
|
278
|
+
setStepStates(previous => previous.map((state, index) => liveDisplaySteps[index]?.occurrence === event.occurrence ? nextState : state));
|
|
279
|
+
}
|
|
280
|
+
else if (event.structuralAddress) {
|
|
160
281
|
const nextState = event.state;
|
|
161
282
|
stepStateCache.current[occurrenceKey(event.structuralAddress)] = nextState;
|
|
162
|
-
setStepStates(previous => previous.map((state, index) => occurrenceKey(
|
|
283
|
+
setStepStates(previous => previous.map((state, index) => occurrenceKey(liveDisplaySteps[index]?.structuralAddress) === occurrenceKey(event.structuralAddress) ? nextState : state));
|
|
163
284
|
}
|
|
164
285
|
}
|
|
165
286
|
})();
|
|
166
287
|
const secretValues = collectSecretValues(inputs, runtimeValues);
|
|
167
288
|
const executionResult = redactExecutionResult(await client.result(id), secretValues);
|
|
168
289
|
completed = true;
|
|
290
|
+
try {
|
|
291
|
+
assertHistoryIdentity(id, structure, executionResult);
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
activeInputRequest.current = undefined;
|
|
295
|
+
setRunState('failed');
|
|
296
|
+
setControlState('failed');
|
|
297
|
+
setLoadError(error.message);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (activeInputRequest.current === id)
|
|
301
|
+
activeInputRequest.current = undefined;
|
|
169
302
|
const finalSteps = flattenResultSteps(executionResult.children?.flatMap(child => child.steps) ?? executionResult.steps);
|
|
170
303
|
const finalStateById = Object.fromEntries(flattenDisplaySteps(packageSteps).filter(step => step.structuralAddress).map(step => [occurrenceKey(step.structuralAddress), stateForOccurrence(finalSteps, step.structuralAddress) ?? 'pending']));
|
|
171
304
|
stepStateCache.current = { ...stepStateCache.current, ...finalStateById };
|
|
172
305
|
setResult(executionResult);
|
|
173
|
-
setStepStates(
|
|
306
|
+
setStepStates(liveDisplaySteps.map(step => stepStateCache.current[occurrenceKey(step.structuralAddress)] ?? 'pending'));
|
|
174
307
|
setRunState(executionResult.state);
|
|
175
308
|
setControlState(executionResult.state === 'cancelled' ? 'stopped' : executionResult.state);
|
|
176
309
|
const finishedAt = executionResult.finishedAt ?? new Date().toISOString();
|
|
177
|
-
const entry = { requestId: id, startedAt: executionResult.startedAt, finishedAt, target:
|
|
310
|
+
const entry = { historyVersion: 2, structure, requestId: id, startedAt: executionResult.startedAt, finishedAt, target: currentPackage.target.name, packageId: currentPackage.packageId, artifactSchemaVersion: packageCatalog.artifactProvenance(executionPackage.packageId)?.originalVersion, artifactProvenance: packageCatalog.artifactProvenance(executionPackage.packageId), executionSchemaVersion: currentPackage.schemaVersion, state: executionResult.state, durationMs: Math.max(0, new Date(finishedAt).getTime() - new Date(executionResult.startedAt).getTime()), result: executionResult };
|
|
178
311
|
setRunHistory(previous => persistRunHistory(localStorage, HISTORY_KEY, [entry, ...previous.filter(item => item.requestId !== id)], historyRetention, secretValues));
|
|
179
312
|
}
|
|
180
313
|
async function stop() { if (requestId)
|
|
@@ -198,38 +331,53 @@ function RunnerApp({ client, onExit, applicationMenu }) {
|
|
|
198
331
|
next.add(stepId); breakpointsRef.current = next; return next; }); }
|
|
199
332
|
function toggleStopAfter(stepId) { setStopAfterStepId(previous => previous === stepId ? undefined : stepId); }
|
|
200
333
|
function exportResult(value, packageId, schemaVersion, provenance, executionVersion) { if (!value)
|
|
201
|
-
return; const exportPayload = createResultExport(value, packageId ?? 'unknown', schemaVersion, collectSecretValues(inputs, runtimeValues), provenance, executionVersion); const blob = new Blob([JSON.stringify(exportPayload, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `${value.target.name.replace(/[^a-z0-9]+/gi, '-').toLowerCase()}-${value.requestId}.result.json`; link.click(); URL.revokeObjectURL(url); }
|
|
202
|
-
function reloadHistoryEntry(entry) {
|
|
203
|
-
try {
|
|
204
|
-
loadedPackage = packageCatalog.get(entry.packageId);
|
|
205
|
-
setExecutionPackage(loadedPackage);
|
|
206
|
-
setSelectedTargetId(loadedPackage.packageId);
|
|
207
|
-
}
|
|
208
|
-
catch { /* The result remains viewable when its package is no longer loaded. */ }
|
|
209
|
-
} 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' }); }
|
|
334
|
+
return; const storedEntry = runHistory.find(entry => entry.requestId === value.requestId); const exportPayload = storedEntry?.historyVersion === 2 ? { runnerResultVersion: 2, entry: storedEntry } : createResultExport(value, packageId ?? 'unknown', schemaVersion, collectSecretValues(inputs, runtimeValues), provenance, executionVersion); const blob = new Blob([JSON.stringify(exportPayload, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `${value.target.name.replace(/[^a-z0-9]+/gi, '-').toLowerCase()}-${value.requestId}.result.json`; link.click(); URL.revokeObjectURL(url); }
|
|
335
|
+
function reloadHistoryEntry(entry) { setHistoricalEntry(entry); setHistorySelectedStep(0); setHistoryCollapsedSteps(new Set()); }
|
|
210
336
|
function clearHistory() { setRunHistory([]); localStorage.removeItem(HISTORY_KEY); }
|
|
211
337
|
function deleteHistoryEntry(requestIdToDelete) { setRunHistory(previous => { const next = previous.filter(item => item.requestId !== requestIdToDelete); localStorage.setItem(HISTORY_KEY, JSON.stringify(next)); return next; }); }
|
|
212
338
|
function changeHistoryRetention(value) { setHistoryRetention(value); localStorage.setItem(HISTORY_RETENTION_KEY, String(value)); setRunHistory(previous => { const next = previous.slice(0, value); localStorage.setItem(HISTORY_KEY, JSON.stringify(next)); return next; }); }
|
|
213
|
-
function rememberVisibleStates() {
|
|
339
|
+
function rememberVisibleStates() { liveDisplaySteps.forEach((step, index) => { const state = stepStates[index]; if (state && step.structuralAddress)
|
|
214
340
|
stepStateCache.current[occurrenceKey(step.structuralAddress)] = state; }); }
|
|
215
|
-
function toggleStep(stepId) {
|
|
341
|
+
function toggleStep(stepId) { if (readOnly) {
|
|
342
|
+
setHistoryCollapsedSteps(previous => { const next = new Set(previous); if (next.has(stepId))
|
|
343
|
+
next.delete(stepId);
|
|
344
|
+
else
|
|
345
|
+
next.add(stepId); return next; });
|
|
346
|
+
return;
|
|
347
|
+
} rememberVisibleStates(); const next = new Set(collapsedSteps); if (next.has(stepId))
|
|
216
348
|
next.delete(stepId);
|
|
217
349
|
else
|
|
218
|
-
next.add(stepId); const nextSteps = flattenDisplaySteps(packageSteps, 0, undefined, undefined, next); setCollapsedSteps(next); setStepStates(nextSteps.map(step => stepStateCache.current[occurrenceKey(step.structuralAddress)] ?? 'pending')); }
|
|
219
|
-
function setAllCollapsed(collapsed) {
|
|
350
|
+
next.add(stepId); const nextSteps = preparedDocument ? contextDisplaySteps(preparedDocument.context, next, preparedDocument) : flattenDisplaySteps(packageSteps, 0, undefined, undefined, next); setCollapsedSteps(next); setStepStates(nextSteps.map(step => stepStateCache.current[occurrenceKey(step.structuralAddress)] ?? 'pending')); }
|
|
351
|
+
function setAllCollapsed(collapsed) { if (readOnly) {
|
|
352
|
+
setHistoryCollapsedSteps(collapsed ? new Set(historicalEntry?.structure?.context.nodes.filter(n => n.children.length).map(n => n.key) ?? containerStepIds(shownPackageSteps)) : new Set());
|
|
353
|
+
return;
|
|
354
|
+
} rememberVisibleStates(); const next = collapsed ? new Set(preparedDocument?.context.nodes.filter(n => n.children.length).map(n => n.key) ?? containerStepIds(packageSteps)) : new Set(); const nextSteps = preparedDocument ? contextDisplaySteps(preparedDocument.context, next, preparedDocument) : flattenDisplaySteps(packageSteps, 0, undefined, undefined, next); setCollapsedSteps(next); setStepStates(nextSteps.map(step => stepStateCache.current[occurrenceKey(step.structuralAddress)] ?? 'pending')); }
|
|
220
355
|
async function loadPackage(file) {
|
|
221
|
-
if (!file)
|
|
356
|
+
if (!file || activeInputRequest.current)
|
|
222
357
|
return;
|
|
223
358
|
try {
|
|
224
|
-
const
|
|
359
|
+
const text = await file.text();
|
|
360
|
+
if (activeInputRequest.current)
|
|
361
|
+
return;
|
|
362
|
+
const target = loadTarget(text);
|
|
225
363
|
const loaded = target.executionPackage;
|
|
364
|
+
const owner = target.authoringSource?.targets.find(item => item.target.kind === 'project')?.target ?? loaded?.target ?? target.authoringSource?.targets[0]?.target;
|
|
365
|
+
setLoadedContexts(previous => ({ ...previous, ...Object.fromEntries([loaded?.packageId, ...(target.authoringSource?.targets ?? []).map(item => item.packageId)].filter((id) => Boolean(id)).map(id => [id, { filename: file.name, owner }])) }));
|
|
226
366
|
const selected = packageCatalog.addLoadedTarget(target);
|
|
227
367
|
setSelectedTargetId(selected);
|
|
368
|
+
setAuthoringSource(target.authoringSource?.serialized);
|
|
369
|
+
setSelectedRoot('');
|
|
370
|
+
setPreparedDocument(undefined);
|
|
371
|
+
setPreparing(Boolean(target.authoringSource));
|
|
372
|
+
setResult(undefined);
|
|
373
|
+
setEvents([]);
|
|
374
|
+
setBreakpoints(new Set());
|
|
375
|
+
breakpointsRef.current = new Set();
|
|
376
|
+
setStopAfterStepId(undefined);
|
|
228
377
|
if (!loaded) {
|
|
229
378
|
localStorage.setItem(IMPORTED_PACKAGE_KEY, JSON.stringify(target));
|
|
230
379
|
setCatalogVersion(v => v + 1);
|
|
231
380
|
setRequestId(undefined);
|
|
232
|
-
setViewingHistoryEntry(undefined);
|
|
233
381
|
setControlState("idle");
|
|
234
382
|
setRunState("ready");
|
|
235
383
|
setLoadError(undefined);
|
|
@@ -252,18 +400,17 @@ function RunnerApp({ client, onExit, applicationMenu }) {
|
|
|
252
400
|
setEvents([]);
|
|
253
401
|
setResult(undefined);
|
|
254
402
|
setRequestId(undefined);
|
|
255
|
-
setViewingHistoryEntry(undefined);
|
|
256
403
|
setControlState('idle');
|
|
257
404
|
setRunState('ready');
|
|
258
405
|
setLoadError(undefined);
|
|
259
406
|
setLoadIssues([]);
|
|
260
407
|
}
|
|
261
408
|
catch (error) {
|
|
262
|
-
setLoadError(error instanceof PackageLoadError ? `${error.category}: ${error.message}` : 'Unable to load the selected
|
|
409
|
+
setLoadError(error instanceof PackageLoadError ? `${error.category}: ${error.message}` : 'Unable to load the selected Project.');
|
|
263
410
|
setLoadIssues(error instanceof PackageLoadError ? error.issues : []);
|
|
264
411
|
}
|
|
265
412
|
}
|
|
266
|
-
const statusLabel =
|
|
413
|
+
const statusLabel = shownRunState === 'ready' ? 'Ready' : shownRunState[0].toUpperCase() + shownRunState.slice(1);
|
|
267
414
|
useEffect(() => {
|
|
268
415
|
document.documentElement.dataset.theme = theme;
|
|
269
416
|
document.documentElement.style.colorScheme = theme;
|
|
@@ -276,11 +423,11 @@ function RunnerApp({ client, onExit, applicationMenu }) {
|
|
|
276
423
|
useEffect(() => { const close = () => setContextMenu(undefined); const keydown = (event) => { if (event.key === 'Escape')
|
|
277
424
|
close(); }; document.addEventListener('click', close); document.addEventListener('keydown', keydown); return () => { document.removeEventListener('click', close); document.removeEventListener('keydown', keydown); }; }, []);
|
|
278
425
|
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)
|
|
279
|
-
|
|
426
|
+
selectDisplayedStep(index); }; document.addEventListener('click', navigateIssue); return () => document.removeEventListener('click', navigateIssue); }, [displaySteps]);
|
|
280
427
|
const projectFileInput = useRef(null);
|
|
281
428
|
const menuActions = useRef({ enabled: () => false, openProject: async () => { }, about: () => { }, demo: () => { } });
|
|
282
429
|
menuActions.current = {
|
|
283
|
-
enabled: id => runnerMenus.some(menu => menu.items.some(([command]) => command === id)) && (!showAbout || id.startsWith('help.')),
|
|
430
|
+
enabled: id => runnerMenus.some(menu => menu.items.some(([command]) => command === id)) && (!showAbout || id.startsWith('help.')) && (id !== 'project.open' || !inputsLocked),
|
|
284
431
|
openProject: file => loadPackage(new File([file.text], file.name, { type: 'application/json' })),
|
|
285
432
|
about: () => setShowAbout(true),
|
|
286
433
|
// Preserve the current standalone/combined destination exactly.
|
|
@@ -293,16 +440,29 @@ function RunnerApp({ client, onExit, applicationMenu }) {
|
|
|
293
440
|
about: () => menuActions.current.about(),
|
|
294
441
|
demo: () => menuActions.current.demo(),
|
|
295
442
|
}), [registerMenu]);
|
|
296
|
-
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" })] }), !applicationMenu?.native && _jsx(HeaderHelp, { enabled: id => menuActions.current.enabled(id), onAbout: () => menuActions.current.about(), onOpenProject: () => projectFileInput.current?.click() }), _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" }), _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") {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
} }, 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 ${displayState.replaceAll(' ', '-')}${isLoop ? ' loop-icon' : ''}`, children: statusGlyph[displayState] }), _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: "\u21BB 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.replaceAll(' ', '-')}`, 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." })] }) })] });
|
|
443
|
+
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" })] }), !applicationMenu?.native && _jsx(HeaderHelp, { enabled: id => menuActions.current.enabled(id), onAbout: () => menuActions.current.about(), onOpenProject: () => projectFileInput.current?.click() }), _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" }), _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); } }), _jsx("div", { className: "runner-tabs", role: "tablist", "aria-label": "Runner workspace", children: ['execution', 'history'].map(tab => _jsx("button", { id: `runner-tab-${tab}`, role: "tab", "aria-selected": workspace === tab, "aria-controls": `runner-panel-${tab}`, tabIndex: workspace === tab ? 0 : -1, onKeyDown: tabKey, onClick: () => switchWorkspace(tab), children: tab === 'execution' ? 'Execution' : 'History' }, tab)) }), _jsx("div", { className: "runner-pane-scroll", role: "region", "aria-label": "Runner workspace and Inspector", tabIndex: 0, children: _jsxs("main", { style: { gridTemplateColumns: `minmax(560px, 1fr) 8px ${inspectorWidth}px`, minWidth: `${568 + inspectorWidth}px` }, className: "runner-main runner-tabbed-main", id: `runner-panel-${workspace}`, role: "tabpanel", "aria-labelledby": `runner-tab-${workspace}`, children: [_jsxs(_Fragment, { children: [_jsxs("section", { className: "runner-workspace", children: [readOnly && loadError && _jsx("p", { className: "load-error", role: "alert", children: loadError }), readOnly && loadIssues.length > 0 && _jsx(PreRunValidation, { issues: loadIssues, title: "Artifact validation" }), readOnly && historicalEntry && !historicalEntry.structure && _jsx("p", { className: "notice", children: "Original Project structure was not recorded for this run. Only recorded execution evidence is available." }), readOnly && _jsx(HistoryPanel, { entries: runHistory, onReload: reloadHistoryEntry, retention: historyRetention, onRetentionChange: changeHistoryRetention, onClear: clearHistory, onDelete: deleteHistoryEntry, onExport: exportResult }), _jsxs("div", { hidden: readOnly, children: [_jsx("h1", { children: contextTarget.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: ["Run only", _jsxs("select", { disabled: inputsLocked || preparing, value: selectedRoot, onChange: event => selectRoot(event.target.value), children: [_jsx("option", { value: "", children: "Entire project" }), preparedDocument && contextDisplaySteps(preparedDocument.context, new Set(), preparedDocument).filter(n => n.kind !== 'body' && n.kind !== 'branch').map(n => _jsxs("option", { value: n.id, children: [' '.repeat(n.depth), n.name] }, n.id))] })] }), _jsxs("label", { children: ["Timeout", _jsxs("select", { disabled: inputsLocked, 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("div", { className: "run-actions", role: "group", "aria-label": "Execution actions", children: [_jsx("button", { className: "button", disabled: inputsLocked, onClick: () => { if (!activeInputRequest.current)
|
|
444
|
+
setEditingInputs(value => !value); }, children: editingInputs ? 'Done inputs' : 'Edit inputs' }), _jsx("button", { className: "button primary", onClick: () => void run(), disabled: !!inputBlocker || preparing || !!preparationError || runState === 'preflighting' || runState === 'running', children: "\u25B6\u00A0 Run" }), _jsx("button", { className: "button dry-run", onClick: () => void run(true), disabled: !!inputBlocker || preparing || !!preparationError || 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"] })] })] }), preparing && _jsx("p", { role: "status", children: "Preparing Project\u2026" }), preparationError && _jsx("p", { role: "alert", children: preparationError }), preparedDocument && !preparedDocument.selection.executable && _jsx("section", { role: "alert", children: preparedDocument.selection.issues.map((i, index) => _jsxs("p", { children: [preparedDocument.context.nodes.find(n => n.key === i.at)?.name, ": ", i.message] }, index)) }), 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: inputsLocked ? 'Inputs are locked while execution is active.' : 'Values are sent with the next run' })] }), _jsxs("fieldset", { className: "input-grid input-lock-fields", "aria-label": "Invocation input values", disabled: inputsLocked, 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) => { if (!activeInputRequest.current)
|
|
445
|
+
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 => { if (!activeInputRequest.current)
|
|
446
|
+
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 => { if (!activeInputRequest.current)
|
|
447
|
+
setRuntimeValues(previous => ({ ...previous, [name]: value })); } }, name))] })] })] }), viewingHistoryEntry && _jsxs("div", { className: "history-view-banner", children: ["Viewing stored results \u00B7 ", viewingHistoryEntry] }), _jsxs("section", { className: "panel", hidden: readOnly && !historicalEntry, children: [_jsxs("div", { className: "panel-heading", children: [_jsx("h2", { children: readOnly ? 'Recorded execution' : 'Live Execution' }), _jsx("span", { className: "panel-spacer" }), _jsxs("span", { className: "execution-menu-label", hidden: readOnly, children: ["Right-click a row to manage its breakpoint \u00B7 Selected: ", displaySteps[shownSelectedStep]?.name ?? 'None'] }), _jsxs("span", { className: shownRunState === 'running' ? 'running' : shownRunState === '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, shownResult ? allResults : eventPresentationEvidence(events, requestId), Boolean(shownResult), step.occurrence) : []; const rowEvidence = shownResult ? allResults : eventPresentationEvidence(events, requestId); const repeatedMatches = rowEvidence.filter(r => step.occurrence ? r.occurrence === step.occurrence : 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 scope = readOnly ? historicalEntry?.structure?.scope : preparedDocument?.selection.root; const context = readOnly ? historicalEntry?.structure?.context : preparedDocument?.context; const outside = Boolean(step.occurrence && scope && context && !scopeIncludes(context, scope, step.occurrence)); const presentation = rowPresentation(step, rowEvidence, Boolean(shownResult), step.branchRow ? 'pending' : readOnly ? 'pending' : stepStates[index] ?? 'pending', { context, scope, result: shownResult }); const displayState = presentation.state; const isLoopContent = Boolean(step.loopId); const isLoopEnd = isLoopContent && displaySteps[index + 1]?.loopId !== step.loopId; return _jsxs("tr", { className: `${outside ? 'outside-scope ' : ''}${shownSelectedStep === 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") {
|
|
448
|
+
event.preventDefault();
|
|
449
|
+
selectDisplayedStep(Math.min(displaySteps.length - 1, index + 1));
|
|
450
|
+
} if (event.key === "ArrowUp") {
|
|
451
|
+
event.preventDefault();
|
|
452
|
+
selectDisplayedStep(Math.max(0, index - 1));
|
|
453
|
+
} }, onClick: () => selectDisplayedStep(index), onContextMenu: event => { event.preventDefault(); if (readOnly)
|
|
454
|
+
return; selectDisplayedStep(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 ${displayState.replaceAll(' ', '-')}${isLoop ? ' loop-icon' : ''}`, children: statusGlyph[displayState] }), _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": `${(readOnly ? historyCollapsedSteps : collapsedSteps).has(step.id) ? 'Expand' : 'Collapse'} ${step.name}`, onClick: event => { event.stopPropagation(); toggleStep(step.id); }, children: (readOnly ? historyCollapsedSteps : collapsedSteps).has(step.id) ? '+' : '−' }), !step.hasChildren && _jsx("span", { className: "collapse-spacer", "aria-hidden": "true" }), _jsx("span", { children: step.name }), !readOnly && 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: "\u21BB LOOP" }), step.depth > 0 ? _jsx("small", { className: "kind-label nested-label", children: "NESTED STEP" }) : !isLoop && _jsx("small", { className: "kind-label", children: step.kind.toUpperCase() })] }), shownSelectedStep === index && _jsx("small", { className: "selected-label", children: "SELECTED" })] }), _jsxs("td", { children: [!outside && 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.replaceAll(' ', '-')}`, children: presentation.label }), latestQualifier && _jsx("small", { className: "latest-invocation", children: "Latest invocation" })] }), _jsx("td", { children: displayResults[index] ? new Date(shownResult?.finishedAt ?? '').toLocaleTimeString() : '—' }), _jsx("td", { children: displayResults[index] ? `${displayResults[index].durationMs} ms` : '—' })] }, `${step.id}-${index}`); }) })] }) }), !readOnly && 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" })] })] }), !readOnly && _jsxs("details", { className: "panel runner-event-log execution-event-log", children: [_jsx("summary", { children: "Event Log" }), _jsxs("div", { className: "event-log-content", children: [_jsxs("div", { className: "event-log-tools", children: [_jsx("input", { className: "event-search", "aria-label": "Search event log", placeholder: "Search events", value: eventSearch, onChange: event => setEventSearch(event.target.value) }), _jsxs("select", { "aria-label": "Filter execution events", 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: "event-log-scroll", role: "region", "aria-label": "Execution events", tabIndex: 0, children: _jsx("ul", { className: "event-log-list", children: visibleEvents.map(event => _jsx("li", { children: _jsxs("button", { type: "button", className: "event-row", children: [_jsxs("span", { className: "event-row-meta", children: [_jsx("time", { dateTime: event.timestamp, children: new Date(event.timestamp).toLocaleTimeString() }), _jsx("code", { children: event.type.toUpperCase() }), _jsx("span", { className: "event-state", children: event.state.toUpperCase() })] }), _jsx("span", { className: "event-message", children: event.message }), event.parallel && _jsxs("small", { className: "parallel-event", children: ["Parallel ", event.parallel.completed, "/", event.parallel.total, " (", event.parallel.join, ")"] })] }) }, event.sequence)) }) }), executionEvents.length > 0 && visibleEvents.length === 0 && _jsx("p", { className: "empty-state", children: "No events match the current filter." }), executionEvents.length === 0 && _jsx("p", { className: "empty-state", children: "No execution events yet." })] })] }, JSON.stringify(['execution', requestId ?? 'ready', selectedTargetId]))] }), _jsx("div", { className: "inspector-divider", role: "separator", "aria-label": "Resize Inspector", "aria-orientation": "vertical", "aria-controls": "runner-inspector", "aria-valuemin": INSPECTOR_MIN_WIDTH, "aria-valuemax": INSPECTOR_MAX_WIDTH, "aria-valuenow": inspectorWidth, "aria-valuetext": `${inspectorWidth} pixels`, tabIndex: 0, hidden: readOnly && !historicalEntry, onPointerDown: startInspectorResize, onPointerMove: moveInspectorResize, onPointerUp: finishInspectorResize, onPointerCancel: finishInspectorResize, onLostPointerCapture: finishInspectorResize, onKeyDown: inspectorResizeKey }), _jsxs("aside", { id: "runner-inspector", className: "inspector", hidden: readOnly && !historicalEntry, children: [_jsx("h2", { children: "Selected execution" }), _jsx("p", { className: "selected-detail-name", children: _jsx("strong", { children: displaySteps[shownSelectedStep]?.name ?? 'No step selected' }) }), _jsxs("p", { className: "run-id", children: ["Run ID: ", shownRequestId ?? 'No run started'] }), _jsx(InspectorSection, { title: "Artifact summary", children: _jsx(KeyValues, { values: ["Target", (readOnly ? historicalEntry?.result.target.name : executionPackage.target.name) ?? 'Unknown', "Type", (readOnly ? historicalEntry?.result.target.kind : executionPackage.target.kind) ?? 'Unknown', "Project reference", (readOnly ? historicalEntry?.packageId : executionPackage.packageId) ?? 'Not recorded', "Execution format", `Execution v${readOnly ? historicalEntry?.executionSchemaVersion ?? 'unknown' : preparedDocument?.schemaVersion ?? executionPackage.schemaVersion}`, "Definitions", shownPackage ? String(shownPackage.definitions.length) : 'Source Project unavailable', "Required inputs", shownPackage ? shownPackage.requiredInputs?.join(', ') || 'None' : 'Not recorded', "Runtime values", shownPackage ? shownPackage.runtimeRequirements?.join(', ') || 'None' : 'Not recorded'] }) }), _jsx(InspectorSection, { title: "Run outcome", children: _jsxs("div", { className: "notice", children: [_jsx("strong", { children: shownResult?.state.toUpperCase() ?? 'NOT STARTED' }), _jsx("br", {}), shownResult ? shownResult.state : 'Run the Project to inspect its terminal outcome.'] }) }), _jsx(InspectorSection, { title: "Selected step inputs", children: _jsx(KeyValues, { values: Object.entries(displayResults[shownSelectedStep]?.inputs ?? (selectedInvocations.length > 1 ? {} : readOnly ? {} : inputs)).flatMap(([key, value]) => [key, String(value)]) }) }), _jsx(InspectorSection, { title: "Selected step outputs", children: _jsx(KeyValues, { values: displayResults[shownSelectedStep]?.outputs ? Object.entries(displayResults[shownSelectedStep]?.outputs ?? {}).flatMap(([key, value]) => [key, String(value)]) : ['—', selectedInvocations.length > 1 ? `${selectedInvocations.length} recorded invocations; see iteration history` : 'Available after execution'] }) }), (shownResult?.error || displayResults[shownSelectedStep]?.error) && _jsx(InspectorSection, { title: "Error", children: _jsxs("div", { className: "error-notice", children: [_jsx("strong", { children: displayResults[shownSelectedStep]?.error?.code ?? shownResult?.error?.code }), _jsx("br", {}), displayResults[shownSelectedStep]?.error?.message ?? shownResult?.error?.message] }) }), shownResult?.issues !== undefined && _jsx(InspectorSection, { title: "Pre-run validation", children: _jsx(PreRunValidation, { issues: shownResult.issues }) }), _jsx(InspectorSection, { title: "Invocation path", children: _jsx("div", { className: "notice", children: displayResults[shownSelectedStep]?.invocationPath?.join(" › ") ?? "No invocation path available." }) }), _jsx(InspectorSection, { title: "Iteration history", children: _jsx(RecordedInvocations, { results: selectedInvocations, definition: findInspectionDefinition(shownPackageSteps, displaySteps[shownSelectedStep]?.structuralAddress) }, JSON.stringify([shownRequestId, displaySteps[shownSelectedStep]?.structuralAddress])) }), _jsx(InspectorSection, { title: "Execution timing", children: _jsxs("div", { className: "timing-details", children: [_jsxs("span", { children: ["Wall ", shownResult?.executionTiming?.wallDurationMs ?? "—", " ms"] }), _jsxs("span", { children: ["Active ", shownResult?.executionTiming?.activeDurationMs ?? "—", " ms"] }), _jsxs("span", { children: ["Paused ", shownResult?.executionTiming?.pausedDurationMs ?? "—", " ms"] })] }) }), _jsx(InspectorSection, { title: "Selected step attempts", children: _jsx("div", { className: "attempt-history", children: displayResults[shownSelectedStep]?.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." }) }) }), readOnly && _jsxs("details", { className: "inspector-section inspector-event-log runner-event-log", children: [_jsx("summary", { children: "Event Log" }), _jsx("div", { className: "event-log-content", children: _jsx("p", { className: "empty-state", children: "Recorded events are unavailable for this run." }) })] }, JSON.stringify(['history', shownRequestId ?? 'ready', historicalEntry?.packageId])), _jsxs(InspectorSection, { title: "Selected step", children: [_jsxs("div", { className: "notice", children: [_jsx("strong", { children: displaySteps[shownSelectedStep]?.name ?? 'No step selected' }), _jsx("br", {}), displayResults[shownSelectedStep] ? displayResults[shownSelectedStep].error ? 'This step failed. See the error details above.' : `Completed in ${displayResults[shownSelectedStep].durationMs} ms.` : selectedInvocations.length > 1 ? `${selectedInvocations.length} invocations recorded. No single invocation selected.` : 'Run the Project to inspect execution details.', _jsxs("div", { className: "inspector-actions", children: [_jsx("button", { className: "button", onClick: () => void retrySelected(), disabled: readOnly || !!preparedDocument || !displayResults[shownSelectedStep] || (!displayResults[shownSelectedStep]?.error && runState !== "completed"), children: "Retry Action" }), _jsx("button", { className: `button ${stopAfterStepId === displaySteps[shownSelectedStep]?.id ? "active" : ""}`, onClick: () => displaySteps[shownSelectedStep] && toggleStopAfter(displaySteps[shownSelectedStep].id), disabled: readOnly || !displaySteps[shownSelectedStep], children: "Stop After" }), _jsx("button", { className: "button", onClick: () => void runFromSelected(), disabled: readOnly || !!preparedDocument || !displayResults[shownSelectedStep] || runState === "running", children: "Run From Here" })] })] }), _jsxs("div", { className: "selection-nav", children: [_jsx("button", { className: "button", disabled: shownSelectedStep <= 0, onClick: () => selectDisplayedStep(index => Math.max(0, index - 1)), children: "\u2039 Previous" }), _jsx("span", { children: displaySteps.length ? `${shownSelectedStep + 1} / ${displaySteps.length}` : '0 / 0' }), _jsx("button", { className: "button", disabled: shownSelectedStep >= displaySteps.length - 1, onClick: () => selectDisplayedStep(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 Projects." }), _jsxs("small", { children: [RUNNER_UI_VERSION, " \u00B7 ", RUNNER_BUILD_LABEL, " \u00B7 QAick contract"] }), _jsx("footer", { children: "\u00A9 2006\u20132026 QAick. All rights reserved." })] }) })] });
|
|
303
455
|
}
|
|
304
|
-
function
|
|
305
|
-
|
|
456
|
+
function recordedDisplaySteps(results) {
|
|
457
|
+
const unique = new Map();
|
|
458
|
+
for (const step of results) {
|
|
459
|
+
const key = step.occurrence ?? occurrenceKey(step.structuralAddress);
|
|
460
|
+
if (!unique.has(key))
|
|
461
|
+
unique.set(key, { occurrence: step.occurrence, id: step.occurrence ?? step.id, name: step.name, kind: step.kind ?? 'action', structuralAddress: step.structuralAddress, depth: Math.max(0, (step.structuralAddress?.segments.filter(segment => segment.kind === 'step').length ?? 1) - 1), hasChildren: false });
|
|
462
|
+
}
|
|
463
|
+
return [...unique.values()];
|
|
464
|
+
}
|
|
465
|
+
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.structure && _jsxs("span", { children: ["Scope: ", entry.structure.scope.kind === 'document' ? 'Entire project' : entry.structure.context.nodes.find(n => n.key === entry.structure.scope.key)?.name ?? 'Unavailable', " \u00B7 ", entry.structure.context.coverage, _jsx("br", {})] }), 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.structure?.documentHash ?? 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." })] }); }
|
|
306
466
|
function InspectorSection({ title, children }) { return _jsxs("section", { className: "inspector-section", children: [_jsx("h3", { children: title }), children] }); }
|
|
307
467
|
function KeyValues({ values }) { return _jsx("div", { className: "key-values", children: redactKeyValuePairs(values).map((value, index) => _jsx("span", { children: value }, `${value}-${index}`)) }); }
|
|
308
468
|
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 })] }); }
|
|
@@ -314,7 +474,7 @@ function ParallelGroupDetails({ result }) { const group = result?.terminalReason
|
|
|
314
474
|
function ResultSummary({ result }) { if (!result)
|
|
315
475
|
return null; const steps = flattenResultSteps([result]); const counts = Object.fromEntries(['finished', 'failed', 'skipped', 'cancelled'].map(state => [state, steps.filter(step => step.state === state).length])); return _jsxs("div", { className: "result-summary", children: [_jsx("strong", { children: "Result summary" }), _jsxs("span", { children: [steps.length, " invocations \u00B7 ", result.durationMs, " ms"] }), _jsxs("small", { children: ["Finished ", counts.finished, " \u00B7 Failed ", counts.failed, " \u00B7 Skipped ", counts.skipped, " \u00B7 Cancelled ", counts.cancelled] })] }); }
|
|
316
476
|
function PreRunValidation({ issues, title = 'Pre-run validation' }) { if (!issues.length)
|
|
317
|
-
return _jsxs("section", { className: "validation-panel validation-pass", children: [_jsx("span", { className: "validation-icon", children: "\u2713" }), _jsxs("div", { children: [_jsxs("strong", { children: [title, " passed"] }), _jsx("small", { children: "No issues found. The next Run can invoke adapters." })] })] }); return _jsxs("section", { className: "validation-panel validation-failed", children: [_jsxs("div", { className: "validation-heading", children: [_jsx("span", { className: "validation-icon", children: "!" }), _jsxs("div", { children: [_jsxs("strong", { children: [title, " found ", issues.length, " issue", issues.length === 1 ? '' : 's'] }), _jsx("small", { children: "Resolve these items before running the
|
|
477
|
+
return _jsxs("section", { className: "validation-panel validation-pass", children: [_jsx("span", { className: "validation-icon", children: "\u2713" }), _jsxs("div", { children: [_jsxs("strong", { children: [title, " passed"] }), _jsx("small", { children: "No issues found. The next Run can invoke adapters." })] })] }); return _jsxs("section", { className: "validation-panel validation-failed", children: [_jsxs("div", { className: "validation-heading", children: [_jsx("span", { className: "validation-icon", children: "!" }), _jsxs("div", { children: [_jsxs("strong", { children: [title, " found ", issues.length, " issue", issues.length === 1 ? '' : 's'] }), _jsx("small", { children: "Resolve these items before running the Project." })] })] }), _jsx("div", { className: "validation-issues", children: issues.map((issue, index) => _jsxs("div", { className: "validation-issue", "data-step-id": issue.stepId, children: [_jsx("span", { className: "issue-icon", children: "\u00D7" }), _jsxs("div", { children: [_jsxs("strong", { children: [diagnosticCategory(issue.code), " \u00B7 ", issue.code] }), _jsx("span", { children: issue.message }), issue.path && _jsxs("small", { children: ["Path: ", issue.path] }), issue.stepId && _jsxs("small", { children: ["Step: ", issue.stepId, " \u00B7 click to inspect"] })] })] }, `${issue.code}-${issue.stepId ?? issue.path ?? 'run'}-${index}`)) })] }); }
|
|
318
478
|
function diagnosticCategory(code) { if (code === 'CONTRACT_INVALID' || code === 'UNSUPPORTED_SCHEMA')
|
|
319
479
|
return 'contract-invalid'; if (code === 'INVALID_BINDING' || code === 'MISSING_INPUT_BINDING' || code === 'MISSING_STEP_BINDING' || code === 'MISSING_STEP_OUTPUT' || code === 'BINDING_FAILED')
|
|
320
480
|
return 'binding-unresolved'; if (code.startsWith('HTTP_') || code === 'ACTION_FAILED' || code === 'MISSING_ACTION_ADAPTER')
|
|
@@ -340,8 +500,8 @@ function flattenDisplaySteps(steps, depth = 0, loopId, parentId, collapsed = new
|
|
|
340
500
|
}
|
|
341
501
|
export function orderDisplayStepsByResult(steps, _results) { return steps; }
|
|
342
502
|
export const occurrenceKey = (address) => address ? JSON.stringify([address.scope, address.segments]) : '';
|
|
343
|
-
export function resultsForOccurrence(results, address) { return results.filter(r => address && occurrenceKey(r.structuralAddress) === occurrenceKey(address)); }
|
|
344
|
-
export function resultForOccurrence(results, address) { const matches = resultsForOccurrence(results, address); return matches.length === 1 ? matches[0] : undefined; }
|
|
503
|
+
export function resultsForOccurrence(results, address, occurrence) { return results.filter(r => occurrence ? r.occurrence === occurrence : address && occurrenceKey(r.structuralAddress) === occurrenceKey(address)); }
|
|
504
|
+
export function resultForOccurrence(results, address, occurrence) { const matches = resultsForOccurrence(results, address, occurrence); return matches.length === 1 ? matches[0] : undefined; }
|
|
345
505
|
export function stateForOccurrence(results, address) {
|
|
346
506
|
const matches = resultsForOccurrence(results, address);
|
|
347
507
|
if (!matches.length)
|
|
@@ -379,4 +539,9 @@ export function RecordedInvocations({ results, definition }) {
|
|
|
379
539
|
}
|
|
380
540
|
function containerStepIds(steps) { return steps.flatMap(step => { const branchIds = step.control?.branches?.flatMap(branch => [`${step.id}.${branch.id}`, ...containerStepIds(branch.children)]) ?? []; return step.children?.length || branchIds.length ? [step.id, ...branchIds, ...(step.children ? containerStepIds(step.children) : [])] : []; }); }
|
|
381
541
|
function flattenResultSteps(steps) { return steps.flatMap(step => [step, ...(step.children ? flattenResultSteps(step.children) : [])]); }
|
|
542
|
+
function contextDisplaySteps(context, collapsed, document) {
|
|
543
|
+
const byKey = new Map(context.nodes.map(n => [n.key, n]));
|
|
544
|
+
function visit(key, depth) { const n = byKey.get(key); const visible = !['project', 'body'].includes(n.kind); const payload = document?.nodes.find(p => p.occurrence === key); const row = { id: key, occurrence: key, name: n.name, kind: n.kind === 'project' ? 'flow' : n.kind === 'unknown' ? 'action' : n.kind, depth, structuralAddress: n.originalAddress, hasChildren: n.children.length > 0, loopRepeat: payload?.control?.type === 'repeat', loopTotal: payload?.control?.iterations, loopDuration: payload?.control?.durationMs }; return [...(visible ? [row] : []), ...(collapsed.has(key) ? [] : [...(n.sequence ?? []), ...n.children.filter(child => !n.sequence?.includes(child))].flatMap(child => visit(child, depth + (visible ? 1 : 0))))]; }
|
|
545
|
+
return visit(context.root, 0);
|
|
546
|
+
}
|
|
382
547
|
//# sourceMappingURL=RunnerModule.js.map
|