@qaick/runner 0.32.1 → 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 +240 -67
  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 +78 -20
  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,11 +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";
22
+ const statusGlyph = { pending: '', 'not executed': '', running: '', finished: '✓', failed: '✕', waiting: '◷', skipped: '–', cancelled: '⊘' };
21
23
  const HISTORY_KEY = 'qaick-runner.history.v1';
22
24
  const HISTORY_RETENTION_KEY = 'qaick-runner.history-retention.v1';
23
25
  const IMPORTED_PACKAGE_KEY = 'qaick-runner.imported-package.v1';
24
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. */ } }
25
44
  function readRunHistory() { try {
26
45
  return JSON.parse(localStorage.getItem(HISTORY_KEY) ?? '[]');
27
46
  }
@@ -62,12 +81,68 @@ function standaloneDemoRequested() { return typeof window !== 'undefined' && new
62
81
  function RunnerApp({ client, onExit, applicationMenu }) {
63
82
  const [theme, setTheme] = useState(readTheme);
64
83
  const [showAbout, setShowAbout] = useState(false);
65
- 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
+ }
66
122
  const [selectedTargetId, setSelectedTargetId] = useState(restoredImportedPackage?.executionPackage?.packageId ?? restoredImportedPackage?.authoringSource?.targets[0]?.packageId ?? checkoutPackage.packageId);
67
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]);
68
140
  const [executionPackage, setExecutionPackage] = useState(restoredImportedPackage?.executionPackage ?? checkoutPackage);
69
141
  const [inputEdits, setInputEdits] = useState(() => inputDrafts(inspectDeclaredInputs(restoredImportedPackage?.executionPackage ?? checkoutPackage).declarations));
70
- 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]);
71
146
  const inputBlocker = conflict ?? unresolved;
72
147
  let inputError;
73
148
  let inputs = {};
@@ -81,6 +156,8 @@ function RunnerApp({ client, onExit, applicationMenu }) {
81
156
  const [timeoutMs, setTimeoutMs] = useState('none');
82
157
  const [editingInputs, setEditingInputs] = useState(false);
83
158
  const [runState, setRunState] = useState('ready');
159
+ const activeInputRequest = useRef(undefined);
160
+ const inputsLocked = activeInputRequest.current !== undefined;
84
161
  const [controlState, setControlState] = useState('idle');
85
162
  const [breakpoints, setBreakpoints] = useState(new Set());
86
163
  const [contextMenu, setContextMenu] = useState();
@@ -94,7 +171,15 @@ function RunnerApp({ client, onExit, applicationMenu }) {
94
171
  const [runHistory, setRunHistory] = useState(readRunHistory);
95
172
  const [historyRetention, setHistoryRetention] = useState(readHistoryRetention);
96
173
  const [result, setResult] = useState();
97
- 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;
98
183
  const [selectedStep, setSelectedStep] = useState(0);
99
184
  const [collapsedSteps, setCollapsedSteps] = useState(new Set());
100
185
  const [requestId, setRequestId] = useState();
@@ -102,71 +187,127 @@ function RunnerApp({ client, onExit, applicationMenu }) {
102
187
  const [loadIssues, setLoadIssues] = useState([]);
103
188
  const [, setCatalogVersion] = useState(0);
104
189
  const packageSteps = executionPackage.target.kind === 'project' ? executionPackage.flows?.flatMap(flow => flow.steps) ?? [] : executionPackage.steps;
105
- const allResults = result?.children?.flatMap(child => flattenResultSteps(child.steps)) ?? flattenResultSteps(result?.steps ?? []);
106
- const displaySteps = orderDisplayStepsByResult(flattenDisplaySteps(packageSteps, 0, undefined, undefined, collapsedSteps), allResults);
107
- const hasNestedSteps = flattenDisplaySteps(packageSteps).some(step => step.depth > 0);
108
- const displayResults = displaySteps.map(step => resultForOccurrence(allResults, step.structuralAddress));
109
- const selectedInvocations = resultsForOccurrence(allResults, displaySteps[selectedStep]?.structuralAddress);
110
- const visibleEvents = events.filter(event => { const categoryMatches = eventFilter === 'all' || eventFilter === 'parallel' && event.parallel || eventFilter === 'steps' && event.stepId || eventFilter === 'lifecycle' && !event.stepId && !event.parallel || eventFilter === 'failures' && (event.state === 'failed' || event.type.includes('error')); const searchMatches = !eventSearch.trim() || `${event.type} ${event.state} ${event.message} ${event.stepName ?? ''}`.toLowerCase().includes(eventSearch.trim().toLowerCase()); return categoryMatches && searchMatches; });
111
- function selectTarget(id) { setSelectedTargetId(id); const descriptor = packageCatalog.targets().find(t => t.packageId === id); if (descriptor?.issues?.length) {
112
- setRequestId(undefined);
113
- setViewingHistoryEntry(undefined);
114
- setControlState("idle");
115
- setRunState("ready");
116
- return;
117
- } const next = packageCatalog.get(id); setExecutionPackage(next); if (!inspectDeclaredInputs(next).conflict && !inspectDeclaredInputs(next).unresolved)
118
- setInputEdits(inputDrafts(inspectDeclaredInputs(next).declarations)); setBreakpoints(new Set()); breakpointsRef.current = new Set(); setStopAfterStepId(undefined); setLoadError(undefined); setLoadIssues([]); setRuntimeValues(Object.fromEntries((next.runtimeRequirements ?? []).map(name => [name, '']))); setStepStates((next.target.kind === 'project' ? next.flows?.flatMap(flow => flattenDisplaySteps(flow.steps)) ?? [] : flattenDisplaySteps(next.steps)).map(() => 'pending')); setSelectedStep(0); setResult(undefined); setEvents([]); setRequestId(undefined); setViewingHistoryEntry(undefined); setControlState('idle'); setRunState('ready'); }
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); }
119
221
  async function run(dryRun = false) {
120
- if (inputBlocker || inputError)
222
+ if (activeInputRequest.current || inputBlocker || inputError || preparing || preparationError)
121
223
  return;
122
224
  let currentPackage;
123
225
  try {
124
- if (selectedTarget?.issues?.length)
125
- throw new Error(selectedTarget.issues.join("\n"));
126
- currentPackage = packageCatalog.prepare(executionPackage);
226
+ if (authoringSource && !preparedDocument)
227
+ throw new Error('Project preparation is not ready.');
228
+ currentPackage = preparedDocument ?? packageCatalog.prepare(executionPackage);
127
229
  }
128
230
  catch (error) {
129
231
  setLoadError(error instanceof Error ? error.message : "Source target cannot execute.");
130
232
  return;
131
233
  }
132
234
  const id = crypto.randomUUID();
133
- setViewingHistoryEntry(undefined);
235
+ activeInputRequest.current = id;
134
236
  setRequestId(id);
135
237
  setResult(undefined);
136
238
  setEvents([]);
137
239
  stepStateCache.current = Object.fromEntries(flattenDisplaySteps(packageSteps).filter(step => step.structuralAddress).map(step => [occurrenceKey(step.structuralAddress), 'pending']));
138
- setStepStates(displaySteps.map(() => 'pending'));
240
+ setStepStates(liveDisplaySteps.map(() => 'pending'));
139
241
  setRunState('preflighting');
140
242
  setControlState('running');
141
- 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
+ }
259
+ let completed = false;
142
260
  void (async () => {
143
261
  for await (const event of client.events(id)) {
144
262
  setEvents(previous => [...previous, redactExecutionEvent(event, collectSecretValues(inputs, runtimeValues))]);
263
+ if (completed)
264
+ continue;
145
265
  if (event.state === 'running') {
146
266
  setRunState('running');
147
267
  setControlState('running');
148
268
  }
149
- if (event.control)
150
- setControlState(await Promise.resolve(client.controlState(id)) ?? 'running');
151
- if (event.structuralAddress) {
269
+ if (event.control) {
270
+ const state = await Promise.resolve(client.controlState(id));
271
+ if (completed)
272
+ continue;
273
+ setControlState(state ?? 'running');
274
+ }
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) {
152
281
  const nextState = event.state;
153
282
  stepStateCache.current[occurrenceKey(event.structuralAddress)] = nextState;
154
- 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));
155
284
  }
156
285
  }
157
286
  })();
