@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.
Files changed (37) hide show
  1. package/package-dist/package.json +5 -5
  2. package/package-dist/src/demo/RunnerWalkthrough.js +4 -4
  3. package/package-dist/src/demo/RunnerWalkthrough.js.map +1 -1
  4. package/package-dist/src/runner/RunnerModule.d.ts +4 -3
  5. package/package-dist/src/runner/RunnerModule.d.ts.map +1 -1
  6. package/package-dist/src/runner/RunnerModule.js +228 -63
  7. package/package-dist/src/runner/RunnerModule.js.map +1 -1
  8. package/package-dist/src/runner/catalog.js +1 -1
  9. package/package-dist/src/runner/execution-context.d.ts +1 -1
  10. package/package-dist/src/runner/execution-context.d.ts.map +1 -1
  11. package/package-dist/src/runner/execution-context.js +3 -3
  12. package/package-dist/src/runner/execution-context.js.map +1 -1
  13. package/package-dist/src/runner/history.d.ts +20 -1
  14. package/package-dist/src/runner/history.d.ts.map +1 -1
  15. package/package-dist/src/runner/history.js +14 -0
  16. package/package-dist/src/runner/history.js.map +1 -1
  17. package/package-dist/src/runner/invocation-inputs.d.ts +6 -2
  18. package/package-dist/src/runner/invocation-inputs.d.ts.map +1 -1
  19. package/package-dist/src/runner/invocation-inputs.js.map +1 -1
  20. package/package-dist/src/runner/loop-presentation.d.ts +4 -4
  21. package/package-dist/src/runner/loop-presentation.d.ts.map +1 -1
  22. package/package-dist/src/runner/loop-presentation.js +4 -4
  23. package/package-dist/src/runner/loop-presentation.js.map +1 -1
  24. package/package-dist/src/runner/presentation-state.d.ts +19 -4
  25. package/package-dist/src/runner/presentation-state.d.ts.map +1 -1
  26. package/package-dist/src/runner/presentation-state.js +68 -3
  27. package/package-dist/src/runner/presentation-state.js.map +1 -1
  28. package/package-dist/src/runner/runtime-client-adapter.d.ts +1 -1
  29. package/package-dist/src/runner/studio-execution-document.d.ts +4 -0
  30. package/package-dist/src/runner/studio-execution-document.d.ts.map +1 -0
  31. package/package-dist/src/runner/studio-execution-document.js +239 -0
  32. package/package-dist/src/runner/studio-execution-document.js.map +1 -0
  33. package/package-dist/src/runner/studio-hierarchy-loader.js +2 -2
  34. package/package-dist/src/runner/types.d.ts +26 -6
  35. package/package-dist/src/runner/types.d.ts.map +1 -1
  36. package/package-dist/styles.css +55 -27
  37. 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 { presentationState, eventPresentationEvidence } from "./presentation-state.js";
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 [environment, setEnvironment] = useState('test');
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 { declarations, conflict, unresolved } = inspectDeclaredInputs(executionPackage);
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 [viewingHistoryEntry, setViewingHistoryEntry] = useState();
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 allResults = result?.children?.flatMap(child => flattenResultSteps(child.steps)) ?? flattenResultSteps(result?.steps ?? []);
107
- const displaySteps = orderDisplayStepsByResult(flattenDisplaySteps(packageSteps, 0, undefined, undefined, collapsedSteps), allResults);
108
- const hasNestedSteps = flattenDisplaySteps(packageSteps).some(step => step.depth > 0);
109
- const displayResults = displaySteps.map(step => resultForOccurrence(allResults, step.structuralAddress));
110
- const selectedInvocations = resultsForOccurrence(allResults, displaySteps[selectedStep]?.structuralAddress);
111
- const visibleEvents = events.filter(event => { const categoryMatches = eventFilter === 'all' || eventFilter === 'parallel' && event.parallel || eventFilter === 'steps' && event.stepId || eventFilter === 'lifecycle' && !event.stepId && !event.parallel || eventFilter === 'failures' && (event.state === 'failed' || event.type.includes('error')); const searchMatches = !eventSearch.trim() || `${event.type} ${event.state} ${event.message} ${event.stepName ?? ''}`.toLowerCase().includes(eventSearch.trim().toLowerCase()); return categoryMatches && searchMatches; });
112
- function selectTarget(id) { setSelectedTargetId(id); const descriptor = packageCatalog.targets().find(t => t.packageId === id); if (descriptor?.issues?.length) {
113
- setRequestId(undefined);
114
- setViewingHistoryEntry(undefined);
115
- setControlState("idle");
116
- setRunState("ready");
117
- return;
118
- } const next = packageCatalog.get(id); setExecutionPackage(next); if (!inspectDeclaredInputs(next).conflict && !inspectDeclaredInputs(next).unresolved)
119
- setInputEdits(inputDrafts(inspectDeclaredInputs(next).declarations)); setBreakpoints(new Set()); breakpointsRef.current = new Set(); setStopAfterStepId(undefined); setLoadError(undefined); setLoadIssues([]); setRuntimeValues(Object.fromEntries((next.runtimeRequirements ?? []).map(name => [name, '']))); setStepStates((next.target.kind === 'project' ? next.flows?.flatMap(flow => flattenDisplaySteps(flow.steps)) ?? [] : flattenDisplaySteps(next.steps)).map(() => 'pending')); setSelectedStep(0); setResult(undefined); setEvents([]); setRequestId(undefined); setViewingHistoryEntry(undefined); setControlState('idle'); setRunState('ready'); }
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 (selectedTarget?.issues?.length)
126
- throw new Error(selectedTarget.issues.join("\n"));
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
- setViewingHistoryEntry(undefined);
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(displaySteps.map(() => 'pending'));
240
+ setStepStates(liveDisplaySteps.map(() => 'pending'));
140
241
  setRunState('preflighting');
