devglide 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +338 -0
- package/bin/claude-md-template.js +94 -0
- package/bin/devglide.js +387 -0
- package/package.json +85 -0
- package/pnpm-workspace.yaml +3 -0
- package/src/apps/coder/.turbo/turbo-lint.log +5 -0
- package/src/apps/coder/package.json +16 -0
- package/src/apps/coder/public/favicon.svg +7 -0
- package/src/apps/coder/public/page.css +275 -0
- package/src/apps/coder/public/page.js +528 -0
- package/src/apps/coder/server.js +3 -0
- package/src/apps/documentation/public/page.css +597 -0
- package/src/apps/documentation/public/page.js +609 -0
- package/src/apps/kanban/.turbo/turbo-lint.log +97 -0
- package/src/apps/kanban/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/kanban/package.json +32 -0
- package/src/apps/kanban/public/favicon.svg +7 -0
- package/src/apps/kanban/public/page.css +1010 -0
- package/src/apps/kanban/public/page.js +1730 -0
- package/src/apps/kanban/public/vendor/marked.min.js +6 -0
- package/src/apps/kanban/public/vendor/sortable.min.js +2 -0
- package/src/apps/kanban/src/db.ts +319 -0
- package/src/apps/kanban/src/index.ts +14 -0
- package/src/apps/kanban/src/mcp-helpers.test.ts +88 -0
- package/src/apps/kanban/src/mcp-helpers.ts +60 -0
- package/src/apps/kanban/src/mcp.ts +59 -0
- package/src/apps/kanban/src/routes/attachments.ts +161 -0
- package/src/apps/kanban/src/routes/features.ts +233 -0
- package/src/apps/kanban/src/routes/issues.ts +373 -0
- package/src/apps/kanban/src/tools/feature-tools.ts +164 -0
- package/src/apps/kanban/src/tools/item-tools.ts +307 -0
- package/src/apps/kanban/src/tools/versioned-entry-tools.ts +72 -0
- package/src/apps/kanban/tsconfig.check.json +9 -0
- package/src/apps/kanban/tsconfig.json +9 -0
- package/src/apps/keymap/.turbo/turbo-lint.log +5 -0
- package/src/apps/keymap/package.json +16 -0
- package/src/apps/keymap/public/page.css +275 -0
- package/src/apps/keymap/public/page.js +294 -0
- package/src/apps/keymap/server.js +25 -0
- package/src/apps/log/.turbo/turbo-build.log +5 -0
- package/src/apps/log/.turbo/turbo-lint.log +45 -0
- package/src/apps/log/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/log/node_modules/.bin/tsc +21 -0
- package/src/apps/log/node_modules/.bin/tsserver +21 -0
- package/src/apps/log/node_modules/.bin/tsx +21 -0
- package/src/apps/log/package.json +36 -0
- package/src/apps/log/public/console-sniffer.js +221 -0
- package/src/apps/log/public/favicon.svg +7 -0
- package/src/apps/log/public/page.css +322 -0
- package/src/apps/log/public/page.js +463 -0
- package/src/apps/log/src/index.ts +9 -0
- package/src/apps/log/src/mcp.ts +122 -0
- package/src/apps/log/src/routes/log.ts +333 -0
- package/src/apps/log/src/routes/status.ts +25 -0
- package/src/apps/log/src/server-sniffer.ts +118 -0
- package/src/apps/log/src/services/file-patterns.ts +39 -0
- package/src/apps/log/src/services/file-tailer.ts +228 -0
- package/src/apps/log/src/services/line-parser.ts +94 -0
- package/src/apps/log/src/services/log-writer.ts +39 -0
- package/src/apps/log/tsconfig.json +8 -0
- package/src/apps/prompts/.turbo/turbo-build.log +5 -0
- package/src/apps/prompts/.turbo/turbo-lint.log +24 -0
- package/src/apps/prompts/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/prompts/mcp.ts +175 -0
- package/src/apps/prompts/node_modules/.bin/tsc +21 -0
- package/src/apps/prompts/node_modules/.bin/tsserver +21 -0
- package/src/apps/prompts/node_modules/.bin/tsx +21 -0
- package/src/apps/prompts/package.json +25 -0
- package/src/apps/prompts/public/page.css +315 -0
- package/src/apps/prompts/public/page.js +541 -0
- package/src/apps/prompts/services/prompt-store.ts +212 -0
- package/src/apps/prompts/src/index.ts +9 -0
- package/src/apps/prompts/tsconfig.json +8 -0
- package/src/apps/prompts/types.ts +27 -0
- package/src/apps/shell/.turbo/turbo-build.log +5 -0
- package/src/apps/shell/.turbo/turbo-lint.log +34 -0
- package/src/apps/shell/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/shell/package.json +35 -0
- package/src/apps/shell/public/favicon.svg +7 -0
- package/src/apps/shell/public/page.css +407 -0
- package/src/apps/shell/public/page.js +1577 -0
- package/src/apps/shell/src/index.ts +150 -0
- package/src/apps/shell/src/mcp.ts +398 -0
- package/src/apps/shell/src/shell-types.ts +41 -0
- package/src/apps/shell/tsconfig.json +8 -0
- package/src/apps/test/.turbo/turbo-build.log +5 -0
- package/src/apps/test/.turbo/turbo-lint.log +27 -0
- package/src/apps/test/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/test/node_modules/.bin/tsc +21 -0
- package/src/apps/test/node_modules/.bin/tsserver +21 -0
- package/src/apps/test/node_modules/.bin/tsx +21 -0
- package/src/apps/test/node_modules/.bin/uuid +21 -0
- package/src/apps/test/package.json +35 -0
- package/src/apps/test/public/favicon.svg +7 -0
- package/src/apps/test/public/page.css +499 -0
- package/src/apps/test/public/page.js +417 -0
- package/src/apps/test/public/scenario-runner.js +450 -0
- package/src/apps/test/src/index.ts +9 -0
- package/src/apps/test/src/mcp.ts +192 -0
- package/src/apps/test/src/routes/trigger.ts +285 -0
- package/src/apps/test/src/services/scenario-broadcaster.ts +60 -0
- package/src/apps/test/src/services/scenario-manager.ts +361 -0
- package/src/apps/test/src/services/scenario-store.ts +145 -0
- package/src/apps/test/tsconfig.json +8 -0
- package/src/apps/vocabulary/.turbo/turbo-build.log +5 -0
- package/src/apps/vocabulary/.turbo/turbo-lint.log +25 -0
- package/src/apps/vocabulary/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/vocabulary/mcp.ts +173 -0
- package/src/apps/vocabulary/node_modules/.bin/tsc +21 -0
- package/src/apps/vocabulary/node_modules/.bin/tsserver +21 -0
- package/src/apps/vocabulary/node_modules/.bin/tsx +21 -0
- package/src/apps/vocabulary/package.json +25 -0
- package/src/apps/vocabulary/public/page.css +247 -0
- package/src/apps/vocabulary/public/page.js +444 -0
- package/src/apps/vocabulary/services/vocabulary-store.ts +179 -0
- package/src/apps/vocabulary/src/index.ts +10 -0
- package/src/apps/vocabulary/tsconfig.json +8 -0
- package/src/apps/vocabulary/types.ts +22 -0
- package/src/apps/voice/.turbo/turbo-build.log +5 -0
- package/src/apps/voice/.turbo/turbo-lint.log +43 -0
- package/src/apps/voice/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/voice/node_modules/.bin/openai +21 -0
- package/src/apps/voice/node_modules/.bin/tsc +21 -0
- package/src/apps/voice/node_modules/.bin/tsserver +21 -0
- package/src/apps/voice/node_modules/.bin/tsx +21 -0
- package/src/apps/voice/package.json +35 -0
- package/src/apps/voice/public/favicon.svg +7 -0
- package/src/apps/voice/public/page.css +388 -0
- package/src/apps/voice/public/page.js +718 -0
- package/src/apps/voice/src/index.ts +10 -0
- package/src/apps/voice/src/mcp.ts +70 -0
- package/src/apps/voice/src/providers/index.ts +85 -0
- package/src/apps/voice/src/providers/openai-compatible.ts +94 -0
- package/src/apps/voice/src/providers/types.ts +27 -0
- package/src/apps/voice/src/routes/config.ts +118 -0
- package/src/apps/voice/src/routes/transcribe.ts +90 -0
- package/src/apps/voice/src/services/config-store.ts +129 -0
- package/src/apps/voice/src/services/stats.ts +108 -0
- package/src/apps/voice/src/transcribe.ts +11 -0
- package/src/apps/voice/src/utils/mime.ts +16 -0
- package/src/apps/voice/tsconfig.json +8 -0
- package/src/apps/workflow/.turbo/turbo-build.log +5 -0
- package/src/apps/workflow/.turbo/turbo-lint.log +96 -0
- package/src/apps/workflow/.turbo/turbo-typecheck.log +5 -0
- package/src/apps/workflow/engine/executors/decision-executor.ts +87 -0
- package/src/apps/workflow/engine/executors/file-executor.ts +90 -0
- package/src/apps/workflow/engine/executors/git-executor.ts +137 -0
- package/src/apps/workflow/engine/executors/http-executor.ts +65 -0
- package/src/apps/workflow/engine/executors/index.ts +28 -0
- package/src/apps/workflow/engine/executors/kanban-executor.ts +154 -0
- package/src/apps/workflow/engine/executors/llm-executor.ts +46 -0
- package/src/apps/workflow/engine/executors/log-executor.ts +62 -0
- package/src/apps/workflow/engine/executors/loop-executor.ts +14 -0
- package/src/apps/workflow/engine/executors/shell-executor.ts +107 -0
- package/src/apps/workflow/engine/executors/sub-workflow-executor.ts +61 -0
- package/src/apps/workflow/engine/executors/test-executor.ts +73 -0
- package/src/apps/workflow/engine/executors/trigger-executor.ts +39 -0
- package/src/apps/workflow/engine/expression-evaluator.ts +117 -0
- package/src/apps/workflow/engine/graph-runner.ts +438 -0
- package/src/apps/workflow/engine/node-executor.ts +104 -0
- package/src/apps/workflow/engine/node-registry.ts +15 -0
- package/src/apps/workflow/engine/variable-resolver.ts +109 -0
- package/src/apps/workflow/mcp.ts +223 -0
- package/src/apps/workflow/node_modules/.bin/tsc +21 -0
- package/src/apps/workflow/node_modules/.bin/tsserver +21 -0
- package/src/apps/workflow/node_modules/.bin/tsx +21 -0
- package/src/apps/workflow/package.json +25 -0
- package/src/apps/workflow/public/editor/canvas.js +366 -0
- package/src/apps/workflow/public/editor/drag-manager.js +326 -0
- package/src/apps/workflow/public/editor/edge-renderer.js +235 -0
- package/src/apps/workflow/public/editor/history-manager.js +147 -0
- package/src/apps/workflow/public/editor/layout-engine.js +159 -0
- package/src/apps/workflow/public/editor/node-renderer.js +199 -0
- package/src/apps/workflow/public/editor/selection-manager.js +193 -0
- package/src/apps/workflow/public/favicon.svg +7 -0
- package/src/apps/workflow/public/models/node-types.js +300 -0
- package/src/apps/workflow/public/models/workflow-model.js +257 -0
- package/src/apps/workflow/public/page.css +406 -0
- package/src/apps/workflow/public/page.js +658 -0
- package/src/apps/workflow/public/panels/inspector.js +360 -0
- package/src/apps/workflow/public/panels/palette.js +106 -0
- package/src/apps/workflow/public/panels/run-view.js +275 -0
- package/src/apps/workflow/public/panels/toolbar.js +232 -0
- package/src/apps/workflow/public/panels/workflow-list.js +237 -0
- package/src/apps/workflow/public/state/store.js +47 -0
- package/src/apps/workflow/services/custom-node-loader.ts +48 -0
- package/src/apps/workflow/services/legacy-converter.ts +72 -0
- package/src/apps/workflow/services/run-manager.ts +190 -0
- package/src/apps/workflow/services/workflow-store.ts +424 -0
- package/src/apps/workflow/services/workflow-validator.test.ts +103 -0
- package/src/apps/workflow/services/workflow-validator.ts +98 -0
- package/src/apps/workflow/src/index.ts +10 -0
- package/src/apps/workflow/templates/ci-pipeline.json +18 -0
- package/src/apps/workflow/templates/code-review.json +22 -0
- package/src/apps/workflow/templates/kanban-testing.json +24 -0
- package/src/apps/workflow/tsconfig.json +8 -0
- package/src/apps/workflow/types.ts +268 -0
- package/src/packages/auth-middleware.ts +14 -0
- package/src/packages/design-tokens/.turbo/turbo-build.log +10 -0
- package/src/packages/design-tokens/STYLEGUIDE.md +414 -0
- package/src/packages/design-tokens/build.js +413 -0
- package/src/packages/design-tokens/demo/index.html +1367 -0
- package/src/packages/design-tokens/demo/proposition-a.html +717 -0
- package/src/packages/design-tokens/demo/proposition-b.html +1239 -0
- package/src/packages/design-tokens/demo/proposition-c.html +1049 -0
- package/src/packages/design-tokens/dist/tailwind-preset.js +115 -0
- package/src/packages/design-tokens/dist/tokens.css +345 -0
- package/src/packages/design-tokens/dist/tokens.d.ts +229 -0
- package/src/packages/design-tokens/dist/tokens.js +386 -0
- package/src/packages/design-tokens/package.json +25 -0
- package/src/packages/design-tokens/tokens.json +228 -0
- package/src/packages/devtools-middleware.ts +22 -0
- package/src/packages/eslint-config/index.js +63 -0
- package/src/packages/eslint-config/node_modules/.bin/eslint +21 -0
- package/src/packages/eslint-config/package.json +18 -0
- package/src/packages/json-file-store.ts +232 -0
- package/src/packages/mcp-utils/.turbo/turbo-build.log +5 -0
- package/src/packages/mcp-utils/dist/index.d.ts +33 -0
- package/src/packages/mcp-utils/dist/index.d.ts.map +1 -0
- package/src/packages/mcp-utils/dist/index.js +126 -0
- package/src/packages/mcp-utils/dist/index.js.map +1 -0
- package/src/packages/mcp-utils/node_modules/.bin/tsc +21 -0
- package/src/packages/mcp-utils/node_modules/.bin/tsserver +21 -0
- package/src/packages/mcp-utils/package.json +32 -0
- package/src/packages/mcp-utils/src/index.ts +171 -0
- package/src/packages/mcp-utils/tsconfig.json +9 -0
- package/src/packages/paths.ts +18 -0
- package/src/packages/project-context/index.js +55 -0
- package/src/packages/project-context/package.json +13 -0
- package/src/packages/project-store.ts +127 -0
- package/src/packages/server-sniffer.ts +132 -0
- package/src/packages/shared-assets/favicon.svg +7 -0
- package/src/packages/shared-assets/keymap-registry.js +512 -0
- package/src/packages/shared-assets/logo.svg +6 -0
- package/src/packages/shared-assets/package.json +11 -0
- package/src/packages/shared-assets/ui-utils.js +48 -0
- package/src/packages/shared-assets/voice-widget.d.ts +37 -0
- package/src/packages/shared-assets/voice-widget.js +695 -0
- package/src/packages/shared-types/.turbo/turbo-build.log +5 -0
- package/src/packages/shared-types/dist/index.d.ts +39 -0
- package/src/packages/shared-types/dist/index.d.ts.map +1 -0
- package/src/packages/shared-types/node_modules/.bin/tsc +21 -0
- package/src/packages/shared-types/node_modules/.bin/tsserver +21 -0
- package/src/packages/shared-types/package.json +25 -0
- package/src/packages/shared-types/src/index.ts +41 -0
- package/src/packages/shared-types/tsconfig.json +11 -0
- package/src/packages/tsconfig/base.json +15 -0
- package/src/packages/tsconfig/next.json +14 -0
- package/src/packages/tsconfig/node.json +11 -0
- package/src/packages/tsconfig/package.json +10 -0
- package/turbo.json +25 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
// ── Test App — Page Module ────────────────────────────────────────────
|
|
2
|
+
// ES module that exports mount(container, ctx), unmount(container),
|
|
3
|
+
// and onProjectChange(project).
|
|
4
|
+
|
|
5
|
+
import { timeAgo, formatDuration } from '/shared-assets/ui-utils.js';
|
|
6
|
+
|
|
7
|
+
let activeProjectPath = null;
|
|
8
|
+
let _refreshTimer = null;
|
|
9
|
+
let _container = null;
|
|
10
|
+
let _visibilityHandler = null;
|
|
11
|
+
|
|
12
|
+
// ── HTML (body content, no script tags) ──────────────────────────────
|
|
13
|
+
|
|
14
|
+
const BODY_HTML = `
|
|
15
|
+
<header>
|
|
16
|
+
<div class="brand">
|
|
17
|
+
Test
|
|
18
|
+
</div>
|
|
19
|
+
<div class="header-meta">
|
|
20
|
+
<span id="pending-badge" class="badge badge-pending" role="status" aria-live="polite" style="display:none">
|
|
21
|
+
<span id="pending-count">0</span> pending
|
|
22
|
+
</span>
|
|
23
|
+
<span class="badge badge-idle" id="status-badge">ready</span>
|
|
24
|
+
<span>
|
|
25
|
+
auto-refresh 5s
|
|
26
|
+
<span class="refresh-indicator" id="refresh-dot"></span>
|
|
27
|
+
</span>
|
|
28
|
+
</div>
|
|
29
|
+
</header>
|
|
30
|
+
|
|
31
|
+
<main>
|
|
32
|
+
<div class="saved-section">
|
|
33
|
+
<div class="section-title">Saved Tests</div>
|
|
34
|
+
<div id="saved-list">
|
|
35
|
+
<div class="saved-empty" id="saved-empty">No saved tests yet.</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="results-section">
|
|
40
|
+
<div class="results-header">
|
|
41
|
+
<div class="section-title" style="margin-bottom:0">Recent Runs</div>
|
|
42
|
+
<button class="btn btn-clear" id="clear-results-btn" style="display:none">Clear</button>
|
|
43
|
+
</div>
|
|
44
|
+
<div id="results-list">
|
|
45
|
+
<div class="results-empty" id="results-empty">No recent runs.</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div class="section-title">Pending Scenarios</div>
|
|
50
|
+
<div id="pending-list">
|
|
51
|
+
<div class="empty" id="empty-state">
|
|
52
|
+
No pending scenarios.
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<details class="info-card-details">
|
|
57
|
+
<summary>Setup & Usage</summary>
|
|
58
|
+
<div class="info-card">
|
|
59
|
+
Automated browser testing that executes real user interactions — clicks, typing, navigation,
|
|
60
|
+
and assertions — against a live app. Submit test scenarios via the
|
|
61
|
+
<code>POST /api/trigger/scenarios</code> REST endpoint or the
|
|
62
|
+
<code>test_run_scenario</code> MCP tool, and watch them run in real time.<br/><br/>
|
|
63
|
+
<strong>Setup:</strong> For external apps, add one script tag to enable both automation and log capture:<br/>
|
|
64
|
+
<code><script src="http://localhost:7000/devtools.js?target=/path/to/app"></script></code><br/>
|
|
65
|
+
The <code>?target</code> param is required — it sets the app directory for log capture and test scenarios.<br/><br/>
|
|
66
|
+
DevGlide monorepo apps use the shared middleware and need no manual setup.<br/>
|
|
67
|
+
Use simple app names as targets (e.g. <code>"kanban"</code>, <code>"dashboard"</code>) — absolute paths also work.<br/><br/>
|
|
68
|
+
<strong>Tip:</strong> Prefer clicking links and buttons over using <code>navigate</code>.
|
|
69
|
+
The <code>navigate</code> command should only be used when no interactive element is available
|
|
70
|
+
(e.g. initial deep-link before a test starts). Overusing it bypasses the UI layer and can
|
|
71
|
+
cause false positives.
|
|
72
|
+
</div>
|
|
73
|
+
</details>
|
|
74
|
+
</main>
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
function flash() {
|
|
80
|
+
if (!_container) return;
|
|
81
|
+
const dot = _container.querySelector('#refresh-dot');
|
|
82
|
+
if (!dot) return;
|
|
83
|
+
dot.classList.add('flash');
|
|
84
|
+
setTimeout(() => dot.classList.remove('flash'), 300);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Data fetching ───────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
async function refresh() {
|
|
90
|
+
if (!_container) return;
|
|
91
|
+
try {
|
|
92
|
+
const params = activeProjectPath
|
|
93
|
+
? '?projectPath=' + encodeURIComponent(activeProjectPath)
|
|
94
|
+
: '';
|
|
95
|
+
const res = await fetch('/api/test/trigger/status' + params);
|
|
96
|
+
if (!res.ok) return;
|
|
97
|
+
const { pendingScenarios } = await res.json();
|
|
98
|
+
|
|
99
|
+
const pendingBadge = _container.querySelector('#pending-badge');
|
|
100
|
+
if (pendingScenarios > 0) {
|
|
101
|
+
_container.querySelector('#pending-count').textContent = pendingScenarios;
|
|
102
|
+
pendingBadge.style.display = '';
|
|
103
|
+
} else {
|
|
104
|
+
pendingBadge.style.display = 'none';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
flash();
|
|
108
|
+
refreshSaved();
|
|
109
|
+
refreshResults();
|
|
110
|
+
} catch (e) {
|
|
111
|
+
// ignore network errors
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function refreshSaved() {
|
|
116
|
+
if (!_container) return;
|
|
117
|
+
try {
|
|
118
|
+
const params = activeProjectPath
|
|
119
|
+
? '?projectPath=' + encodeURIComponent(activeProjectPath)
|
|
120
|
+
: '';
|
|
121
|
+
const res = await fetch('/api/test/trigger/scenarios/saved' + params);
|
|
122
|
+
if (!res.ok) return;
|
|
123
|
+
const scenarios = await res.json();
|
|
124
|
+
|
|
125
|
+
const list = _container.querySelector('#saved-list');
|
|
126
|
+
const emptyEl = _container.querySelector('#saved-empty');
|
|
127
|
+
|
|
128
|
+
if (!scenarios.length) {
|
|
129
|
+
list.innerHTML = '';
|
|
130
|
+
list.appendChild(emptyEl);
|
|
131
|
+
emptyEl.style.display = '';
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
emptyEl.style.display = 'none';
|
|
136
|
+
const fragment = document.createDocumentFragment();
|
|
137
|
+
|
|
138
|
+
for (const s of scenarios) {
|
|
139
|
+
const card = document.createElement('div');
|
|
140
|
+
card.className = 'saved-card';
|
|
141
|
+
card.dataset.id = s.id;
|
|
142
|
+
|
|
143
|
+
const info = document.createElement('div');
|
|
144
|
+
info.className = 'saved-info';
|
|
145
|
+
|
|
146
|
+
const name = document.createElement('div');
|
|
147
|
+
name.className = 'saved-name';
|
|
148
|
+
name.textContent = s.name;
|
|
149
|
+
|
|
150
|
+
const details = document.createElement('div');
|
|
151
|
+
details.className = 'saved-details';
|
|
152
|
+
const stepCount = s.steps ? s.steps.length : 0;
|
|
153
|
+
const runCount = s.runCount || 0;
|
|
154
|
+
const lastRun = s.lastRunAt ? timeAgo(s.lastRunAt) : 'never';
|
|
155
|
+
details.textContent = (s.target || 'no target')
|
|
156
|
+
+ ' \u00b7 ' + stepCount + ' step' + (stepCount !== 1 ? 's' : '')
|
|
157
|
+
+ ' \u00b7 ' + runCount + ' run' + (runCount !== 1 ? 's' : '')
|
|
158
|
+
+ ' \u00b7 last run ' + lastRun;
|
|
159
|
+
|
|
160
|
+
info.appendChild(name);
|
|
161
|
+
info.appendChild(details);
|
|
162
|
+
|
|
163
|
+
const actions = document.createElement('div');
|
|
164
|
+
actions.className = 'saved-actions';
|
|
165
|
+
|
|
166
|
+
const runBtn = document.createElement('button');
|
|
167
|
+
runBtn.className = 'btn btn-run';
|
|
168
|
+
runBtn.textContent = 'Run';
|
|
169
|
+
runBtn.addEventListener('click', () => runSaved(s.id));
|
|
170
|
+
|
|
171
|
+
const delBtn = document.createElement('button');
|
|
172
|
+
delBtn.className = 'btn btn-delete';
|
|
173
|
+
delBtn.textContent = 'Delete';
|
|
174
|
+
delBtn.addEventListener('click', () => deleteSaved(s.id));
|
|
175
|
+
|
|
176
|
+
actions.appendChild(runBtn);
|
|
177
|
+
actions.appendChild(delBtn);
|
|
178
|
+
|
|
179
|
+
card.appendChild(info);
|
|
180
|
+
card.appendChild(actions);
|
|
181
|
+
fragment.appendChild(card);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Preserve the hidden empty element
|
|
185
|
+
list.innerHTML = '';
|
|
186
|
+
emptyEl.style.display = 'none';
|
|
187
|
+
list.appendChild(emptyEl);
|
|
188
|
+
list.appendChild(fragment);
|
|
189
|
+
} catch (e) {
|
|
190
|
+
// ignore network errors
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function runSaved(id) {
|
|
195
|
+
if (!_container) return;
|
|
196
|
+
try {
|
|
197
|
+
await fetch('/api/test/trigger/scenarios/saved/' + encodeURIComponent(id) + '/run', {
|
|
198
|
+
method: 'POST'
|
|
199
|
+
});
|
|
200
|
+
const card = _container.querySelector('.saved-card[data-id="' + id + '"]');
|
|
201
|
+
if (card) {
|
|
202
|
+
card.classList.add('just-triggered');
|
|
203
|
+
setTimeout(() => card.classList.remove('just-triggered'), 1200);
|
|
204
|
+
}
|
|
205
|
+
refresh();
|
|
206
|
+
refreshSaved();
|
|
207
|
+
} catch (e) {
|
|
208
|
+
// ignore
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function deleteSaved(id) {
|
|
213
|
+
try {
|
|
214
|
+
await fetch('/api/test/trigger/scenarios/saved/' + encodeURIComponent(id), {
|
|
215
|
+
method: 'DELETE'
|
|
216
|
+
});
|
|
217
|
+
refreshSaved();
|
|
218
|
+
} catch (e) {
|
|
219
|
+
// ignore
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ── Results history ─────────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
let _resultsCleared = false;
|
|
226
|
+
let _expandedErrors = new Set();
|
|
227
|
+
|
|
228
|
+
function clearResults() {
|
|
229
|
+
_resultsCleared = true;
|
|
230
|
+
_expandedErrors.clear();
|
|
231
|
+
renderResults([]);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function renderResults(results) {
|
|
235
|
+
if (!_container) return;
|
|
236
|
+
const list = _container.querySelector('#results-list');
|
|
237
|
+
const emptyEl = _container.querySelector('#results-empty');
|
|
238
|
+
const clearBtn = _container.querySelector('#clear-results-btn');
|
|
239
|
+
if (!list || !emptyEl) return;
|
|
240
|
+
|
|
241
|
+
if (!results.length) {
|
|
242
|
+
list.innerHTML = '';
|
|
243
|
+
list.appendChild(emptyEl);
|
|
244
|
+
emptyEl.style.display = '';
|
|
245
|
+
if (clearBtn) clearBtn.style.display = 'none';
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
emptyEl.style.display = 'none';
|
|
250
|
+
if (clearBtn) clearBtn.style.display = '';
|
|
251
|
+
|
|
252
|
+
// Sort newest first
|
|
253
|
+
const sorted = results.slice().sort((a, b) =>
|
|
254
|
+
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const fragment = document.createDocumentFragment();
|
|
258
|
+
|
|
259
|
+
for (const r of sorted) {
|
|
260
|
+
const card = document.createElement('div');
|
|
261
|
+
card.className = 'result-card';
|
|
262
|
+
|
|
263
|
+
const info = document.createElement('div');
|
|
264
|
+
info.className = 'result-info';
|
|
265
|
+
|
|
266
|
+
// Top row: ID + status badge
|
|
267
|
+
const topRow = document.createElement('div');
|
|
268
|
+
topRow.className = 'result-top-row';
|
|
269
|
+
|
|
270
|
+
const idEl = document.createElement('span');
|
|
271
|
+
idEl.className = 'result-id';
|
|
272
|
+
idEl.textContent = r.id.length > 12 ? r.id.slice(0, 12) + '\u2026' : r.id;
|
|
273
|
+
idEl.title = r.id;
|
|
274
|
+
|
|
275
|
+
const statusEl = document.createElement('span');
|
|
276
|
+
statusEl.className = 'result-status ' + r.status;
|
|
277
|
+
statusEl.textContent = r.status;
|
|
278
|
+
|
|
279
|
+
topRow.appendChild(idEl);
|
|
280
|
+
topRow.appendChild(statusEl);
|
|
281
|
+
|
|
282
|
+
// Details row: duration + time ago + failed step info
|
|
283
|
+
const details = document.createElement('div');
|
|
284
|
+
details.className = 'result-details';
|
|
285
|
+
|
|
286
|
+
const parts = [];
|
|
287
|
+
const dur = formatDuration(r.duration);
|
|
288
|
+
if (dur) parts.push(dur);
|
|
289
|
+
parts.push(timeAgo(r.createdAt));
|
|
290
|
+
if (r.status === 'failed' && r.failedStep != null) {
|
|
291
|
+
parts.push('failed at step ' + r.failedStep);
|
|
292
|
+
}
|
|
293
|
+
details.textContent = parts.join(' \u00b7 ');
|
|
294
|
+
|
|
295
|
+
// Error toggle for failed scenarios
|
|
296
|
+
if (r.status === 'failed' && r.error) {
|
|
297
|
+
const toggleBtn = document.createElement('button');
|
|
298
|
+
toggleBtn.className = 'result-error-toggle';
|
|
299
|
+
const isExpanded = _expandedErrors.has(r.id);
|
|
300
|
+
toggleBtn.textContent = isExpanded ? 'hide error' : 'show error';
|
|
301
|
+
|
|
302
|
+
const errorEl = document.createElement('div');
|
|
303
|
+
errorEl.className = 'result-error';
|
|
304
|
+
errorEl.textContent = r.error;
|
|
305
|
+
errorEl.style.display = isExpanded ? '' : 'none';
|
|
306
|
+
|
|
307
|
+
toggleBtn.addEventListener('click', () => {
|
|
308
|
+
const nowExpanded = errorEl.style.display === 'none';
|
|
309
|
+
errorEl.style.display = nowExpanded ? '' : 'none';
|
|
310
|
+
toggleBtn.textContent = nowExpanded ? 'hide error' : 'show error';
|
|
311
|
+
if (nowExpanded) {
|
|
312
|
+
_expandedErrors.add(r.id);
|
|
313
|
+
} else {
|
|
314
|
+
_expandedErrors.delete(r.id);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
details.appendChild(toggleBtn);
|
|
319
|
+
info.appendChild(topRow);
|
|
320
|
+
info.appendChild(details);
|
|
321
|
+
info.appendChild(errorEl);
|
|
322
|
+
} else {
|
|
323
|
+
info.appendChild(topRow);
|
|
324
|
+
info.appendChild(details);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
card.appendChild(info);
|
|
328
|
+
fragment.appendChild(card);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
list.innerHTML = '';
|
|
332
|
+
emptyEl.style.display = 'none';
|
|
333
|
+
list.appendChild(emptyEl);
|
|
334
|
+
list.appendChild(fragment);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function refreshResults() {
|
|
338
|
+
if (!_container || _resultsCleared) return;
|
|
339
|
+
try {
|
|
340
|
+
const params = activeProjectPath
|
|
341
|
+
? '?projectPath=' + encodeURIComponent(activeProjectPath)
|
|
342
|
+
: '';
|
|
343
|
+
const res = await fetch('/api/test/trigger/scenarios/results' + params);
|
|
344
|
+
if (!res.ok) return;
|
|
345
|
+
const results = await res.json();
|
|
346
|
+
renderResults(results);
|
|
347
|
+
} catch (e) {
|
|
348
|
+
// ignore network errors
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ── Refresh polling ─────────────────────────────────────────────────
|
|
353
|
+
|
|
354
|
+
function _startRefreshPoll() {
|
|
355
|
+
if (_refreshTimer) return;
|
|
356
|
+
refresh();
|
|
357
|
+
_refreshTimer = setInterval(refresh, 5000);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function _stopRefreshPoll() {
|
|
361
|
+
if (_refreshTimer) { clearInterval(_refreshTimer); _refreshTimer = null; }
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ── Exports ─────────────────────────────────────────────────────────
|
|
365
|
+
|
|
366
|
+
export function mount(container, ctx) {
|
|
367
|
+
_container = container;
|
|
368
|
+
|
|
369
|
+
// 1. Scope the container
|
|
370
|
+
container.classList.add('page-test');
|
|
371
|
+
|
|
372
|
+
// 2. Build HTML
|
|
373
|
+
container.innerHTML = BODY_HTML;
|
|
374
|
+
|
|
375
|
+
// 3. Set initial project from context
|
|
376
|
+
activeProjectPath = ctx?.project?.path || null;
|
|
377
|
+
|
|
378
|
+
// 4. Clear results button
|
|
379
|
+
const clearBtn = container.querySelector('#clear-results-btn');
|
|
380
|
+
if (clearBtn) clearBtn.addEventListener('click', clearResults);
|
|
381
|
+
|
|
382
|
+
// 5. Visibility-change handler for auto-refresh
|
|
383
|
+
_visibilityHandler = () => {
|
|
384
|
+
if (document.hidden) _stopRefreshPoll();
|
|
385
|
+
else _startRefreshPoll();
|
|
386
|
+
};
|
|
387
|
+
document.addEventListener('visibilitychange', _visibilityHandler);
|
|
388
|
+
|
|
389
|
+
// 6. Start polling
|
|
390
|
+
_startRefreshPoll();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function unmount(container) {
|
|
394
|
+
// 1. Stop refresh timer
|
|
395
|
+
_stopRefreshPoll();
|
|
396
|
+
|
|
397
|
+
// 2. Remove visibility handler
|
|
398
|
+
if (_visibilityHandler) {
|
|
399
|
+
document.removeEventListener('visibilitychange', _visibilityHandler);
|
|
400
|
+
_visibilityHandler = null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// 3. Remove scope class & clear HTML
|
|
404
|
+
container.classList.remove('page-test');
|
|
405
|
+
container.innerHTML = '';
|
|
406
|
+
|
|
407
|
+
// 4. Clear module references
|
|
408
|
+
_container = null;
|
|
409
|
+
activeProjectPath = null;
|
|
410
|
+
_resultsCleared = false;
|
|
411
|
+
_expandedErrors.clear();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function onProjectChange(project) {
|
|
415
|
+
activeProjectPath = project?.path || null;
|
|
416
|
+
refresh();
|
|
417
|
+
}
|