158
287
  const secretValues = collectSecretValues(inputs, runtimeValues);
159
288
  const executionResult = redactExecutionResult(await client.result(id), secretValues);
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;
160
302
  const finalSteps = flattenResultSteps(executionResult.children?.flatMap(child => child.steps) ?? executionResult.steps);
161
- const finalControlState = await Promise.resolve(client.controlState(id));
162
303
  const finalStateById = Object.fromEntries(flattenDisplaySteps(packageSteps).filter(step => step.structuralAddress).map(step => [occurrenceKey(step.structuralAddress), stateForOccurrence(finalSteps, step.structuralAddress) ?? 'pending']));
163
304
  stepStateCache.current = { ...stepStateCache.current, ...finalStateById };
164
305
  setResult(executionResult);
165
- setStepStates(displaySteps.map(step => stepStateCache.current[occurrenceKey(step.structuralAddress)] ?? 'pending'));
306
+ setStepStates(liveDisplaySteps.map(step => stepStateCache.current[occurrenceKey(step.structuralAddress)] ?? 'pending'));
166
307
  setRunState(executionResult.state);
167
- setControlState(finalControlState ?? (executionResult.state === 'cancelled' ? 'stopped' : executionResult.state));
308
+ setControlState(executionResult.state === 'cancelled' ? 'stopped' : executionResult.state);
168
309
  const finishedAt = executionResult.finishedAt ?? new Date().toISOString();
169
- 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 };
170
311
  setRunHistory(previous => persistRunHistory(localStorage, HISTORY_KEY, [entry, ...previous.filter(item => item.requestId !== id)], historyRetention, secretValues));
171
312
  }
172
313
  async function stop() { if (requestId)
@@ -190,38 +331,53 @@ function RunnerApp({ client, onExit, applicationMenu }) {
190
331
  next.add(stepId); breakpointsRef.current = next; return next; }); }
191
332
  function toggleStopAfter(stepId) { setStopAfterStepId(previous => previous === stepId ? undefined : stepId); }
192
333
  function exportResult(value, packageId, schemaVersion, provenance, executionVersion) { if (!value)
193
- 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); }
194
- function reloadHistoryEntry(entry) { let loadedPackage = executionPackage; if (entry.packageId) {
195
- try {
196
- loadedPackage = packageCatalog.get(entry.packageId);
197
- setExecutionPackage(loadedPackage);
198
- setSelectedTargetId(loadedPackage.packageId);
199
- }
200
- catch { /* The result remains viewable when its package is no longer loaded. */ }
201
- } const steps = loadedPackage.target.kind === 'project' ? loadedPackage.flows?.flatMap(flow => flow.steps) ?? [] : loadedPackage.steps; const storedSteps = flattenResultSteps(entry.result.children?.flatMap(child => child.steps) ?? entry.result.steps); const states = new Map(flattenDisplaySteps(steps).map(step => [occurrenceKey(step.structuralAddress), stateForOccurrence(storedSteps, step.structuralAddress) ?? 'pending'])); setRequestId(entry.requestId); setEnvironment(entry.environment); setResult(entry.result); setViewingHistoryEntry(entry.requestId); setEvents([]); setSelectedStep(0); setStepStates(flattenDisplaySteps(steps).map(step => states.get(occurrenceKey(step.structuralAddress)) ?? 'pending')); setRunState(entry.state); document.querySelector('.runner-workspace')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); }
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()); }
202
336
  function clearHistory() { setRunHistory([]); localStorage.removeItem(HISTORY_KEY); }
