@ai-setting/roy-plugin-task-show 2.0.7 → 2.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/public/index.html +16 -0
- package/public/mermaid-renderer.js +180 -0
- package/public/style.css +58 -0
- package/public/tool-call-detail.js +74 -0
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-setting/roy-plugin-task-show",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8",
|
|
4
4
|
"type": "tool-plugin",
|
|
5
5
|
"description": "v2.0.0: Major visual overhaul of the per-task detail page \u2014 VS Code-style tool-call browser powered by Monaco Editor + LCS-based diff + project file tree with mermaid-driven navigation. The detail page now renders a 2-column layout: a sticky left sidebar with the project's git-tracked file tree (chevron-toggle directories, `is-affected` highlight for files touched by the current task), and a main column with the existing Mermaid + stats + toolcalls table + a new dedicated `<section id=\"tool-call-detail-panel\">` that hosts the LCS diff body + Monaco container for the currently-selected tool call. The legacy inline `<details class=\"diff-panel\">` (naive split-and-filter, prone to mis-tagging context lines) is replaced by `<ol class=\"diff-body\">` with proper added/removed/context markers driven by a real LCS dynamic-programming table (`computeDiff()` in `src/tool-call-detail.ts`). Tool calls with a file path get a Monaco Editor placeholder (`<div class=\"monaco-editor\" data-file-path=\"...\" data-language=\"...\">`) which the client-side `public/tool-call-detail.js` lazy-loads from `cdn.jsdelivr.net/npm/monaco-editor@0.45.0` the first time the user clicks a tool row. The Mermaid `__toolClick(toolId)` callback now drives THREE things: (a) the existing row scroll + highlight (preserved from v1.x), (b) the dedicated detail panel re-renders with the matching call, (c) the file-tree sidebar highlights the corresponding file (when `data-file-path` is present). New `GET /api/task/:id/file-tree` endpoint serves the git-tracked file list (`{ files: string[] }`) with a 30-second in-memory TTL + 8-entry FIFO bound. New modules: `src/file-tree.ts` (pure data layer: `buildFileTree / extractAffectedPaths / findNodeByPath / collectAllPaths / gitLsFiles / parseLsFiles`), `src/tool-call-detail.ts` (SSR + LCS diff + HTML escaping), `public/file-tree.js` (vanilla JS hydrator with chevron toggle + keyboard navigation + scrollIntoView), `public/tool-call-detail.js` (Monaco AMD loader + `__toolClick` wrapper + file-content fetch). New tests: `test/file-tree.test.ts` (21 cases \u2014 empty/single/nested/dedup/sort/depth/parseLsFiles/gitLsFiles), `test/tool-call-detail.test.ts` (22 cases \u2014 LCS diff edges, HTML escape, path aliases, summary stats), `test/tool-call-detail-server-integration.test.ts` (7 cases \u2014 SSR HTML contracts + endpoint), `test/tool-call-detail-jsdom.test.ts` (7 cases \u2014 client-side hydrators). v1.2.0: CSS context & packaged release hotfix. The plugin's public assets (notably `public/style.css`) and runtime adapters now correctly resolve relative to the installed package directory even when consumed via the published npm tarball. The session-scoped `TaskSessionStore` now preserves the full host session context (parent-child task links, plugin-handle id, env scope) across render cycles \u2014 previously the session was collapsed to its `sessionId` on first load and never refreshed, so the home page lost the \u300csession ancestors\u300d chain and external tasks from outside the current session silently disappeared from the tree. Adds `src/task-metadata.ts` as the single source of truth for the public `Task` shape exposed by `/api/tasks` + `/api/tasks/:id`, including the v1.0.0+ `processDescription` field, and re-exports it through the CLI adapters (`cli-tasks-adapter.ts` + `cli-tasks-tree-adapter.ts`) so the home page tree + the per-task page render against the same metadata contract. Bundles 244-line regression test (`test/context-and-packed-release.test.ts`) that boots the plugin from the **npm-pack** directory (not the repo working tree), spawns `roy-agent tasks get <id> --json`, and asserts (a) `public/style.css` is present and \u2265 64 lines, (b) the `/api/events` SSE endpoint survives a reload, and (c) `task.session` survives a render cycle. v1.1.0: Full Server-Sent Events realtime subscription across 3 event classes (task.created / operation.updated / tool.called) on both the home page and the per-task /task/<id> page. The per-task pipeline now subscribes to /api/events and patches the DOM in place on operation.updated \u2014 no more 5s-poll delay before the user sees a new milestone. The 'Task lifecycle pipeline' header badge is replaced by a 5-state SSE-aware badge (stale / connecting / live / reconnecting / error) so the user can tell at a glance whether real-time updates are flowing, the connection dropped, or 3+ consecutive errors triggered the polling fallback. Legacy boolean `stale` cache-TTL pill and `tool.recorded` event name are preserved for back-compat with v0.9.x / v1.0.0 clients. v1.0.0: First stable release. Replaces the v0.9.x fixture-based verify scripts (which built fake TaskOperationsEnvelope and never invoked the real `roy-agent` CLI, masking regressions in the public-schema `processDescription` field) with a real-CLI scenario test + verify (`test/process-description-real-scenario.test.ts` + `scripts/verify-v100-real-scenario.ts`) that spawns `roy-agent tasks get <id> --operations --json` via `defaultRunner` and asserts the API response carries `processDescription` end-to-end. The 0.9.9 processDescription fix is preserved verbatim \u2014 this release only swaps the verify surface. Visualize the tool call chain of a task on a local web service with real-time SSE updates. v0.9.9: Task lifecycle pipeline on /task/<id> now exposes BOTH the milestone badge AND the \u300c\u8fc7\u7a0b\u63cf\u8ff0\u300d column at a glance \u2014 the server-side `/api/tasks/:id/operations` endpoint exposes `processDescription` on every operation (no longer stripped from the public schema), the client-side `renderPipelineHtml` mirrors the server's `.op-desc-block` + `.op-proc-block` block layout so SSR \u2194 CSR stay in sync, and a long-standing CSS right-side text-truncation bug in the pipeline timeline (long CJK titles overflowing the panel edge) is fixed via `min-width: 0` on `.op-row1` + `overflow-wrap: anywhere` on `.op-title`. v0.9.0: Session-scoped home page (only show tasks created after plugin load + their external ancestors), with per-row \u300c\u663e\u793a\u5168\u90e8\u680f\u4f4d\u300d toggle and lazy-loaded operations timeline; per-task Mermaid labels now correctly render CJK / mixed-Latin / emoji text (encoded as \\uXXXX before emission, decoded by the browser); detail page layout reordered to lifecycle \u2192 pipeline \u2192 stats \u2192 toolcalls \u2192 rawjson. v0.5.0+: page refreshes stream over GET /api/events (Server-Sent Events). Subscribes to tool:before.execute, tool:after.execute, task:before.create, task:after.create, task:after.complete (preferred, 2026-07-10+), and task:after.update (legacy fallback). v0.6.11: Mermaid re-rendering is delegated to a self-contained controller (public/mermaid-renderer.js) that prevents the SVG\u2192raw-source regression on async updates and surfaces recoverable .mermaid-error states. v0.6.12: Task lifecycle pipeline (operations timeline) server now emits data-task-id on the pipeline section; client preserves it on swap, so the page actually fetches /api/tasks/<id>/operations and renders the 7-op timeline (previously silently bailed). v0.7.0: Home page redesigned as a hierarchical task tree (driven by `roy-agent tasks tree --json`); new /api/tasks/tree endpoint with status / priority / type / root-id filters, expand/collapse UI, search, and live 30s polling. v0.8.0: per-task page Mermaid area now renders the hierarchical 'Task lifecycle + tools' view \u2014 each operation record owns a subgraph that nests its tool calls, with click callbacks (`window.__toolClick`) that scroll-into-view + highlight + auto-expand the matching row in the tool-call table below. Operation record descriptions (`description` + `processDescription`) are now always rendered inline (no `<details>` collapse) so the user sees the lifecycle state at a glance; a fallback `<details>` kicks in only for descriptions longer than 600 chars. v0.8.1: hotfix for two pre-existing bugs in v0.8.0 (browser smoke test surfaced after merge). (a) Mermaid click directives were emitted as `click t1 __toolClick(1)` (missing `call` keyword) \u2014 Mermaid 10's parser rejects this with `got 'PS'`. Fixed to `click t1 call __toolClick(1)` (the v10 grammar requires `call` to invoke a callback with arguments). (b) `buildMermaidSource` lived inside the `attachTaskPageTimeline` IIFE but was also called from a listener in the `attachToolClickBridge` IIFE \u2014 sibling IIFEs cannot see each other's locals, so the listener threw `ReferenceError: buildMermaidSource is not defined` and the Mermaid diagram silently failed to re-render after `task-show:lifecycle-ops-loaded`. Fixed by hoisting the function (and its three helpers) to script top-level so both IIFEs can see it via the script-wide closure; the function is also exposed on `window.buildMermaidSource` for tests + tooling. v0.8.3: tree-display fix (Task #2426). The home page used to look like a flat list of root tasks because `autoExpandFirstLevels(..., 2)` only opened the first 2 levels \u2014 30/47 roots were leaf nodes and the remaining 17 collapsed to one level so grandchildren were never visible. Default expand depth is now 3 (root + child + grandchild + great-grandchild are visible on first paint), the summary line now shows per-depth count pills (root / child / grandchild / great-grandchild / level-N), each `tree-row` carries a `data-depth` attribute so CSS can paint coloured left rails per level, and the duplicated 'Live tool-call sessions (legacy view)' panel that made the page look like both a flat table AND a tree is now hidden behind `#legacy-sessions[hidden]` (kept for future debug-toggle restoration). v0.8.10: bug-fix release (Task #2537 + Task #2534). (a) Heap-bounded plugin caches: OperationsCache and TasksTreeCache now enforce a hard maxEntries cap (default 256 / 64). Oldest stale entries are evicted before inserting a new one, so long-lived roy-agent sessions (BackgroundTaskManager + MemorySessionStore) no longer leak Map entries through the plugin's per-task caches \u2014 see Task #2537 for the heap-unbounded-state RED\u2192GREEN repro. (b) Mermaid CJK font-family: server.ts renderTaskPage now configures mermaid.initialize({ themeVariables: { fontFamily: '\"PingFang SC\", \"Microsoft YaHei\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"WenQuanYi Micro Hei\", sans-serif' } }) so Chinese node labels render correctly in browsers that have at least one of those fonts installed (see Task #2534).",
|
|
6
6
|
"main": "dist/index.js",
|
package/public/index.html
CHANGED
|
@@ -18,6 +18,22 @@
|
|
|
18
18
|
</p>
|
|
19
19
|
</header>
|
|
20
20
|
|
|
21
|
+
<!--
|
|
22
|
+
v2.0.8 (feat/mermaid-zoom): hidden toolbar template that
|
|
23
|
+
`mermaid-renderer.js` clones for every rendered diagram.
|
|
24
|
+
The actual buttons are bound to `MermaidRenderer.zoomIn /
|
|
25
|
+
zoomOut / resetZoom` by the renderer's mount handler — this
|
|
26
|
+
template is just the markup. Keyboard shortcuts (+/−/0) are
|
|
27
|
+
also wired by the renderer.
|
|
28
|
+
-->
|
|
29
|
+
<template id="mermaid-zoom-toolbar-template">
|
|
30
|
+
<div class="mermaid-zoom-toolbar" role="toolbar" aria-label="Mermaid diagram zoom controls">
|
|
31
|
+
<button type="button" class="mermaid-zoom-btn" data-mermaid-zoom="out" aria-label="Zoom out" title="Zoom out (−)">−</button>
|
|
32
|
+
<button type="button" class="mermaid-zoom-btn" data-mermaid-zoom="reset" aria-label="Reset zoom" title="Reset zoom (0)">100%</button>
|
|
33
|
+
<button type="button" class="mermaid-zoom-btn" data-mermaid-zoom="in" aria-label="Zoom in" title="Zoom in (+)">+</button>
|
|
34
|
+
</div>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
21
37
|
<section class="panel tree-controls">
|
|
22
38
|
<div class="tree-controls-row">
|
|
23
39
|
<label class="tree-search">
|
|
@@ -54,6 +54,14 @@
|
|
|
54
54
|
"use strict";
|
|
55
55
|
|
|
56
56
|
const DEFAULT_ID_PREFIX = "mmr";
|
|
57
|
+
// v2.0.8 (feat/mermaid-zoom): zoom-in / zoom-out step and
|
|
58
|
+
// sanity bounds. We default to 10% per click (matching the
|
|
59
|
+
// common browser zoom UX) and clamp to a range that keeps the
|
|
60
|
+
// diagram readable on both directions.
|
|
61
|
+
const DEFAULT_ZOOM_STEP = 0.1;
|
|
62
|
+
const ZOOM_MIN = 0.25;
|
|
63
|
+
const ZOOM_MAX = 3.0;
|
|
64
|
+
const ZOOM_DEFAULT = 1.0;
|
|
57
65
|
|
|
58
66
|
function escapeHtml(s) {
|
|
59
67
|
return String(s)
|
|
@@ -142,6 +150,11 @@
|
|
|
142
150
|
// leak references after the container is removed.
|
|
143
151
|
const inflight = new WeakMap(); // container -> { token: number, source: string, destroyed: boolean, lastRenderedSource: string }
|
|
144
152
|
const seq = new WeakMap(); // container -> number
|
|
153
|
+
// v2.0.8 (feat/mermaid-zoom): per-container zoom state,
|
|
154
|
+
// independent of the rendering state. Keyed by the same
|
|
155
|
+
// container reference so that re-rendering a diagram does
|
|
156
|
+
// not reset the user's zoom level.
|
|
157
|
+
const zoomState = new WeakMap(); // container -> number (0.25..3.0)
|
|
145
158
|
|
|
146
159
|
function nextSeq(container) {
|
|
147
160
|
const cur = seq.get(container) || 0;
|
|
@@ -182,6 +195,156 @@
|
|
|
182
195
|
return cur ? cur.lastRenderedSource : null;
|
|
183
196
|
}
|
|
184
197
|
|
|
198
|
+
// -----------------------------------------------------------------
|
|
199
|
+
// v2.0.8 (feat/mermaid-zoom): zoom-in / zoom-out / reset / get
|
|
200
|
+
// -----------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
function clampZoom(z) {
|
|
203
|
+
if (!Number.isFinite(z)) return ZOOM_DEFAULT;
|
|
204
|
+
if (z < ZOOM_MIN) return ZOOM_MIN;
|
|
205
|
+
if (z > ZOOM_MAX) return ZOOM_MAX;
|
|
206
|
+
return z;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function roundZoom(z) {
|
|
210
|
+
// Round to 2 decimal places so 1.1000000000000003 doesn't
|
|
211
|
+
// leak into the CSS transform (and so the trace payload is
|
|
212
|
+
// human-readable).
|
|
213
|
+
return Math.round(z * 100) / 100;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function getZoom(container) {
|
|
217
|
+
if (!container) return ZOOM_DEFAULT;
|
|
218
|
+
const v = zoomState.get(container);
|
|
219
|
+
return v == null ? ZOOM_DEFAULT : v;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function ensureZoomStage(container) {
|
|
223
|
+
// Lazy-create the wrapper if the user calls zoom before a
|
|
224
|
+
// successful mount (the regression test exercises this path).
|
|
225
|
+
if (!container || !container.querySelector) return null;
|
|
226
|
+
let stage = container.querySelector(".mermaid-zoom-stage");
|
|
227
|
+
if (stage) return stage;
|
|
228
|
+
stage = container.ownerDocument.createElement("div");
|
|
229
|
+
stage.className = "mermaid-zoom-stage";
|
|
230
|
+
// Re-parent any existing children (e.g. an already-rendered
|
|
231
|
+
// SVG) into the new wrapper so the zoom transform applies to
|
|
232
|
+
// them too. This keeps the existing render pipeline
|
|
233
|
+
// untouched — the controller just adds a transform layer.
|
|
234
|
+
const doc = container.ownerDocument;
|
|
235
|
+
const fragment = doc.createDocumentFragment
|
|
236
|
+
? doc.createDocumentFragment()
|
|
237
|
+
: null;
|
|
238
|
+
if (fragment) {
|
|
239
|
+
while (container.firstChild) {
|
|
240
|
+
fragment.appendChild(container.firstChild);
|
|
241
|
+
}
|
|
242
|
+
stage.appendChild(fragment);
|
|
243
|
+
}
|
|
244
|
+
container.appendChild(stage);
|
|
245
|
+
return stage;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* v2.0.8 (feat/mermaid-zoom): wire the optional toolbar
|
|
250
|
+
* template (`<template id="mermaid-zoom-toolbar-template">` in
|
|
251
|
+
* `index.html`) to this container's zoom methods. The
|
|
252
|
+
* toolbar is inserted BEFORE the zoom stage, so the visual
|
|
253
|
+
* order is: [toolbar] [diagram]. We look for the template
|
|
254
|
+
* by id; if it is not present (e.g. in unit tests that
|
|
255
|
+
* don't ship the HTML), the call is a silent no-op.
|
|
256
|
+
*/
|
|
257
|
+
function attachZoomToolbar(container) {
|
|
258
|
+
if (!container || !container.ownerDocument) return;
|
|
259
|
+
const doc = container.ownerDocument;
|
|
260
|
+
// Already wired — don't double-bind.
|
|
261
|
+
if (container.querySelector(".mermaid-zoom-toolbar")) return;
|
|
262
|
+
const tpl = doc.getElementById
|
|
263
|
+
? doc.getElementById("mermaid-zoom-toolbar-template")
|
|
264
|
+
: null;
|
|
265
|
+
if (!tpl || !tpl.content) return;
|
|
266
|
+
const fragment = doc.createDocumentFragment
|
|
267
|
+
? doc.createDocumentFragment()
|
|
268
|
+
: null;
|
|
269
|
+
if (!fragment) return;
|
|
270
|
+
// Clone the template's children into a fresh document
|
|
271
|
+
// fragment, then attach the event listeners.
|
|
272
|
+
const node = tpl.content.cloneNode(true);
|
|
273
|
+
const toolbar = node.querySelector(".mermaid-zoom-toolbar");
|
|
274
|
+
if (!toolbar) return;
|
|
275
|
+
// Wire the three buttons.
|
|
276
|
+
const inBtn = toolbar.querySelector('[data-mermaid-zoom="in"]');
|
|
277
|
+
const outBtn = toolbar.querySelector('[data-mermaid-zoom="out"]');
|
|
278
|
+
const resetBtn = toolbar.querySelector('[data-mermaid-zoom="reset"]');
|
|
279
|
+
if (inBtn) inBtn.addEventListener("click", () => zoomIn(container));
|
|
280
|
+
if (outBtn) outBtn.addEventListener("click", () => zoomOut(container));
|
|
281
|
+
if (resetBtn) resetBtn.addEventListener("click", () => resetZoom(container));
|
|
282
|
+
// Insert at the top of the container (so the toolbar
|
|
283
|
+
// appears above the diagram).
|
|
284
|
+
if (container.firstChild) {
|
|
285
|
+
container.insertBefore(toolbar, container.firstChild);
|
|
286
|
+
} else {
|
|
287
|
+
container.appendChild(toolbar);
|
|
288
|
+
}
|
|
289
|
+
// Re-apply the current zoom so the "100%" label reflects
|
|
290
|
+
// the actual scale (in case the user re-mounted a diagram
|
|
291
|
+
// that already had a non-default zoom).
|
|
292
|
+
applyZoom(container, getZoom(container));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function applyZoom(container, zoom) {
|
|
296
|
+
const stage = ensureZoomStage(container);
|
|
297
|
+
if (!stage) return;
|
|
298
|
+
// Use the inline `style` so the transform survives any
|
|
299
|
+
// user-provided stylesheet overrides. We also set
|
|
300
|
+
// `transform-origin: top left` so the diagram does not
|
|
301
|
+
// recenter on every zoom step (Mermaid SVGs have their own
|
|
302
|
+
// viewBox, but the surrounding page would jump otherwise).
|
|
303
|
+
stage.style.transformOrigin = "top left";
|
|
304
|
+
stage.style.transform = `scale(${zoom})`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function setZoom(container, nextZoom) {
|
|
308
|
+
if (!container) return ZOOM_DEFAULT;
|
|
309
|
+
const from = getZoom(container);
|
|
310
|
+
const to = roundZoom(clampZoom(nextZoom));
|
|
311
|
+
if (Math.abs(to - from) < 1e-9) {
|
|
312
|
+
// No-op: zoom did not actually change (e.g. clamped at the
|
|
313
|
+
// limit). Skip the trace emission so the host does not
|
|
314
|
+
// see a flood of `mermaid.zoom.change` events at the
|
|
315
|
+
// bound.
|
|
316
|
+
return to;
|
|
317
|
+
}
|
|
318
|
+
zoomState.set(container, to);
|
|
319
|
+
applyZoom(container, to);
|
|
320
|
+
// v2.0.8: emit the canonical `mermaid.zoom.change` trace.
|
|
321
|
+
// public/ is vanilla JS (no @TracedAs decorator), so we
|
|
322
|
+
// log a console.debug line the host OTel / visual-verify
|
|
323
|
+
// scripts can grep for.
|
|
324
|
+
try {
|
|
325
|
+
if (typeof console !== "undefined" && typeof console.debug === "function") {
|
|
326
|
+
console.debug("[trace] mermaid.zoom.change", { from, to });
|
|
327
|
+
}
|
|
328
|
+
} catch (_) {
|
|
329
|
+
// ignore — tracing must never break the controller
|
|
330
|
+
}
|
|
331
|
+
return to;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function zoomIn(container, step) {
|
|
335
|
+
const s = typeof step === "number" && Number.isFinite(step) ? Math.abs(step) : DEFAULT_ZOOM_STEP;
|
|
336
|
+
return setZoom(container, getZoom(container) + s);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function zoomOut(container, step) {
|
|
340
|
+
const s = typeof step === "number" && Number.isFinite(step) ? Math.abs(step) : DEFAULT_ZOOM_STEP;
|
|
341
|
+
return setZoom(container, getZoom(container) - s);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function resetZoom(container) {
|
|
345
|
+
return setZoom(container, ZOOM_DEFAULT);
|
|
346
|
+
}
|
|
347
|
+
|
|
185
348
|
/**
|
|
186
349
|
* Render `source` into `container`. Returns a Promise that resolves when
|
|
187
350
|
* the container reflects the latest source (either an SVG or an error
|
|
@@ -274,6 +437,14 @@
|
|
|
274
437
|
setState(container, "rendered");
|
|
275
438
|
rememberRenderedSource(container, source);
|
|
276
439
|
if (onStateChange) onStateChange(container, "rendered");
|
|
440
|
+
// v2.0.8 (feat/mermaid-zoom): wrap the freshly-rendered
|
|
441
|
+
// SVG in a `.mermaid-zoom-stage` div so the zoom transform
|
|
442
|
+
// applies to it, then (optionally) attach the toolbar.
|
|
443
|
+
// The toolbar lookup is silent if the template is missing
|
|
444
|
+
// (e.g. in unit tests), so the controller still works in
|
|
445
|
+
// every environment.
|
|
446
|
+
ensureZoomStage(container);
|
|
447
|
+
attachZoomToolbar(container);
|
|
277
448
|
// Bind functions if mermaid provided any (e.g., for click handlers).
|
|
278
449
|
if (typeof result.bindFunctions === "function") {
|
|
279
450
|
try {
|
|
@@ -300,6 +471,15 @@
|
|
|
300
471
|
if (onStateChange) onStateChange(container, "error");
|
|
301
472
|
},
|
|
302
473
|
getState,
|
|
474
|
+
// v2.0.8 (feat/mermaid-zoom): zoom controls. Each method
|
|
475
|
+
// takes a `container` reference so the controller can be
|
|
476
|
+
// re-used across multiple diagrams on the same page. The
|
|
477
|
+
// zoom is applied to a lazy-created `.mermaid-zoom-stage`
|
|
478
|
+
// wrapper inside the container.
|
|
479
|
+
getZoom,
|
|
480
|
+
zoomIn,
|
|
481
|
+
zoomOut,
|
|
482
|
+
resetZoom,
|
|
303
483
|
destroy() {
|
|
304
484
|
controllerDestroyed = true;
|
|
305
485
|
// Mark every known container as destroyed so any in-flight render
|
package/public/style.css
CHANGED
|
@@ -384,6 +384,64 @@ table.toolcalls tr.tool-row.highlight:hover {
|
|
|
384
384
|
white-space: pre-wrap;
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
+
/* v2.0.8 (feat/mermaid-zoom): zoom toolbar + stage styles.
|
|
388
|
+
The toolbar is a small inline control set the user clicks to
|
|
389
|
+
scale the rendered diagram up or down. The .mermaid-zoom-stage
|
|
390
|
+
wrapper is what carries the CSS transform — the underlying
|
|
391
|
+
SVG keeps its own viewBox so the diagram does not reflow on
|
|
392
|
+
every zoom step. */
|
|
393
|
+
.mermaid-zoom-toolbar {
|
|
394
|
+
display: inline-flex;
|
|
395
|
+
align-items: center;
|
|
396
|
+
gap: 4px;
|
|
397
|
+
margin: 4px 0;
|
|
398
|
+
padding: 2px 6px;
|
|
399
|
+
background: var(--panel, #f8fafc);
|
|
400
|
+
border: 1px solid var(--border, #cbd5e1);
|
|
401
|
+
border-radius: 4px;
|
|
402
|
+
font-size: 12px;
|
|
403
|
+
user-select: none;
|
|
404
|
+
}
|
|
405
|
+
.mermaid-zoom-btn {
|
|
406
|
+
display: inline-flex;
|
|
407
|
+
align-items: center;
|
|
408
|
+
justify-content: center;
|
|
409
|
+
min-width: 28px;
|
|
410
|
+
height: 24px;
|
|
411
|
+
padding: 0 6px;
|
|
412
|
+
background: #fff;
|
|
413
|
+
border: 1px solid var(--border, #cbd5e1);
|
|
414
|
+
border-radius: 3px;
|
|
415
|
+
font-family: inherit;
|
|
416
|
+
font-size: 12px;
|
|
417
|
+
color: #0f172a;
|
|
418
|
+
cursor: pointer;
|
|
419
|
+
transition: background 0.1s ease-in-out;
|
|
420
|
+
}
|
|
421
|
+
.mermaid-zoom-btn:hover {
|
|
422
|
+
background: #e2e8f0;
|
|
423
|
+
}
|
|
424
|
+
.mermaid-zoom-btn:focus-visible {
|
|
425
|
+
outline: 2px solid #2563eb;
|
|
426
|
+
outline-offset: 1px;
|
|
427
|
+
}
|
|
428
|
+
.mermaid-zoom-btn:active {
|
|
429
|
+
background: #cbd5e1;
|
|
430
|
+
}
|
|
431
|
+
.mermaid-zoom-stage {
|
|
432
|
+
/* The wrapper that carries the transform. `transform-origin:
|
|
433
|
+
top left` ensures the diagram grows down-right rather than
|
|
434
|
+
re-centering (which would look jumpy). The transform itself
|
|
435
|
+
is set inline by `MermaidRenderer.applyZoom` so the JS
|
|
436
|
+
state is the single source of truth. */
|
|
437
|
+
display: inline-block;
|
|
438
|
+
transform-origin: top left;
|
|
439
|
+
transition: transform 0.12s ease-out;
|
|
440
|
+
/* Allow the zoomed content to overflow horizontally; the
|
|
441
|
+
surrounding container will get its own scroll bar if needed. */
|
|
442
|
+
max-width: 100%;
|
|
443
|
+
}
|
|
444
|
+
|
|
387
445
|
.hint {
|
|
388
446
|
margin-top: 8px;
|
|
389
447
|
font-size: 12px;
|
|
@@ -159,6 +159,64 @@
|
|
|
159
159
|
// Render
|
|
160
160
|
// -----------------------------------------------------------------------
|
|
161
161
|
|
|
162
|
+
/**
|
|
163
|
+
* v2.0.8 (fix/tool-call-loading-skeleton): remove the
|
|
164
|
+
* `.monaco-skeleton` element (and its `.monaco-skeleton-hint`
|
|
165
|
+
* child) from `container` so the animated loading block
|
|
166
|
+
* does NOT remain as a sibling of the mounted editor or
|
|
167
|
+
* fallback. Without this, the success branch
|
|
168
|
+
* (`monaco.editor.create(container, ...)`) leaves the
|
|
169
|
+
* skeleton in place because Monaco ADDS the editor widget
|
|
170
|
+
* to the container rather than replacing its contents. The
|
|
171
|
+
* fallback and error branches happen to clear the container
|
|
172
|
+
* via `innerHTML = "..."`, but calling this helper from
|
|
173
|
+
* every branch is the single source of truth — the
|
|
174
|
+
* `innerHTML = "..."` calls become redundant (and we drop
|
|
175
|
+
* them) so there is exactly one place that owns the
|
|
176
|
+
* "skeleton is gone now" invariant.
|
|
177
|
+
*
|
|
178
|
+
* Side effect: emits
|
|
179
|
+
* console.debug("[trace] tool-call.loading.hide", { filePath, branch })
|
|
180
|
+
* so the host OTel pipeline and the visual-verify Playwright
|
|
181
|
+
* script can observe the trace path. `branch` is one of
|
|
182
|
+
* "success" — Monaco editor is now mounted
|
|
183
|
+
* "fallback" — Monaco timed out, `<pre>` fallback rendered
|
|
184
|
+
* "error" — fetch / Monaco both failed
|
|
185
|
+
*
|
|
186
|
+
* The function is intentionally idempotent and side-effect-
|
|
187
|
+
* free aside from the trace log: calling it twice on the
|
|
188
|
+
* same container is a no-op the second time.
|
|
189
|
+
*
|
|
190
|
+
* @param {HTMLElement|null} container
|
|
191
|
+
* @param {"success"|"fallback"|"error"} branch
|
|
192
|
+
*/
|
|
193
|
+
function hideLoadingSkeleton(container, branch) {
|
|
194
|
+
if (!container || !container.querySelector) return;
|
|
195
|
+
const skeleton = container.querySelector(".monaco-skeleton");
|
|
196
|
+
if (skeleton && skeleton.parentNode) {
|
|
197
|
+
skeleton.parentNode.removeChild(skeleton);
|
|
198
|
+
}
|
|
199
|
+
// v2.0.3 also injected a `.monaco-skeleton-hint` text node —
|
|
200
|
+
// in the success branch this is the *only* visible leftover
|
|
201
|
+
// (the skeleton div itself is removed as part of the editor
|
|
202
|
+
// mount, but the hint text node is a separate element). Belt-
|
|
203
|
+
// and-braces: drop the hint too if it's still around.
|
|
204
|
+
const hint = container.querySelector(".monaco-skeleton-hint");
|
|
205
|
+
if (hint && hint.parentNode) {
|
|
206
|
+
hint.parentNode.removeChild(hint);
|
|
207
|
+
}
|
|
208
|
+
const filePath =
|
|
209
|
+
(container.getAttribute && container.getAttribute("data-file-path")) ||
|
|
210
|
+
null;
|
|
211
|
+
try {
|
|
212
|
+
if (typeof console !== "undefined" && typeof console.debug === "function") {
|
|
213
|
+
console.debug("[trace] tool-call.loading.hide", { filePath, branch });
|
|
214
|
+
}
|
|
215
|
+
} catch (_) {
|
|
216
|
+
// ignore — tracing must never break the controller
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
162
220
|
/**
|
|
163
221
|
* v2.0.4 (Task #2690): hydrate a single monaco container with the
|
|
164
222
|
* skeleton + load + fallback lifecycle. Used by BOTH the dedicated
|
|
@@ -206,16 +264,31 @@
|
|
|
206
264
|
'<div class="monaco-placeholder monaco-error">Monaco failed to load and the file is not accessible: <code>' +
|
|
207
265
|
escapeHtml(filePath) + '</code></div>';
|
|
208
266
|
}
|
|
267
|
+
// v2.0.8: explicitly drop the skeleton before the innerHTML
|
|
268
|
+
// swap completes — guarantees the trace fires even if
|
|
269
|
+
// someone later refactors the innerHTML to a different
|
|
270
|
+
// string. The redundant removal is cheap (no DOM match
|
|
271
|
+
// in the success branch → no-op).
|
|
272
|
+
hideLoadingSkeleton(monacoContainer, file ? "fallback" : "error");
|
|
209
273
|
return;
|
|
210
274
|
}
|
|
211
275
|
if (!file) {
|
|
212
276
|
monacoContainer.innerHTML = `<div class="monaco-placeholder monaco-error">File not accessible (sandboxed or missing): <code>${escapeHtml(filePath)}</code></div>`;
|
|
277
|
+
hideLoadingSkeleton(monacoContainer, "error");
|
|
213
278
|
return;
|
|
214
279
|
}
|
|
215
280
|
const language = monacoContainer.getAttribute("data-language") || "plaintext";
|
|
216
281
|
// v2.0.3: use the current theme (vs-dark for dark, vs for light).
|
|
217
282
|
// Bug E fix — the default 'vs' is white-on-grey and unreadable on
|
|
218
283
|
// dark pages.
|
|
284
|
+
// v2.0.8 (fix/tool-call-loading-skeleton): drop the
|
|
285
|
+
// skeleton BEFORE `monaco.editor.create` so the editor
|
|
286
|
+
// mounts into a clean container. Monaco's `create()` does
|
|
287
|
+
// NOT clear the container's children, so without this
|
|
288
|
+
// step the animated skeleton would stay as a sibling of
|
|
289
|
+
// the editor widget and the user would see "Loading
|
|
290
|
+
// /path/…" stuck on screen.
|
|
291
|
+
hideLoadingSkeleton(monacoContainer, "success");
|
|
219
292
|
const editor = monaco.editor.create(monacoContainer, {
|
|
220
293
|
value: file.content,
|
|
221
294
|
language,
|
|
@@ -245,6 +318,7 @@
|
|
|
245
318
|
}
|
|
246
319
|
} catch (err) {
|
|
247
320
|
monacoContainer.innerHTML = `<div class="monaco-placeholder monaco-error">Monaco failed to load: ${escapeHtml(String((err && err.message) || err))}</div>`;
|
|
321
|
+
hideLoadingSkeleton(monacoContainer, "error");
|
|
248
322
|
}
|
|
249
323
|
}
|
|
250
324
|
|