141
242
  setControlState('running');
142
- await client.submit({ requestId: id, executionPackage: currentPackage, inputs, environment, runtimeValues, debugSession: { breakpoints: [...breakpointsRef.current], ...(stopAfterStepId ? { stopAfterStepId } : {}) }, options: { ...(timeoutMs === 'none' ? {} : { timeoutMs: Number(timeoutMs) }), dryRun } });
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.structuralAddress) {
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(displaySteps[index]?.structuralAddress) === occurrenceKey(event.structuralAddress) ? nextState : state));
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(displaySteps.map(step => stepStateCache.current[occurrenceKey(step.structuralAddress)] ?? 'pending'));
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: executionPackage.target.name, packageId: executionPackage.packageId, artifactSchemaVersion: packageCatalog.artifactProvenance(executionPackage.packageId)?.originalVersion, artifactProvenance: packageCatalog.artifactProvenance(executionPackage.packageId), executionSchemaVersion: executionPackage.schemaVersion, environment: executionResult.environment, state: executionResult.state, durationMs: Math.max(0, new Date(finishedAt).getTime() - new Date(executionResult.startedAt).getTime()), result: executionResult };
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) { let loadedPackage = executionPackage; if (entry.packageId) {
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() { displaySteps.forEach((step, index) => { const state = stepStates[index]; if (state && step.structuralAddress)
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) { rememberVisibleStates(); const next = new Set(collapsedSteps); if (next.has(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) { rememberVisibleStates(); const next = collapsed ? new Set(containerStepIds(packageSteps)) : new Set(); const nextSteps = flattenDisplaySteps(packageSteps, 0, undefined, undefined, next); setCollapsedSteps(next); setStepStates(nextSteps.map(step => stepStateCache.current[occurrenceKey(step.structuralAddress)] ?? 'pending')); }
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 target = loadTarget(await file.text());
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 package.');
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 = runState === 'ready' ? 'Ready' : runState[0].toUpperCase() + runState.slice(1);
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
- setSelectedStep(index); }; document.addEventListener('click', navigateIssue); return () => document.removeEventListener('click', navigateIssue); }, [displaySteps]);
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
- event.preventDefault();
298
- setSelectedStep(Math.min(displaySteps.length - 1, index + 1));
299
- } if (event.key === "ArrowUp") {
300
- event.preventDefault();
301
- setSelectedStep(Math.max(0, index - 1));
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 NavItem({ icon, text, active = false }) { const scrollTarget = text === 'Run History' ? '.history-panel' : text === 'Flows' ? '.runner-workspace table' : '.runner-workspace'; return _jsxs("a", { className: `nav-item ${active ? 'active' : ''}`, href: `#${text.toLowerCase().replaceAll(' ', '-')}`, onClick: event => { event.preventDefault(); document.querySelector(scrollTarget)?.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, children: [_jsx("span", { children: icon }), text] }); }
305
- function HistoryPanel({ entries, retention, onRetentionChange, onClear, onDelete, onExport, onReload }) { return _jsxs("section", { className: "panel history-panel", children: [_jsxs("div", { className: "panel-heading", children: [_jsx("h2", { children: "Run History" }), _jsx("span", { className: "panel-spacer" }), _jsxs("label", { className: "history-retention", children: ["Keep", _jsxs("select", { "aria-label": "Run history retention", value: retention, onChange: event => onRetentionChange(Number(event.target.value)), children: [_jsx("option", { value: "10", children: "10 runs" }), _jsx("option", { value: "20", children: "20 runs" }), _jsx("option", { value: "50", children: "50 runs" }), _jsx("option", { value: "100", children: "100 runs" })] })] }), _jsx("button", { className: "button", onClick: onClear, disabled: !entries.length, children: "Clear history" })] }), entries.length ? _jsx("div", { className: "table-scroll", role: "region", "aria-label": "Stored runs", tabIndex: 0, children: _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Target" }), _jsx("th", { children: "Started" }), _jsx("th", { children: "State" }), _jsx("th", { children: "Duration" }), _jsx("th", { children: "Result" })] }) }), _jsx("tbody", { children: entries.map(entry => _jsxs("tr", { children: [_jsxs("td", { children: [entry.target, _jsxs("small", { className: "history-meta", children: [entry.result.provenance ? 'Run From Here' : entry.result.steps.some(step => step.attempts?.some(attempt => attempt.kind === 'interactive')) ? 'Interactive retry' : 'Normal run', " \u00B7 ", entry.packageId ?? 'legacy result', " \u00B7 Artifact v", entry.artifactProvenance?.originalVersion ?? 'unknown', " \u00B7 Execution v", entry.executionSchemaVersion ?? 'unknown', _jsx("br", {}), "hash ", entry.result.artifactHash ?? 'legacy result'] })] }), _jsx("td", { children: new Date(entry.startedAt).toLocaleString() }), _jsx("td", { children: _jsx("span", { className: `status ${entry.state}`, children: entry.state.toUpperCase() }) }), _jsxs("td", { children: [entry.durationMs, " ms"] }), _jsxs("td", { children: [_jsx("button", { className: "button", onClick: () => onReload(entry), children: "View results" }), _jsx("button", { className: "button", onClick: () => onExport(entry.result, entry.packageId, entry.artifactProvenance?.originalVersion, entry.artifactProvenance, entry.executionSchemaVersion), children: "Export JSON" }), _jsx("button", { className: "button history-delete", onClick: () => onDelete(entry.requestId), children: "Delete" })] })] }, entry.requestId)) })] }) }) : _jsx("p", { className: "empty-state", children: "Completed and failed runs will be kept here in this browser." })] }); }
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 package." })] })] }), _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}`)) })] }); }
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