203
337
  function deleteHistoryEntry(requestIdToDelete) { setRunHistory(previous => { const next = previous.filter(item => item.requestId !== requestIdToDelete); localStorage.setItem(HISTORY_KEY, JSON.stringify(next)); return next; }); }
204
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; }); }
205
- 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)
206
340
  stepStateCache.current[occurrenceKey(step.structuralAddress)] = state; }); }
207
- 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))
208
348
  next.delete(stepId);
209
349
  else
210
- next.add(stepId); const nextSteps = flattenDisplaySteps(packageSteps, 0, undefined, undefined, next); setCollapsedSteps(next); setStepStates(nextSteps.map(step => stepStateCache.current[occurrenceKey(step.structuralAddress)] ?? 'pending')); }
211
- 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')); }
212
355
  async function loadPackage(file) {
213
- if (!file)
356
+ if (!file || activeInputRequest.current)
214
357
  return;
215
358
  try {
216
- const target = loadTarget(await file.text());
359
+ const text = await file.text();
360
+ if (activeInputRequest.current)
361
+ return;
362
+ const target = loadTarget(text);
217
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 }])) }));
218
366
  const selected = packageCatalog.addLoadedTarget(target);
219
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);
220
377
  if (!loaded) {
221
378
  localStorage.setItem(IMPORTED_PACKAGE_KEY, JSON.stringify(target));
222
379
  setCatalogVersion(v => v + 1);
223
380
  setRequestId(undefined);
224
- setViewingHistoryEntry(undefined);
225
381
  setControlState("idle");
226
382
  setRunState("ready");
227
383
  setLoadError(undefined);
@@ -244,18 +400,17 @@ function RunnerApp({ client, onExit, applicationMenu }) {
244
400
  setEvents([]);
245
401
  setResult(undefined);
246
402
  setRequestId(undefined);
247
- setViewingHistoryEntry(undefined);
248
403
  setControlState('idle');
249
404
  setRunState('ready');
250
405
  setLoadError(undefined);
251
406
  setLoadIssues([]);
252
407
  }
253
408
  catch (error) {
254
- 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.');
255
410
  setLoadIssues(error instanceof PackageLoadError ? error.issues : []);
256
411
  }
257
412
  }
258
- const statusLabel = runState === 'ready' ? 'Ready' : runState[0].toUpperCase() + runState.slice(1);
413
+ const statusLabel = shownRunState === 'ready' ? 'Ready' : shownRunState[0].toUpperCase() + shownRunState.slice(1);
259
414
  useEffect(() => {
260
415
  document.documentElement.dataset.theme = theme;
261
416
  document.documentElement.style.colorScheme = theme;
@@ -268,11 +423,11 @@ function RunnerApp({ client, onExit, applicationMenu }) {
268
423
  useEffect(() => { const close = () => setContextMenu(undefined); const keydown = (event) => { if (event.key === 'Escape')
269
424
  close(); }; document.addEventListener('click', close); document.addEventListener('keydown', keydown); return () => { document.removeEventListener('click', close); document.removeEventListener('keydown', keydown); }; }, []);
270
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)
271
- 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]);
272
427
  const projectFileInput = useRef(null);
273
428
  const menuActions = useRef({ enabled: () => false, openProject: async () => { }, about: () => { }, demo: () => { } });
274
429
  menuActions.current = {
275
- 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),
276
431
  openProject: file => loadPackage(new File([file.text], file.name, { type: 'application/json' })),
277
432
  about: () => setShowAbout(true),
278
433
  // Preserve the current standalone/combined destination exactly.
@@ -285,16 +440,29 @@ function RunnerApp({ client, onExit, applicationMenu }) {
285
440
  about: () => menuActions.current.about(),
286
441
  demo: () => menuActions.current.demo(),
287
442
  }), [registerMenu]);
