scenescout 3.1.0 → 3.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/CHANGELOG.md +18 -0
- package/dist/engine/replay.js +64 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# scenescout
|
|
2
2
|
|
|
3
|
+
## 3.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 99562e7: The run's page now notices when the engine behind it has exited.
|
|
8
|
+
|
|
9
|
+
That address is served by a process. Once the process is gone, reloading it
|
|
10
|
+
gets the browser's own "site can't be reached" and the tab is lost, although
|
|
11
|
+
everything on the page was still readable a moment earlier. The served copy
|
|
12
|
+
now watches the engine, and when it goes says so in place: the page is still
|
|
13
|
+
good, reloading will not reach anything, and the copy that survives — with its
|
|
14
|
+
frames — is at the path it names, ready to copy. Leaving the page from then on
|
|
15
|
+
asks first, so a reflexive refresh cannot throw it away.
|
|
16
|
+
|
|
17
|
+
A browser will not follow a `file://` link from a served page, so the saved
|
|
18
|
+
copy cannot be opened from there; the path is offered instead. The copy on
|
|
19
|
+
disk carries none of this and stays a plain document with no script in it.
|
|
20
|
+
|
|
3
21
|
## 3.1.0
|
|
4
22
|
|
|
5
23
|
### Minor Changes
|
package/dist/engine/replay.js
CHANGED
|
@@ -254,8 +254,8 @@ export function renderMarkdown(md, evidence = [], framePrefix = "", savedAt = ""
|
|
|
254
254
|
return out.join("\n");
|
|
255
255
|
}
|
|
256
256
|
const STYLE = `
|
|
257
|
-
:root { color-scheme: light dark; --bg:#f6f7f9; --panel:#fff; --line:#d9dde3; --text:#15181d; --muted:#5d6673; --accent:#2563eb; --bad:#b91c1c; }
|
|
258
|
-
@media (prefers-color-scheme: dark) { :root { --bg:#0e1116; --panel:#161a21; --line:#2a303a; --text:#e6e9ee; --muted:#98a2b3; --accent:#7aa2ff; --bad:#fca5a5; } }
|
|
257
|
+
:root { color-scheme: light dark; --bg:#f6f7f9; --panel:#fff; --line:#d9dde3; --text:#15181d; --muted:#5d6673; --accent:#2563eb; --bad:#b91c1c; --warn:#7c4a03; --warn-bg:#fef3c7; }
|
|
258
|
+
@media (prefers-color-scheme: dark) { :root { --bg:#0e1116; --panel:#161a21; --line:#2a303a; --text:#e6e9ee; --muted:#98a2b3; --accent:#7aa2ff; --bad:#fca5a5; --warn:#fbbf24; --warn-bg:#3a2a08; } }
|
|
259
259
|
* { box-sizing: border-box; }
|
|
260
260
|
body { margin:0; background:var(--bg); color:var(--text); font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif; }
|
|
261
261
|
header { position:sticky; top:0; z-index:2; display:flex; flex-wrap:wrap; gap:8px 16px; align-items:baseline; padding:14px 20px; background:var(--panel); border-bottom:1px solid var(--line); }
|
|
@@ -303,6 +303,13 @@ details.evidence > summary { cursor:pointer; color:var(--muted); font-size:13px;
|
|
|
303
303
|
details.evidence .shots { display:flex; flex-wrap:wrap; gap:14px; margin-top:12px; }
|
|
304
304
|
details.evidence figure { margin:0; max-width:min(100%,460px); }
|
|
305
305
|
details.evidence img { width:100%; max-height:300px; object-fit:cover; object-position:top; border:1px solid var(--line); border-radius:6px; display:block; background:var(--panel); }
|
|
306
|
+
#gone-bar { position:sticky; top:0; z-index:3; margin:0; padding:14px 22px; background:var(--warn-bg); color:var(--warn);
|
|
307
|
+
border-bottom:1px solid var(--line); }
|
|
308
|
+
#gone-bar p { margin:0 0 6px; max-width:68ch; }
|
|
309
|
+
#gone-bar .path { display:flex; flex-wrap:wrap; gap:10px; align-items:center; }
|
|
310
|
+
#gone-bar code { font:12px/1.6 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; overflow-wrap:anywhere; user-select:all; }
|
|
311
|
+
#gone-bar button { font:inherit; padding:4px 10px; border:1px solid currentColor; border-radius:6px; background:transparent; color:inherit; cursor:pointer; }
|
|
312
|
+
#gone-bar .why { margin:0; font-size:12px; opacity:.8; }
|
|
306
313
|
.onDisk { display:block; margin-top:4px; color:var(--muted); font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; overflow-wrap:anywhere; }
|
|
307
314
|
header .served { flex:1 1 100%; margin:6px 0 0; color:var(--muted); font-size:12px; }
|
|
308
315
|
header .served code { font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; overflow-wrap:anywhere; }
|
|
@@ -312,6 +319,53 @@ figure.gone img, a.gone img { display:none; }
|
|
|
312
319
|
a.frame.gone { display:block; max-width:min(100%,720px); }
|
|
313
320
|
details.evidence figcaption { margin-top:4px; color:var(--muted); font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; overflow-wrap:anywhere; }
|
|
314
321
|
`;
|
|
322
|
+
/**
|
|
323
|
+
* Only the SERVED copy carries this. It watches the engine, and the moment the
|
|
324
|
+
* engine is gone it says so, because from then on a refresh gets the browser's
|
|
325
|
+
* "site can't be reached" and the tab is lost for nothing — this page is still
|
|
326
|
+
* perfectly readable, and the copy on disk is the one that survives.
|
|
327
|
+
*
|
|
328
|
+
* A browser refuses to follow a `file://` link from an `http://` page, so the
|
|
329
|
+
* saved copy cannot be opened from here. The path is offered to copy instead.
|
|
330
|
+
*
|
|
331
|
+
* No template literals and no backticks: this whole module is one.
|
|
332
|
+
*/
|
|
333
|
+
function exitWatch(savedFile) {
|
|
334
|
+
return ("<script>(function () {\n" +
|
|
335
|
+
" var saved = " +
|
|
336
|
+
JSON.stringify(savedFile) +
|
|
337
|
+
";\n" +
|
|
338
|
+
" var gone = false;\n" +
|
|
339
|
+
" var bar = document.getElementById('gone-bar');\n" +
|
|
340
|
+
" function ended() {\n" +
|
|
341
|
+
" if (gone) return;\n" +
|
|
342
|
+
" gone = true;\n" +
|
|
343
|
+
" bar.hidden = false;\n" +
|
|
344
|
+
" document.body.classList.add('engine-gone');\n" +
|
|
345
|
+
" }\n" +
|
|
346
|
+
" function check() {\n" +
|
|
347
|
+
" fetch('api/status', { cache: 'no-store' })\n" +
|
|
348
|
+
" .then(function (r) { if (!r.ok && r.status >= 500) ended(); })\n" +
|
|
349
|
+
" .catch(ended);\n" +
|
|
350
|
+
" }\n" +
|
|
351
|
+
" setInterval(check, 5000);\n" +
|
|
352
|
+
" check();\n" +
|
|
353
|
+
" document.getElementById('gone-copy').addEventListener('click', function () {\n" +
|
|
354
|
+
" var self = this;\n" +
|
|
355
|
+
" function said(text) { self.textContent = text; setTimeout(function () { self.textContent = 'Copy the path'; }, 2000); }\n" +
|
|
356
|
+
" if (navigator.clipboard && navigator.clipboard.writeText) {\n" +
|
|
357
|
+
" navigator.clipboard.writeText(saved).then(function () { said('Copied'); }, function () { said('Select it and copy'); });\n" +
|
|
358
|
+
" } else { said('Select it and copy'); }\n" +
|
|
359
|
+
" });\n" +
|
|
360
|
+
" // Refresh, close, back — all of them lose this page once the engine has\n" +
|
|
361
|
+
" // exited, and none of them can be told apart here. Ask first.\n" +
|
|
362
|
+
" window.addEventListener('beforeunload', function (e) {\n" +
|
|
363
|
+
" if (!gone) return;\n" +
|
|
364
|
+
" e.preventDefault();\n" +
|
|
365
|
+
" e.returnValue = '';\n" +
|
|
366
|
+
" });\n" +
|
|
367
|
+
"})();</script>");
|
|
368
|
+
}
|
|
315
369
|
/** The whole document: one file, no external assets, opens from the file system. */
|
|
316
370
|
export function buildReplayHtml(input) {
|
|
317
371
|
const framed = input.sessions.some((s) => s.steps.some((x) => x.frame));
|
|
@@ -333,6 +387,13 @@ export function buildReplayHtml(input) {
|
|
|
333
387
|
${savedAt ? `<p class="served">This page is served by the engine and goes when it does. The copy that stays is <code>${escapeHtml(savedAt)}/report.html</code>, beside the frames it shows.</p>` : ""}
|
|
334
388
|
<nav><a href="#report">Report</a><a href="#steps">Steps</a></nav>
|
|
335
389
|
</header>
|
|
390
|
+
${savedAt
|
|
391
|
+
? `<div id="gone-bar" role="alert" data-testid="run-engine-gone" hidden>
|
|
392
|
+
<p><b>The engine has exited.</b> Everything here is still readable, but reloading this address will not reach anything. The copy that survives, with its frames, is on disk:</p>
|
|
393
|
+
<p class="path"><code>${escapeHtml(savedAt)}/report.html</code> <button type="button" id="gone-copy" data-testid="run-copy-path">Copy the path</button></p>
|
|
394
|
+
<p class="why">A browser will not open a file from a served page, so it has to be opened from there.</p>
|
|
395
|
+
</div>`
|
|
396
|
+
: ""}
|
|
336
397
|
<main>
|
|
337
398
|
<h2 id="report">Report</h2>
|
|
338
399
|
${renderMarkdown(input.markdown, input.evidence ?? [], prefix, savedAt)}
|
|
@@ -342,6 +403,7 @@ ${renderMarkdown(input.markdown, input.evidence ?? [], prefix, savedAt)}
|
|
|
342
403
|
: "This run was not recorded, so there are no frames — attach with record:true to keep them."}</p>
|
|
343
404
|
${sessions || "<p>No session recorded any action.</p>"}
|
|
344
405
|
</main>
|
|
406
|
+
${savedAt ? exitWatch(`${savedAt}/report.html`) : ""}
|
|
345
407
|
</body>
|
|
346
408
|
</html>
|
|
347
409
|
`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scenescout",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "SceneScout — exploratory UI testing for AI coding agents. An MCP server that gives any agent (Claude Code, Cursor, VS Code Copilot, Codex, Gemini CLI and others) a structured view of a running web app, always-on oracles, a network-level write policy, memory across runs and a gap-checked report.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "brunoboto96",
|