288
- return _jsxs("div", { className: "runner-shell", children: [_jsxs("header", { className: "runner-header", children: [_jsxs("div", { className: "runner-brand", children: [_jsx("span", { className: "runner-brand-mark", children: "QAICK" }), _jsx("span", { className: "runner-brand-product", children: "Runner" }), _jsx("span", { className: "alpha-label", children: RUNNER_UI_VERSION }), onExit && _jsx("button", { className: "about-button", onClick: onExit, children: "Studio" })] }), _jsx("span", { className: "header-spacer" }), _jsx(ScreenshotButton, { className: "about-button" }), _jsx("a", { className: "demo-entry", href: "./examples/qaick-local-http-regression.qaick.json", download: true, title: "Download QAick Local HTTP Regression. Start the local fixture on port 5197, then load this file. Runner executes both Flows. Editing is restricted in Studio.", children: "HTTP example" }), !applicationMenu?.native && _jsx(HeaderHelp, { enabled: id => menuActions.current.enabled(id), onAbout: () => menuActions.current.about(), onOpenProject: () => projectFileInput.current?.click() }), _jsx(ThemeToggle, { theme: theme, onChange: setTheme, className: "runner-theme-toggle" })] }), _jsx("input", { ref: projectFileInput, type: "file", accept: "application/json,.json", hidden: true, "aria-label": "Open Project\u2026", onChange: event => { const file = event.target.files?.[0]; event.target.value = ''; void loadPackage(file); } }), _jsxs("main", { className: "runner-main", children: [selectedTarget?.issues?.length ? _jsxs("section", { className: "runner-workspace runner-authoring-source", children: [_jsx("div", { className: "eyebrow", children: "AUTHORING SOURCE" }), _jsx("h1", { children: selectedTarget.target.name }), _jsx("div", { className: "run-settings", children: _jsxs("label", { children: ["Target", _jsx("select", { value: selectedTargetId, onChange: event => selectTarget(event.target.value), children: packageCatalog.targets().map(t => _jsx("option", { value: t.packageId, children: t.target.name }, t.packageId)) })] }) }), _jsxs("div", { className: "notice", children: [_jsx("strong", { children: "Not executable" }), _jsx("p", { children: "This source has no validated executable structure. Complete and validate it in Studio, then reload the saved Project. Valid sibling Flows remain available in Target." })] }), _jsx("ul", { role: "alert", children: selectedTarget.issues.map((issue, index) => _jsx("li", { children: issue }, index)) }), loadError && _jsx("p", { role: "alert", children: loadError })] }) : _jsxs(_Fragment, { children: [_jsxs("nav", { className: "runner-nav", children: [_jsx("p", { className: "nav-label", children: "Workspace" }), _jsx(NavItem, { active: true, icon: "\u25C8", text: "Execution Package" }), _jsx(NavItem, { icon: "\u25A1", text: "Projects" }), _jsx(NavItem, { icon: "\u2318", text: "Flows" }), _jsx(NavItem, { icon: "\u21B6", text: "Run History" }), _jsxs("div", { className: "package-card", children: [_jsx("small", { children: "Loaded package" }), _jsxs("strong", { children: [executionPackage.packageId, ".qaick.json"] }), _jsxs("small", { children: ["Execution v", executionPackage.schemaVersion, " \u00B7 ", executionPackage.definitions.length, " definitions"] })] })] }), _jsxs("section", { className: "runner-workspace", children: [_jsx("div", { className: "eyebrow", children: "EXECUTION PACKAGE" }), _jsx("h1", { children: executionPackage.target.name }), _jsxs("p", { className: "description", children: ["Execute the selected QAick ", executionPackage.target.kind === 'project' ? 'Project' : 'Flow', " and capture structured evidence."] }), _jsxs("div", { className: "run-controls", children: [_jsxs("div", { className: "run-settings", role: "group", "aria-label": "Execution setup", children: [_jsxs("label", { children: ["Target", _jsx("select", { value: selectedTargetId, onChange: event => selectTarget(event.target.value), children: packageCatalog.targets().map(item => _jsx("option", { value: item.packageId, children: item.target.name }, item.packageId)) })] }), _jsxs("label", { children: ["Environment", _jsxs("select", { value: environment, onChange: event => setEnvironment(event.target.value), children: [_jsx("option", { children: "test" }), _jsx("option", { children: "local" }), _jsx("option", { children: "staging" })] })] }), _jsxs("label", { children: ["Timeout", _jsxs("select", { value: timeoutMs, onChange: event => setTimeoutMs(event.target.value), children: [_jsx("option", { value: "none", children: "None" }), _jsx("option", { value: "1000", children: "1 second" }), _jsx("option", { value: "3000", children: "3 seconds" }), _jsx("option", { value: "10000", children: "10 seconds" })] })] }), _jsxs("label", { children: ["Run configuration", _jsxs("select", { defaultValue: "Default", children: [_jsx("option", { children: "Default" }), _jsx("option", { children: "Debug" })] })] })] }), _jsxs("div", { className: "run-actions", role: "group", "aria-label": "Execution actions", children: [_jsx("button", { className: "button", onClick: () => setEditingInputs(value => !value), children: editingInputs ? 'Done inputs' : 'Edit inputs' }), _jsx("button", { className: "button primary", onClick: () => void run(), disabled: !!inputBlocker || runState === 'preflighting' || runState === 'running', children: "\u25B6\u00A0 Run" }), _jsx("button", { className: "button dry-run", onClick: () => void run(true), disabled: !!inputBlocker || runState === "preflighting" || runState === "running", children: "\u25CE\u00A0 Pre-run validation" }), _jsx("button", { className: "button stop", onClick: () => void stop(), disabled: runState !== 'preflighting' && runState !== 'running', children: "\u25A0\u00A0 Stop" }), _jsx("button", { className: "button", onClick: () => void pause(), disabled: controlState !== 'running', children: "\u2161\u00A0 Pause" }), _jsx("button", { className: `button ${controlState === 'paused' ? 'resume-highlight' : ''}`, onClick: () => void resume(), disabled: controlState !== 'paused', children: "\u25B6\u00A0 Resume" }), _jsx("button", { className: "button", onClick: () => void stepExecution(), disabled: controlState !== 'paused', children: "\u21A6\u00A0 Step" }), breakpoints.size > 0 && _jsxs("span", { className: "debug-session-badge", children: [breakpoints.size, " breakpoint", breakpoints.size === 1 ? "" : "s", " armed"] })] })] }), viewingHistoryEntry && _jsxs("div", { className: "history-view-banner", children: ["Viewing stored results \u00B7 ", viewingHistoryEntry] }), conflict && _jsxs("section", { role: "alert", children: [_jsx("p", { children: conflict.message }), _jsx("ul", { children: conflict.entries.map((entry, index) => _jsxs("li", { children: [entry.owner, " \u00B7 ", entry.declaration.name, " \u00B7 ", entry.declaration.type, " \u00B7 ", entry.declaration.required ? 'required' : 'optional', " \u00B7 default: ", Object.hasOwn(entry.declaration, 'defaultValue') ? JSON.stringify(entry.declaration.defaultValue) : 'none'] }, index)) }), _jsx("p", { children: "Retained input drafts:" }), _jsx("ul", { children: Object.entries(inputEdits).map(([name, draft]) => _jsxs("li", { children: [name, " \u00B7 ", draft.supplied ? 'supplied' : 'omitted'] }, name)) })] }), unresolved && _jsxs("section", { role: "alert", children: [_jsx("p", { children: unresolved.message }), _jsx("ul", { children: unresolved.entries.map((entry, index) => _jsxs("li", { children: [entry.owner, " \u00B7 ", entry.name, " \u00B7 required \u00B7 type unresolved"] }, index)) }), _jsx("p", { children: "Retained input drafts:" }), _jsx("ul", { children: Object.entries(inputEdits).map(([name, draft]) => _jsxs("li", { children: [name, " \u00B7 ", draft.supplied ? 'supplied' : 'omitted'] }, name)) })] }), inputError && _jsx("p", { role: "alert", children: inputError }), loadError && _jsx("p", { className: "load-error", children: loadError }), loadIssues.length > 0 && _jsx(PreRunValidation, { issues: loadIssues, title: "Artifact validation" }), result?.issues !== undefined && _jsx(PreRunValidation, { issues: result.issues }), result?.warnings !== undefined && result.warnings.length > 0 && _jsx(PreRunValidation, { issues: result.warnings, title: "Browser execution warnings" }), editingInputs && _jsxs("section", { className: "inputs-panel panel", children: [_jsxs("div", { className: "panel-heading", children: [_jsx("h2", { children: "Invocation Inputs" }), _jsx("span", { className: "panel-spacer" }), _jsx("span", { className: "input-hint", children: "Values are sent with the next run" })] }), _jsxs("div", { className: "input-grid", children: [inputBlocker ? _jsx("p", { children: "Invocation input editing is unavailable until requirements are resolved." }) : declarations.map(declaration => { const draft = inputEdits[declaration.name] ?? { supplied: false, text: '' }; const change = (text) => setInputEdits(previous => ({ ...previous, [declaration.name]: { supplied: true, text } })); return _jsxs("div", { children: [_jsxs("label", { children: [_jsx("input", { type: "checkbox", "aria-label": `Supply ${declaration.name}`, checked: draft.supplied, onChange: event => setInputEdits(previous => ({ ...previous, [declaration.name]: { ...draft, supplied: event.target.checked } })) }), "Supply ", declaration.name] }), declaration.type === 'boolean' ? _jsxs("label", { className: "input-field", children: [declaration.name, _jsxs("select", { value: draft.text, onChange: event => change(event.target.value), children: [_jsx("option", { value: "false", children: "false" }), _jsx("option", { value: "true", children: "true" })] })] }) : _jsx(InputField, { label: declaration.name, value: draft.text, onChange: change }), _jsxs("small", { children: [declaration.type, " \u00B7 ", draft.supplied ? 'supplied' : Object.hasOwn(declaration, 'defaultValue') ? 'omitted — declared default applies' : 'omitted', declaration.required ? ' · required' : ''] })] }, declaration.name); }), (executionPackage.runtimeRequirements ?? []).map(name => _jsx(InputField, { label: `Runtime · ${name}`, value: runtimeValues[name] ?? '', onChange: value => setRuntimeValues(previous => ({ ...previous, [name]: value })) }, name))] })] }), _jsxs("section", { className: "panel", children: [_jsxs("div", { className: "panel-heading", children: [_jsx("h2", { children: "Live Execution" }), _jsx("span", { className: "panel-spacer" }), _jsxs("span", { className: "execution-menu-label", children: ["Right-click a row to manage its breakpoint \u00B7 Selected: ", displaySteps[selectedStep]?.name ?? 'None'] }), _jsxs("span", { className: runState === 'running' ? 'running' : runState === 'failed' ? 'stopped' : 'run-status', children: ["\u25CF ", statusLabel] })] }), hasNestedSteps && _jsxs("div", { className: "hierarchy-hint", children: [_jsx("strong", { children: "Nested execution" }), _jsx("span", { children: "Use \u2212 / + to collapse or expand nested Operations, Control Blocks, and loops." }), _jsx("button", { className: "hierarchy-action", onClick: () => setAllCollapsed(true), children: "Collapse all" }), _jsx("button", { className: "hierarchy-action", onClick: () => setAllCollapsed(false), children: "Expand all" })] }), _jsx("div", { className: "table-scroll", role: "region", "aria-label": "Execution steps", tabIndex: 0, children: _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "#" }), _jsx("th", { children: "Step hierarchy" }), _jsx("th", { children: "Status" }), _jsx("th", { children: "Start time" }), _jsx("th", { children: "Duration" })] }) }), _jsx("tbody", { children: displaySteps.map((step, index) => { const isLoop = step.loopRepeat; const loopContexts = isLoop ? loopPresentation(step.structuralAddress, step.loopTotal, step.loopDuration, result ? allResults : eventPresentationEvidence(events), Boolean(result)) : []; const rowEvidence = result ? allResults : eventPresentationEvidence(events); const repeatedMatches = rowEvidence.filter(r => occurrenceKey(r.structuralAddress) === occurrenceKey(step.structuralAddress)); const latestQualifier = step.branchRow ? rowEvidence.filter(r => occurrenceKey(r.structuralAddress) === occurrenceKey(step.branchOwner)).length > 1 : repeatedMatches.length > 1; const displayState = presentationState(step, step.branchRow ? rowEvidence : latestQualifier ? [repeatedMatches.at(-1)] : rowEvidence, Boolean(result), step.branchRow ? 'pending' : stepStates[index] ?? 'pending'); const isLoopContent = Boolean(step.loopId); const isLoopEnd = isLoopContent && displaySteps[index + 1]?.loopId !== step.loopId; return _jsxs("tr", { className: `${selectedStep === index ? 'selected ' : ''}${isLoop ? 'loop-row ' : ''}${isLoopContent ? 'loop-content ' : ''}${isLoopEnd ? 'loop-end ' : ''}${step.depth > 0 ? 'nested-row' : 'parent-row'}`, tabIndex: 0, onKeyDown: event => { if (event.key === "ArrowDown") {
289
- event.preventDefault();
290
- setSelectedStep(Math.min(displaySteps.length - 1, index + 1));
291
- } if (event.key === "ArrowUp") {
292
- event.preventDefault();
293
- setSelectedStep(Math.max(0, index - 1));
294
- } }, onClick: () => setSelectedStep(index), onContextMenu: event => { event.preventDefault(); setSelectedStep(index); setContextMenu({ x: event.clientX, y: event.clientY, stepId: step.id }); }, children: [_jsx("td", { children: index + 1 }), _jsxs("td", { children: [_jsx("span", { className: `step-icon ${isLoop ? 'loop-icon' : displayState}`, children: isLoop ? '↻' : displayState === 'finished' ? '✓' : displayState === 'waiting' ? '◷' : displayState === 'skipped' ? '–' : '' }), _jsxs("span", { className: "hierarchy-name", style: { paddingLeft: `${step.depth * 18}px` }, children: [step.depth > 0 && _jsx("span", { className: "branch-marker", children: "\u2514" }), step.hasChildren && _jsx("button", { className: "collapse-toggle", "aria-label": `${collapsedSteps.has(step.id) ? 'Expand' : 'Collapse'} ${step.name}`, onClick: event => { event.stopPropagation(); toggleStep(step.id); }, children: collapsedSteps.has(step.id) ? '+' : '−' }), _jsx("span", { children: step.name }), breakpoints.has(step.id) && _jsx("small", { className: "breakpoint-marker", "aria-label": "Breakpoint armed", title: "Breakpoint armed", children: "STOP" }), isLoop && _jsx("small", { className: "kind-label loop-label", children: "LOOP" }), step.depth > 0 ? _jsx("small", { className: "kind-label nested-label", children: "NESTED STEP" }) : !isLoop && _jsx("small", { className: "kind-label", children: step.kind.toUpperCase() })] }), selectedStep === index && _jsx("small", { className: "selected-label", children: "SELECTED" })] }), _jsxs("td", { children: [loopContexts.map(context => _jsxs("div", { className: "loop-progress", children: [context.context.length > 0 && _jsxs("small", { children: ["Enclosing iterations: ", context.context.map(s => s.iteration).join(' / ')] }), _jsx("strong", { children: context.label }), _jsx("small", { children: context.state.toUpperCase() })] }, JSON.stringify(context.context))), _jsx("span", { className: `status ${displayState}`, children: displayState.toUpperCase() }), latestQualifier && _jsx("small", { className: "latest-invocation", children: "Latest invocation" })] }), _jsx("td", { children: displayResults[index] ? new Date(result?.finishedAt ?? '').toLocaleTimeString() : '—' }), _jsx("td", { children: displayResults[index] ? `${displayResults[index].durationMs} ms` : '—' })] }, `${step.id}-${index}`); }) })] }) }), contextMenu && _jsxs("div", { className: "runner-context-menu", style: { left: contextMenu.x, top: contextMenu.y }, onClick: event => event.stopPropagation(), onContextMenu: event => event.preventDefault(), children: [_jsx("strong", { children: "Breakpoint" }), breakpoints.has(contextMenu.stepId) ? _jsx("button", { onClick: () => { toggleBreakpoint(contextMenu.stepId); setContextMenu(undefined); }, children: "Remove breakpoint" }) : _jsx("button", { onClick: () => { toggleBreakpoint(contextMenu.stepId); setContextMenu(undefined); }, children: "Add breakpoint" })] })] }), _jsxs("section", { className: "panel event-panel", children: [_jsxs("div", { className: "panel-heading", children: [_jsx("h2", { children: "Event Log" }), _jsx("span", { className: "panel-spacer" }), _jsx("input", { className: "event-search", "aria-label": "Search event log", placeholder: "Search events", value: eventSearch, onChange: event => setEventSearch(event.target.value) }), _jsxs("select", { className: "event-filter", value: eventFilter, onChange: event => setEventFilter(event.target.value), children: [_jsx("option", { value: "all", children: "All events" }), _jsx("option", { value: "steps", children: "Step events" }), _jsx("option", { value: "parallel", children: "Parallel groups" }), _jsx("option", { value: "lifecycle", children: "Lifecycle" }), _jsx("option", { value: "failures", children: "Failures" })] })] }), _jsx("div", { className: "table-scroll", role: "region", "aria-label": "Execution events", tabIndex: 0, children: _jsx("table", { children: _jsx("tbody", { children: visibleEvents.map(event => _jsxs("tr", { className: "event-row", children: [_jsx("td", { children: new Date(event.timestamp).toLocaleTimeString() }), _jsx("td", { children: event.state.toUpperCase() }), _jsx("td", { children: _jsx("code", { children: event.type.toUpperCase() }) }), _jsxs("td", { children: [event.message, event.parallel && _jsxs("small", { className: "parallel-event", children: [" \u00B7 Parallel ", event.parallel.completed, "/", event.parallel.total, " (", event.parallel.join, ")"] })] })] }, event.sequence)) }) }) }), events.length > 0 && visibleEvents.length === 0 && _jsx("p", { className: "empty-state", children: "No events match the current filter." })] }), _jsx(HistoryPanel, { entries: runHistory, onReload: reloadHistoryEntry, retention: historyRetention, onRetentionChange: changeHistoryRetention, onClear: clearHistory, onDelete: deleteHistoryEntry, onExport: exportResult })] }), _jsxs("aside", { className: "inspector", children: [_jsx("h2", { children: "Selected execution" }), _jsx("p", { className: "selected-detail-name", children: _jsx("strong", { children: displaySteps[selectedStep]?.name ?? 'No step selected' }) }), _jsxs("p", { className: "run-id", children: ["Run ID: ", requestId ?? 'No run started'] }), _jsx(InspectorSection, { title: "Artifact summary", children: _jsx(KeyValues, { values: ["Target", executionPackage.target.name, "Type", executionPackage.target.kind, "Package", executionPackage.packageId, "Execution format", `Execution v${executionPackage.schemaVersion}`, "Definitions", String(executionPackage.definitions.length), "Required inputs", (executionPackage.requiredInputs ?? []).join(", ") || "None", "Runtime values", (executionPackage.runtimeRequirements ?? []).join(", ") || "None"] }) }), _jsx(InspectorSection, { title: "Run outcome", children: _jsxs("div", { className: "notice", children: [_jsx("strong", { children: result?.state.toUpperCase() ?? 'NOT STARTED' }), _jsx("br", {}), result ? `${result.environment} · ${result.state}` : 'Run the package to inspect its terminal outcome.'] }) }), _jsx(InspectorSection, { title: "Selected step inputs", children: _jsx(KeyValues, { values: Object.entries(displayResults[selectedStep]?.inputs ?? (selectedInvocations.length > 1 ? {} : inputs)).flatMap(([key, value]) => [key, String(value)]) }) }), _jsx(InspectorSection, { title: "Selected step outputs", children: _jsx(KeyValues, { values: displayResults[selectedStep]?.outputs ? Object.entries(displayResults[selectedStep]?.outputs ?? {}).flatMap(([key, value]) => [key, String(value)]) : ['—', selectedInvocations.length > 1 ? `${selectedInvocations.length} recorded invocations; see iteration history` : 'Available after execution'] }) }), (result?.error || displayResults[selectedStep]?.error) && _jsx(InspectorSection, { title: "Error", children: _jsxs("div", { className: "error-notice", children: [_jsx("strong", { children: displayResults[selectedStep]?.error?.code ?? result?.error?.code }), _jsx("br", {}), displayResults[selectedStep]?.error?.message ?? result?.error?.message] }) }), result?.issues !== undefined && _jsx(InspectorSection, { title: "Pre-run validation", children: _jsx(PreRunValidation, { issues: result.issues }) }), _jsx(InspectorSection, { title: "Invocation path", children: _jsx("div", { className: "notice", children: displayResults[selectedStep]?.invocationPath?.join(" › ") ?? "No invocation path available." }) }), _jsx(InspectorSection, { title: "Iteration history", children: _jsx(RecordedInvocations, { results: selectedInvocations, definition: findInspectionDefinition(packageSteps, displaySteps[selectedStep]?.structuralAddress) }, JSON.stringify([requestId, displaySteps[selectedStep]?.structuralAddress])) }), _jsx(InspectorSection, { title: "Execution timing", children: _jsxs("div", { className: "timing-details", children: [_jsxs("span", { children: ["Wall ", result?.executionTiming?.wallDurationMs ?? "—", " ms"] }), _jsxs("span", { children: ["Active ", result?.executionTiming?.activeDurationMs ?? "—", " ms"] }), _jsxs("span", { children: ["Paused ", result?.executionTiming?.pausedDurationMs ?? "—", " ms"] })] }) }), _jsx(InspectorSection, { title: "Selected step attempts", children: _jsx("div", { className: "attempt-history", children: displayResults[selectedStep]?.attempts?.map(attempt => _jsxs("div", { className: "iteration-entry", children: [_jsxs("strong", { children: ["Attempt ", attempt.attempt] }), _jsxs("span", { children: [(attempt.kind ?? 'automatic').toUpperCase(), " \u00B7 ", attempt.state.toUpperCase(), " \u00B7 ", attempt.durationMs, " ms"] })] }, `${attempt.attempt}-${attempt.kind}`)) ?? _jsx("div", { className: "notice", children: "No retry attempts recorded." }) }) }), _jsxs(InspectorSection, { title: "Selected step", children: [_jsxs("div", { className: "notice", children: [_jsx("strong", { children: displaySteps[selectedStep]?.name ?? 'No step selected' }), _jsx("br", {}), displayResults[selectedStep] ? displayResults[selectedStep].error ? 'This step failed. See the error details above.' : `Completed in ${displayResults[selectedStep].durationMs} ms.` : selectedInvocations.length > 1 ? `${selectedInvocations.length} invocations recorded. No single invocation selected.` : 'Run the package to inspect execution details.', _jsxs("div", { className: "inspector-actions", children: [_jsx("button", { className: "button", onClick: () => void retrySelected(), disabled: !displayResults[selectedStep] || (!displayResults[selectedStep]?.error && runState !== "completed"), children: "Retry Action" }), _jsx("button", { className: `button ${stopAfterStepId === displaySteps[selectedStep]?.id ? "active" : ""}`, onClick: () => displaySteps[selectedStep] && toggleStopAfter(displaySteps[selectedStep].id), disabled: !displaySteps[selectedStep], children: "Stop After" }), _jsx("button", { className: "button", onClick: () => void runFromSelected(), disabled: !displayResults[selectedStep] || runState === "running", children: "Run From Here" })] })] }), _jsxs("div", { className: "selection-nav", children: [_jsx("button", { className: "button", disabled: selectedStep <= 0, onClick: () => setSelectedStep(index => Math.max(0, index - 1)), children: "\u2039 Previous" }), _jsx("span", { children: displaySteps.length ? `${selectedStep + 1} / ${displaySteps.length}` : '0 / 0' }), _jsx("button", { className: "button", disabled: selectedStep >= displaySteps.length - 1, onClick: () => setSelectedStep(index => Math.min(displaySteps.length - 1, index + 1)), children: "Next \u203A" })] })] })] })] }), " "] }), showAbout && _jsx("div", { className: "about-backdrop", onClick: () => setShowAbout(false), children: _jsxs("section", { className: "about-dialog", role: "dialog", "aria-modal": "true", "aria-labelledby": "about-title", onClick: event => event.stopPropagation(), children: [_jsx("button", { className: "about-close", onClick: () => setShowAbout(false), "aria-label": "Close About", children: "\u00D7" }), _jsx("div", { className: "runner-logo", children: "\u25B6" }), _jsx("h2", { id: "about-title", children: "QAick Runner" }), _jsx("p", { children: "Standalone execution and evidence viewer for QAICK packages." }), _jsxs("small", { children: [RUNNER_UI_VERSION, " \u00B7 ", RUNNER_BUILD_LABEL, " \u00B7 QAick contract"] }), _jsx("footer", { children: "\u00A9 2006\u20132026 QAick. All rights reserved." })] }) })] });
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." })] }) })] });
295
455
  }
296
- function NavItem({ icon, text, active = false }) { const scrollTarget = text === 'Run History' ? '.history-panel' : text === 'Flows' ? '.runner-workspace table' : '.runner-workspace'; return _jsxs("a", { className: `nav-item ${active ? 'active' : ''}`, href: `#${text.toLowerCase().replaceAll(' ', '-')}`, onClick: event => { event.preventDefault(); document.querySelector(scrollTarget)?.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, children: [_jsx("span", { children: icon }), text] }); }
297
- function HistoryPanel({ entries, retention, onRetentionChange, onClear, onDelete, onExport, onReload }) { return _jsxs("section", { className: "panel history-panel", children: [_jsxs("div", { className: "panel-heading", children: [_jsx("h2", { children: "Run History" }), _jsx("span", { className: "panel-spacer" }), _jsxs("label", { className: "history-retention", children: ["Keep", _jsxs("select", { "aria-label": "Run history retention", value: retention, onChange: event => onRetentionChange(Number(event.target.value)), children: [_jsx("option", { value: "10", children: "10 runs" }), _jsx("option", { value: "20", children: "20 runs" }), _jsx("option", { value: "50", children: "50 runs" }), _jsx("option", { value: "100", children: "100 runs" })] })] }), _jsx("button", { className: "button", onClick: onClear, disabled: !entries.length, children: "Clear history" })] }), entries.length ? _jsx("div", { className: "table-scroll", role: "region", "aria-label": "Stored runs", tabIndex: 0, children: _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Target" }), _jsx("th", { children: "Started" }), _jsx("th", { children: "State" }), _jsx("th", { children: "Duration" }), _jsx("th", { children: "Result" })] }) }), _jsx("tbody", { children: entries.map(entry => _jsxs("tr", { children: [_jsxs("td", { children: [entry.target, _jsxs("small", { className: "history-meta", children: [entry.result.provenance ? 'Run From Here' : entry.result.steps.some(step => step.attempts?.some(attempt => attempt.kind === 'interactive')) ? 'Interactive retry' : 'Normal run', " \u00B7 ", entry.packageId ?? 'legacy result', " \u00B7 Artifact v", entry.artifactProvenance?.originalVersion ?? 'unknown', " \u00B7 Execution v", entry.executionSchemaVersion ?? 'unknown', _jsx("br", {}), "hash ", entry.result.artifactHash ?? 'legacy result'] })] }), _jsx("td", { children: new Date(entry.startedAt).toLocaleString() }), _jsx("td", { children: _jsx("span", { className: `status ${entry.state}`, children: entry.state.toUpperCase() }) }), _jsxs("td", { children: [entry.durationMs, " ms"] }), _jsxs("td", { children: [_jsx("button", { className: "button", onClick: () => onReload(entry), children: "View results" }), _jsx("button", { className: "button", onClick: () => onExport(entry.result, entry.packageId, entry.artifactProvenance?.originalVersion, entry.artifactProvenance, entry.executionSchemaVersion), children: "Export JSON" }), _jsx("button", { className: "button history-delete", onClick: () => onDelete(entry.requestId), children: "Delete" })] })] }, entry.requestId)) })] }) }) : _jsx("p", { className: "empty-state", children: "Completed and failed runs will be kept here in this browser." })] }); }
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." })] }); }
298
466
  function InspectorSection({ title, children }) { return _jsxs("section", { className: "inspector-section", children: [_jsx("h3", { children: title }), children] }); }
299
467
  function KeyValues({ values }) { return _jsx("div", { className: "key-values", children: redactKeyValuePairs(values).map((value, index) => _jsx("span", { children: value }, `${value}-${index}`)) }); }
300
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 })] }); }
@@ -306,7 +474,7 @@ function ParallelGroupDetails({ result }) { const group = result?.terminalReason
306
474
  function ResultSummary({ result }) { if (!result)
307
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] })] }); }
308
476
  function PreRunValidation({ issues, title = 'Pre-run validation' }) { if (!issues.length)
309
- 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}`)) })] }); }
310
478
  function diagnosticCategory(code) { if (code === 'CONTRACT_INVALID' || code === 'UNSUPPORTED_SCHEMA')
311
479
  return 'contract-invalid'; if (code === 'INVALID_BINDING' || code === 'MISSING_INPUT_BINDING' || code === 'MISSING_STEP_BINDING' || code === 'MISSING_STEP_OUTPUT' || code === 'BINDING_FAILED')
312
480
  return 'binding-unresolved'; if (code.startsWith('HTTP_') || code === 'ACTION_FAILED' || code === 'MISSING_ACTION_ADAPTER')
@@ -332,8 +500,8 @@ function flattenDisplaySteps(steps, depth = 0, loopId, parentId, collapsed = new
332
500
  }
333
501
  export function orderDisplayStepsByResult(steps, _results) { return steps; }
334
502
  export const occurrenceKey = (address) => address ? JSON.stringify([address.scope, address.segments]) : '';
335
- export function resultsForOccurrence(results, address) { return results.filter(r => address && occurrenceKey(r.structuralAddress) === occurrenceKey(address)); }
336
- 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; }
337
505
  export function stateForOccurrence(results, address) {
338
506
  const matches = resultsForOccurrence(results, address);
339
507
  if (!matches.length)
@@ -371,4 +539,9 @@ export function RecordedInvocations({ results, definition }) {
371
539
  }
372
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) : [])] : []; }); }
373
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
+ }
374
547
  //# sourceMappingURL=RunnerModule.js.map