From 41ff53811975b897c4722eb3b44326612535e57b Mon Sep 17 00:00:00 2001 From: Mike Lyons Date: Wed, 4 Nov 2026 13:47:52 -0500 Subject: [PATCH 1/6] Have AI reviewers brief the user, not just critique the change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A reviewer's submit_review now takes a `briefing`: markdown written for the user rather than the author agent — what the change does and why, how it works (with ASCII diagrams), the critique with blockers kept apart from non-blocking notes, and a merge recommendation. It is stored on the submission and shown in the Review tab above the summary, but never delivered to the author, who wrote the change and only needs the summary and comments. The default reviewer and PR-review prompts now ask for that explanation instead of telling the agent to skip restating what the PR does. The review-request footer changed with it, so the previous wording is kept as a retired footer that transcripts on disk still strip cleanly. Co-Authored-By: Claude Opus 5.5 --- resources/mcp-bridge.js | 8 ++++++- resources/mcp-bridge.test.js | 8 ++++++- src/main/control-server.ts | 10 +++++++-- src/main/review-service.test.ts | 21 +++++++++++++++++++ src/main/review-service.ts | 4 ++++ src/renderer/components/ReviewSubmissions.tsx | 21 ++++++++++++++++++- src/shared/state/json-claude.test.ts | 7 +++++++ src/shared/state/json-claude.ts | 17 ++++++++++----- src/shared/state/reviews.ts | 4 ++++ src/shared/state/settings.ts | 12 ++++++++--- 10 files changed, 99 insertions(+), 13 deletions(-) diff --git a/resources/mcp-bridge.js b/resources/mcp-bridge.js index e647618e..9a8bb01b 100644 --- a/resources/mcp-bridge.js +++ b/resources/mcp-bridge.js @@ -462,7 +462,7 @@ const TOOLS = [ { name: 'submit_review', description: - "Finish a review of this worktree's branch: a verdict plus a summary, covering every comment of yours that is still open. Leave inline comments with add_review_comment first and put cross-cutting points in the summary. Call it once per review round.\n\nIf you were opened by `request_review`, the review goes back to the tab that asked for it — you do not need to work out who that is. Otherwise it goes to the most recently active Chat tab other than yours; pass `tab` (from list_tabs) to choose. Terminal tabs cannot receive reviews. Depending on the user's settings it is delivered right away or held as a draft in the Review tab for the user to edit and send; the result says which, and whether delivery failed.", + "Finish a review of this worktree's branch: a verdict, a summary, and a briefing, covering every comment of yours that is still open. Leave inline comments with add_review_comment first and put cross-cutting points in the summary. Call it once per review round.\n\nThe summary and comments go to the author agent. The briefing does not: it is shown to the user in the Review tab, above the summary, and is written for a human who has probably not read the change yet.\n\nIf you were opened by `request_review`, the review goes back to the tab that asked for it — you do not need to work out who that is. Otherwise it goes to the most recently active Chat tab other than yours; pass `tab` (from list_tabs) to choose. Terminal tabs cannot receive reviews. Depending on the user's settings it is delivered right away or held as a draft in the Review tab for the user to edit and send; the result says which, and whether delivery failed.", inputSchema: { type: 'object', properties: { @@ -476,6 +476,11 @@ const TOOLS = [ type: 'string', description: 'Overall assessment: what the change does well, what must change, and anything not tied to one line.' }, + briefing: { + type: 'string', + description: + "Markdown for the user, not the author: the case for this change, so they can understand it and decide whether to merge without reading the diff first. Cover what it does and why, how it works (an ASCII diagram in a code fence for any flow, state or data-shape change — the user learns visually), your critique ranked by severity with blockers clearly separated from non-blocking notes, and a closing recommendation. Optional, but leave it out only for a trivial change." + }, tab: { type: 'string', description: "Id of the author's Chat tab, from list_tabs. Optional." @@ -1109,6 +1114,7 @@ async function handleToolCall(name, args) { const r = await callControl('POST', '/reviews/submit', { verdict: args.verdict, summary: typeof args.summary === 'string' ? args.summary : '', + briefing: typeof args.briefing === 'string' && args.briefing.trim() ? args.briefing : undefined, tab: typeof args.tab === 'string' && args.tab.trim() ? args.tab.trim() : undefined }) const what = diff --git a/resources/mcp-bridge.test.js b/resources/mcp-bridge.test.js index d4864180..c12b9a41 100644 --- a/resources/mcp-bridge.test.js +++ b/resources/mcp-bridge.test.js @@ -546,11 +546,17 @@ describe('mcp-bridge review tools', () => { expect(draft.result.content[0].text).toMatch(/held as a draft.*tab author/) const sent = await callTool('submit_review', { verdict: 'approve', summary: 's' }) expect(sent.result.content[0].text).toMatch(/delivered to tab author \(woke it\)/) - const failed = await callTool('submit_review', { verdict: 'comment', summary: 's', tab: 'term' }) + const failed = await callTool('submit_review', { + verdict: 'comment', + summary: 's', + briefing: '## What it does', + tab: 'term' + }) expect(failed.result.content[0].text).toMatch(/NOT delivered: not supported yet/) expect(stub.captured.filter((c) => c.url === '/reviews/submit').at(-1).body).toEqual({ verdict: 'comment', summary: 's', + briefing: '## What it does', tab: 'term' }) }) diff --git a/src/main/control-server.ts b/src/main/control-server.ts index 51f1b08b..43d9e1d2 100644 --- a/src/main/control-server.ts +++ b/src/main/control-server.ts @@ -158,7 +158,7 @@ export interface ReviewQueries { submit: ( worktreePath: string, callerTabId: string, - input: { verdict: ReviewVerdict; summary: string; targetTabId?: string } + input: { verdict: ReviewVerdict; summary: string; briefing?: string; targetTabId?: string } ) => SubmitReviewResult | Promise /** Open a reviewer Chat tab for the caller and hand it the request. */ request: ( @@ -1046,8 +1046,14 @@ async function handleRequest( }) } const summary = typeof body.summary === 'string' ? body.summary : '' + const briefing = typeof body.briefing === 'string' ? body.briefing : undefined const tab = typeof body.tab === 'string' && body.tab.trim() ? body.tab.trim() : undefined - const result = await deps.reviews.submit(wt, terminalId, { verdict, summary, targetTabId: tab }) + const result = await deps.reviews.submit(wt, terminalId, { + verdict, + summary, + briefing, + targetTabId: tab + }) if ('error' in result) return sendJson(res, 400, { error: result.error }) const { submission, delivery } = result return sendJson(res, 200, { diff --git a/src/main/review-service.test.ts b/src/main/review-service.test.ts index 261b0048..36b376d9 100644 --- a/src/main/review-service.test.ts +++ b/src/main/review-service.test.ts @@ -298,6 +298,27 @@ describe('submitReview', () => { expect(store.getSnapshot().state.reviews.byPath[WT].submissions![0].state).toBe('sent') }) + it('keeps the briefing on the submission but out of the delivered message', () => { + const { store, deps, deliver } = setup([comment('mine')]) + const result = submitReview(deps, WT, author, { + verdict: 'approve', + summary: 'lgtm', + briefing: ' ## What it does\nAdds a cache. ', + holdAsDraft: false + }) + if ('error' in result) throw new Error(result.error) + expect(store.getSnapshot().state.reviews.byPath[WT].submissions![0].briefing).toBe( + '## What it does\nAdds a cache.' + ) + expect(deliver.mock.calls[0][2]).not.toContain('Adds a cache') + }) + + it('omits a blank briefing', () => { + const { store, deps } = setup([comment('mine')]) + submitReview(deps, WT, author, { verdict: 'comment', summary: 's', briefing: ' ', holdAsDraft: true }) + expect(store.getSnapshot().state.reviews.byPath[WT].submissions![0]).not.toHaveProperty('briefing') + }) + it('records a failed delivery with a readable reason', () => { const { store, deps } = setup([comment('mine')], () => ({ ok: false, diff --git a/src/main/review-service.ts b/src/main/review-service.ts index a0a6fad4..144fde5b 100644 --- a/src/main/review-service.ts +++ b/src/main/review-service.ts @@ -338,6 +338,8 @@ export function requestReview( export interface SubmitReviewInput { verdict: ReviewVerdict summary: string + /** Explanation for the user; shown in the Review tab, never delivered. */ + briefing?: string /** Explicit author tab. Must be a Chat tab in the worktree other than * the reviewer's. */ targetTabId?: string @@ -385,11 +387,13 @@ export function submitReview( const requester = reviewRequester(state, worktreePath, reviewerTabId) const targetTabId = input.targetTabId ?? requester ?? pickReviewTarget(tabs, reviewerTabId) ?? undefined + const briefing = input.briefing?.trim() const submission: ReviewSubmission = { id: randomUUID(), author, verdict: input.verdict, summary: input.summary.trim(), + ...(briefing ? { briefing } : {}), commentIds, createdAt: Date.now(), state: 'draft', diff --git a/src/renderer/components/ReviewSubmissions.tsx b/src/renderer/components/ReviewSubmissions.tsx index 224e5684..efcc3c8a 100644 --- a/src/renderer/components/ReviewSubmissions.tsx +++ b/src/renderer/components/ReviewSubmissions.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { Bot, ChevronDown, ChevronRight, MessageSquare, Pencil, Send, SquareTerminal, X } from 'lucide-react' +import { BookOpen, Bot, ChevronDown, ChevronRight, MessageSquare, Pencil, Send, SquareTerminal, X } from 'lucide-react' import type { ReviewSubmission, ReviewVerdict } from '../types' import { useBackend } from '../backend' import { useWorktreePanes } from '../store' @@ -276,6 +276,19 @@ export function SubmissionCard({ {submission.state === 'failed' && submission.error && (
{submission.error}
)} + {submission.briefing && ( +
+
+ + Briefing for you +
+ {fullSummary ? ( + + ) : ( + + )} +
+ )} {editing ? (
@@ -320,6 +333,12 @@ export function SubmissionCard({ ) : ( submission.summary && (
+ {submission.briefing && ( +
+ + Summary for the author +
+ )} {fullSummary ? ( ) : ( diff --git a/src/shared/state/json-claude.test.ts b/src/shared/state/json-claude.test.ts index e65bad81..a69b40ff 100644 --- a/src/shared/state/json-claude.test.ts +++ b/src/shared/state/json-claude.test.ts @@ -1654,6 +1654,13 @@ describe('automated message sentinel', () => { expect(wire.endsWith('\n')).toBe(true) }) + it('strips a retired footer from a transcript written by an older build', () => { + const old = + 'How to leave this review: `list_review_comments` first so you do not repeat a point already made, then `add_review_comment` per issue, on the line it belongs to. When you are done call `submit_review` once with a verdict and a summary — that is what sends the review back to the agent that asked, and nothing reaches them until you do.\n\nYou share this checkout with the author, so an edit from you would land in their working tree underneath them: make every point as a comment instead, however small the fix looks. You are in plan mode; there is no plan to present and no need to call ExitPlanMode — the review is the output.' + const wire = `\nplease review\n\n${old}\n` + expect(parseAutomatedMessage(wire)).toEqual({ source: 'review-request', body: 'please review' }) + }) + it('treats ordinary turns as not automated', () => { expect(parseAutomatedMessage('fix the build')).toBeNull() expect(parseAutomatedMessage('')).toBeNull() diff --git a/src/shared/state/json-claude.ts b/src/shared/state/json-claude.ts index d77190af..41824f24 100644 --- a/src/shared/state/json-claude.ts +++ b/src/shared/state/json-claude.ts @@ -122,7 +122,7 @@ const AUTOMATION_GUIDANCE: Partial> = // user's `reviewPrompt` setting, which arrives in the body above — two // sets of instructions would fight, and a custom prompt should win. 'review-request': - 'How to leave this review: `list_review_comments` first so you do not repeat a point already made, then `add_review_comment` per issue, on the line it belongs to. When you are done call `submit_review` once with a verdict and a summary — that is what sends the review back to the agent that asked, and nothing reaches them until you do.\n\nYou share this checkout with the author, so an edit from you would land in their working tree underneath them: make every point as a comment instead, however small the fix looks. You are in plan mode; there is no plan to present and no need to call ExitPlanMode — the review is the output.', + 'How to leave this review: `list_review_comments` first so you do not repeat a point already made, then `add_review_comment` per issue, on the line it belongs to. When you are done call `submit_review` once with a verdict, a summary and a briefing — that is what sends the review back to the agent that asked, and nothing reaches them until you do. The summary and comments go to that agent; the briefing goes to the user, in the Review tab, so write it for a human who has not read the diff.\n\nYou share this checkout with the author, so an edit from you would land in their working tree underneath them: make every point as a comment instead, however small the fix looks. You are in plan mode; there is no plan to present and no need to call ExitPlanMode — the review is the output.', review: 'Address this review. The id in [brackets] after each comment works with the ness-control MCP tools: call `reply_to_comment` to answer a comment or push back on it, and `resolve_comment` once you have fixed it, so the reviewer and the user can see where each one stands in the Review tab. A comment you disagree with deserves a reply rather than a silent change. If you do not have those tools, just address the feedback.', 'worktree-kickoff': @@ -135,6 +135,14 @@ const AUTOMATION_GUIDANCE: Partial> = 'The message above is the user\'s own kickoff prompt, typed by them. Ness created this worktree from it and guessed the branch name — before you start the work, call the `rename_worktree` tool from the ness-control MCP server once with a better `branchName` (kebab-case, e.g. `fix-login-redirect`) and a short Title Case `alias` for the sidebar (e.g. "Login Redirect"). One call, no need to ask first, then get on with the task. If you do not have that tool, skip this and carry on.' } +/** Earlier wordings of a footer. Transcripts on disk still carry them, and + * the card would show the stale footer if parse only knew the current one. */ +const RETIRED_AUTOMATION_GUIDANCE: Partial> = { + 'review-request': [ + 'How to leave this review: `list_review_comments` first so you do not repeat a point already made, then `add_review_comment` per issue, on the line it belongs to. When you are done call `submit_review` once with a verdict and a summary — that is what sends the review back to the agent that asked, and nothing reaches them until you do.\n\nYou share this checkout with the author, so an edit from you would land in their working tree underneath them: make every point as a comment instead, however small the fix looks. You are in plan mode; there is no plan to present and no need to call ExitPlanMode — the review is the output.' + ] +} + const AUTOMATION_TAG = 'ness-automated-message' // Sentinels written before the Ness rename are already sitting in users' // on-disk transcripts, so both spellings parse; only the new one is @@ -218,10 +226,9 @@ export function parseAutomatedMessage( let body = text.slice(open[0].length, text.length - close.length) // Optional so sentinels written before a source grew its guidance footer // still round-trip. - const footer = AUTOMATION_GUIDANCE[source] - if (footer && body.endsWith(`\n\n${footer}`)) { - body = body.slice(0, -(footer.length + 2)) - } + const footers = [AUTOMATION_GUIDANCE[source], ...(RETIRED_AUTOMATION_GUIDANCE[source] ?? [])] + const footer = footers.find((f) => f && body.endsWith(`\n\n${f}`)) + if (footer) body = body.slice(0, -(footer.length + 2)) return { source, body, ...(from ? { from } : {}) } } diff --git a/src/shared/state/reviews.ts b/src/shared/state/reviews.ts index b7a3739d..848d2353 100644 --- a/src/shared/state/reviews.ts +++ b/src/shared/state/reviews.ts @@ -58,6 +58,10 @@ export interface ReviewSubmission { author: ReviewAuthor verdict: ReviewVerdict summary: string + /** The reviewer's case to the user: what the change does, how it works, + * the critique and a recommendation. Shown in the Review tab only — the + * author agent wrote the change and gets `summary` and the comments. */ + briefing?: string /** Open comments by `author` at submit time. Only the ones still open * when the submission is sent go out with it. */ commentIds: string[] diff --git a/src/shared/state/settings.ts b/src/shared/state/settings.ts index e2a967a6..a0f6616f 100644 --- a/src/shared/state/settings.ts +++ b/src/shared/state/settings.ts @@ -244,7 +244,7 @@ export function nessieColorById(id: string): NessieColor { /** Default kickoff prompt for "Open PR as worktree". Editable globally in * Settings (`prReviewPrompt`) and per-creation in the New Worktree screen. */ export const DEFAULT_PR_REVIEW_PROMPT = - "Review this PR. Read the diff, then check for correctness issues, design problems, security concerns, and missing edge cases. Cite file paths and line numbers for anything you flag. Skip restating what the PR does — focus on what could go wrong or be improved." + "Review this PR. Read the diff, then check for correctness issues, design problems, security concerns, and missing edge cases. Cite file paths and line numbers for anything you flag.\n\nI usually haven't looked at the PR yet, so start by explaining it: what it does and why, and how it works, so I understand the change fundamentally. I'm a visual learner — use ASCII diagrams for flows, state and data-shape changes.\n\nThen give your critique, ranked by severity. I do want to merge this, so keep real blockers (bugs, security, data loss) separate from non-blocking notes, and finish with a recommendation." /** Primary wake-lock mode. The actual hold = (this mode wants it) OR (the * transient `preventSleepUntil` timer is still live). Single-select; the @@ -263,11 +263,17 @@ export type ReviewAgentChoice = 'inherit' | AgentKindSetting /** What a reviewer is told to do. The requester's own context is added * underneath, and the tool mechanics (how to comment, that submit_review * is what sends it) are appended by Ness and not editable here. */ -export const DEFAULT_REVIEW_PROMPT = `Review this branch the way a careful colleague would. +export const DEFAULT_REVIEW_PROMPT = `Review this branch the way a careful colleague would, and then make the case for it to the user. Read the diff first. Leave each concrete issue as an inline comment on the line it belongs to, and keep it specific: what is wrong, why it matters, and what to do instead. Prefer a few substantial points over a long list of nits. -Look for: correctness bugs and unhandled edge cases, error handling, anything that will surprise the next reader, missing test coverage for the behaviour that changed, and code that duplicates something the repo already has. Call out what is done well too, briefly. +Look for: correctness bugs and unhandled edge cases, design problems, security concerns, error handling, anything that will surprise the next reader, missing test coverage for the behaviour that changed, and code that duplicates something the repo already has. Call out what is done well too, briefly. + +The user has usually not looked at this change yet, so the briefing is as important as the comments. Write it so they understand the change fundamentally and can decide whether to merge it without reading the diff first: +- What it does and why — the problem, then the approach, in plain language. +- How it works — walk through the moving parts and how they connect. The user is a visual learner: include an ASCII diagram in a code fence for any flow, state machine, data shape or before/after that changed. +- Your critique — ranked by severity. Keep real blockers (bugs, data loss, security) clearly apart from non-blocking notes; the user wants to merge, so anything that is not mission critical is a note, not a blocker. +- Recommendation — merge, merge with follow-ups, or hold, and why in one or two sentences. Do not change any code — you share this checkout with the author.` From e00cc0ef16e3a3359b741a73464b45276a77038c Mon Sep 17 00:00:00 2001 From: Mike Lyons Date: Wed, 4 Nov 2026 14:26:29 -0500 Subject: [PATCH 2/6] Drop a malformed briefing when loading saved reviews Co-Authored-By: Claude Opus 5.5 --- src/main/build-initial-state.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/build-initial-state.ts b/src/main/build-initial-state.ts index a4769774..abe54bda 100644 --- a/src/main/build-initial-state.ts +++ b/src/main/build-initial-state.ts @@ -140,10 +140,10 @@ function sanitizeReviews(raw: unknown): ReviewsState['byPath'] { (s.verdict === 'approve' || s.verdict === 'changes-requested' || s.verdict === 'comment') ) .map((s: ReviewSubmission) => { - if (s.headSha === undefined || typeof s.headSha === 'string') return s - const { headSha: _drop, ...rest } = s - void _drop - return rest + const next = { ...s } + if (next.headSha !== undefined && typeof next.headSha !== 'string') delete next.headSha + if (next.briefing !== undefined && typeof next.briefing !== 'string') delete next.briefing + return next }) : [] const requests: ReviewRequest[] = Array.isArray(rawRequests) From 6d8eba899a572c4dd5e379e4ebce4c983e281860 Mon Sep 17 00:00:00 2001 From: Mike Lyons Date: Wed, 4 Nov 2026 16:02:16 -0500 Subject: [PATCH 3/6] Collapse the author-facing summary when a review has a briefing The summary is what the author agent receives; next to the briefing it read as a second, redundant verdict. It now sits behind a disclosure, and submit_review tells the reviewer to keep it a terse to-do list for the author rather than restate the briefing. Co-Authored-By: Claude Opus 5.5 --- resources/mcp-bridge.js | 3 ++- src/renderer/components/ReviewSubmissions.tsx | 25 ++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/resources/mcp-bridge.js b/resources/mcp-bridge.js index 9a8bb01b..495ee25f 100644 --- a/resources/mcp-bridge.js +++ b/resources/mcp-bridge.js @@ -474,7 +474,8 @@ const TOOLS = [ }, summary: { type: 'string', - description: 'Overall assessment: what the change does well, what must change, and anything not tied to one line.' + description: + 'For the author agent: a short, actionable list of what must change and anything not tied to one line. Do not repeat the briefing — the user reads that, the author reads this.' }, briefing: { type: 'string', diff --git a/src/renderer/components/ReviewSubmissions.tsx b/src/renderer/components/ReviewSubmissions.tsx index efcc3c8a..81bb6725 100644 --- a/src/renderer/components/ReviewSubmissions.tsx +++ b/src/renderer/components/ReviewSubmissions.tsx @@ -177,6 +177,7 @@ export function SubmissionCard({ const [summary, setSummary] = useState(submission.summary) const [verdict, setVerdict] = useState(submission.verdict) const [sending, setSending] = useState(false) + const [showAuthorSummary, setShowAuthorSummary] = useState(false) const reviewerTabId = submission.author.kind === 'agent' ? submission.author.tabId : undefined const target = submission.targetTabId ? (tabLabels.get(submission.targetTabId) ?? 'a closed tab') @@ -331,21 +332,33 @@ export function SubmissionCard({
) : ( - submission.summary && ( + submission.summary && + (submission.briefing ? (
- {submission.briefing && ( -
- - Summary for the author + + {showAuthorSummary && ( +
+
)} +
+ ) : ( +
{fullSummary ? ( ) : ( )}
- ) + )) )} {children}
From e1beb5db4fb62e0973d3306cf150f58299af329c Mon Sep 17 00:00:00 2001 From: Mike Lyons Date: Wed, 4 Nov 2026 16:39:14 -0500 Subject: [PATCH 4/6] Render mermaid diagrams in review briefings and Chat tabs A ```mermaid block in review markdown or a Chat message now draws as a diagram, themed from the Ness color variables and sized from the root font-size so it follows uiScale. Mermaid loads lazily on the first diagram, renders are serialized and cached by theme + source, and a parse error falls back to the code block with the error underneath. A hover toggle shows the source. In Chat, remarkMermaid only marks a block once its closing fence has arrived, so a diagram streaming in stays a code block instead of re-running mermaid on every token. It matches blocks by body rather than position because remarkInsight re-parses Insight cards. The reviewer prompts, submit_review's briefing description and the Ness system prompt now tell agents mermaid renders, keeping ASCII for tiny sketches. Co-Authored-By: Claude Opus 5.5 --- package-lock.json | 1335 ++++++++++++++++++-- package.json | 1 + resources/mcp-bridge.js | 2 +- src/main/persistence.ts | 2 + src/renderer/components/JsonModeChat.tsx | 8 +- src/renderer/components/MermaidDiagram.tsx | 273 ++++ src/renderer/components/ReviewMarkdown.tsx | 10 +- src/renderer/remark-mermaid.test.ts | 54 + src/renderer/remark-mermaid.ts | 57 + src/shared/state/settings.ts | 4 +- 10 files changed, 1609 insertions(+), 137 deletions(-) create mode 100644 src/renderer/components/MermaidDiagram.tsx create mode 100644 src/renderer/remark-mermaid.test.ts create mode 100644 src/renderer/remark-mermaid.ts diff --git a/package-lock.json b/package-lock.json index f3ba04d1..e4b52e9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "framer-motion": "^12.38.0", "highlight.js": "^11.11.1", "lucide-react": "^1.8.0", + "mermaid": "^11.17.2", "monaco-editor": "^0.55.1", "node-pty": "^1.1.0", "node-ssh": "^13.2.1", @@ -62,6 +63,19 @@ "vitest": "^4.1.4" } }, + "node_modules/@antfu/install-pkg": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-2.1.0.tgz", + "integrity": "sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.8.0", + "tinyexec": "^1.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@anthropic-ai/claude-code": { "version": "2.1.301", "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-2.1.301.tgz", @@ -455,6 +469,18 @@ "node": ">=6.9.0" } }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "license": "MIT" + }, + "node_modules/@chevrotain/types": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", + "license": "Apache-2.0" + }, "node_modules/@develar/schema-utils": { "version": "2.6.5", "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", @@ -1577,6 +1603,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.7.tgz", + "integrity": "sha512-JZHlwdID+dy+lTgbYC8NEC4zeugqeYsc6jewvzb4c58kHauJn+X7rNwQjxz5p2qSjqaEeQoLkCIQ9v/H4PK0/w==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^2.0.1", + "@iconify/types": "^2.0.0", + "import-meta-resolve": "^4.2.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1831,6 +1874,15 @@ "node": ">= 10.0.0" } }, + "node_modules/@mermaid-js/parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.2.1.tgz", + "integrity": "sha512-n12NohV3mrUyUL2o93IgG/ifeW9FTyeJn3zDxkhwa8MJ9Fxg3HQMlA3RiGmD/3UnJvheztkjjQAjA2T4LmUcpw==", + "license": "MIT", + "dependencies": { + "@chevrotain/types": "~11.1.2" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", @@ -3135,6 +3187,259 @@ "assertion-error": "^2.0.1" } }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.4.tgz", + "integrity": "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.12.tgz", + "integrity": "sha512-Qe/KWYhEiIIxGs7HrAAjMfShxKldx19SJtr5zu53f3afPsdZNz7HHtdTLXo/kqeiWNXVycI24kSnfzBYkTzpgw==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.9.tgz", + "integrity": "sha512-0sE1406XBYJGiqD3AusTl9ZqC//2mIXix51tbom25gDCA8ri4xnSZg28CaSE8Srl6FClqABUKfsn0qghgdepMA==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.13", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", @@ -3182,6 +3487,12 @@ "@types/node": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -3323,6 +3634,16 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "license": "MIT", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, "node_modules/@vitejs/plugin-react": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", @@ -4595,167 +4916,691 @@ "dev": true, "license": "ISC", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/cpu-features": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.19.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.1.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cytoscape": { + "version": "3.34.4", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.4.tgz", + "integrity": "sha512-msY99tMY8iWTg+sIPGfSqDcPNOt4xcj+v/K/7qUFaCUxxlqnMkUYLBjRUzn9W5CU/R2hrmtJa5+UtovrEM2ctg==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.2.tgz", + "integrity": "sha512-ZKb/mONPVKzaO5UlcWcwN8bS4ezvQaYu+bsou+XgNABdGC800kwH651ADTbbTxMPHW13UlvbQafdmJQDk1w7Mg==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.5", + "d3-axis": "^3.0.1", + "d3-brush": "^3.0.1", + "d3-chord": "^3.0.2", + "d3-color": "^3.1.1", + "d3-contour": "^4.0.3", + "d3-delaunay": "^6.0.6", + "d3-dispatch": "^3.0.2", + "d3-drag": "^3.0.1", + "d3-dsv": "^3.0.2", + "d3-ease": "^3.0.2", + "d3-fetch": "^3.0.2", + "d3-force": "^3.0.1", + "d3-format": "^3.1.3", + "d3-geo": "^3.1.2", + "d3-hierarchy": "^3.1.3", + "d3-interpolate": "^3.0.2", + "d3-path": "^3.1.1", + "d3-polygon": "^3.0.2", + "d3-quadtree": "^3.0.2", + "d3-random": "^3.0.2", + "d3-scale": "^4.0.3", + "d3-scale-chromatic": "^3.1.1", + "d3-selection": "^3.0.1", + "d3-shape": "^3.2.1", + "d3-time": "^3.1.1", + "d3-time-format": "^4.1.1", + "d3-timer": "^3.0.2", + "d3-transition": "^3.0.2", + "d3-zoom": "^3.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.5.tgz", + "integrity": "sha512-Th8qGXoBalmWsstpkuhmBuTsBQIFqYOJtjlBvNVvBfJ8E3uYusBUwUR8HU20U0qKKQkgCvKyR1YjiozWadk5QA==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.1.tgz", + "integrity": "sha512-E5QCXfaf/uJZEHHiqoWRq+bQEm2PzoVFv2rDC7CZ3wwI0MjfMW3w83J3ubobG4IJj3lShAI5eiPJhOu/8wHofA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.1.tgz", + "integrity": "sha512-L5Glqa/Gb7LU3ndIOMtA5mDzlJ0aXKp5ELP2X09ctWF23jO7+faIUVDbmaIi+D6k3EemXFWn1F3lkfguExoVhA==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.2.tgz", + "integrity": "sha512-cGmADo5M2PTi3ewJZ2lcE32eURd4JmHtQ5JwRIUcas5L1SXqufG9yPyjWp8fW0gRi7aZi9bgbZPFZO1eyZ+1CQ==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.1.tgz", + "integrity": "sha512-lfab8x0Bj0UO7SNsY8dZSbNIA6ipbkE3X0NG9nqjCu6CfCHZseuDnOSJY7hidKrjpP++i1M2qT+FlAdPKYRilA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.3.tgz", + "integrity": "sha512-5DYURc8KwXPOhSUBblU0P9rVcArsSPxPOxsfO0Eud0s28oU0sB2e80aRHRL9AZW3usT8ikFKyIHpHHLNWLdRVA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.6.tgz", + "integrity": "sha512-DMQIEvSJhZCB6pwedcXaAlQ+MeKU5LlM2XakKtc73b/VM+RBiI1SRXycOEOKg2jsKj4zZ9F0KGYG6nCGwwWu/g==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.2.tgz", + "integrity": "sha512-sAzH4Y4nsLo8eSqw5jKiZt+LU2wZmL/qS+OkxQGjhwazB/D7TY34R/QK1eshfGr7cZEKymKZ/Haem7m+lu98Sg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.1.tgz", + "integrity": "sha512-OWeoayCq8J69hUIjvL+Mb6Q6Oaj/fyHV5XaXByEUAG+JUjIfTGDnk3qGpcXrD3VmQ3BSV96fcbM6fqq7C0tbPA==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.2.tgz", + "integrity": "sha512-FUnXhX7I4/AXyRCZzwfZTDGSq8PGZ4oR0tiCBqNGOZMsDrQ+6sIWNQnBzcCGhzJKZ6uCvCezTwflUqI209RRiQ==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-B5znKqRmKROAN4oHWQ50W/qisVjA9GXh2WMsVKdFVp4DxWmcrOsweqgUOUVsv5th96kwaZlokU6SlV4J3gak1w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.2.tgz", + "integrity": "sha512-1wGF62bgjMRbF6mqbD+6rRWBL2hwWVYQjFO2UP3Wm1nIAvJZKaQxXM5caVY7athoCa8wIq9lCf7g44lPWFk50g==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.1.tgz", + "integrity": "sha512-kAaaHLFebBYwgi5cYBXAI8vKrQFMCD2MEswj2QuPKKbdU1wYLc8WXtS7O8F/b6POCIe3gJjY6hkDstHi1yMETQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.3.tgz", + "integrity": "sha512-+F+/49swqmGuQJmSKnVMIDH/QkEFV5li9QRj0xm/KhyAUBv099O5PrvpQFxBp24PYph17Mnvt9mjKQV0ii0nMA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.2.tgz", + "integrity": "sha512-5R8ZgDQorIxmauv7R6gpE/ndz+Z0qAZssigq0aXFdvPHdY41RqtW02jrRUQAL724NdPABYXo1+gQEGBBr7uKNg==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.3.tgz", + "integrity": "sha512-GRajN5ZcnEwYFWqqdzhG1qJ3+fDg6WWzk47TJg7ePY7yvYAgRfD2r1mUx+wviBOWuCwamCUzjwMSdato43jbGA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.2.tgz", + "integrity": "sha512-IRzGU62DtxhvsnSTWMQIdnoZ6gA+BCWwBon1EJI6SjoN595799b6z/QRBzXfFhE8iupUPp3042yBNo0STPqnzQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-jsbgV8zIT38ReB9O7X7gcLndTQRONSqNNd+oGIaTBdUug4r5jFmp0N6RY9R5GWbdUDucn0qoTGyfjapT4tDhcg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-WAUvPudGU1Dn4mDPfb6+JnnbeVEV53hG/BoT+bxts6AkZsOpTEiV1jHYX+2vtwCICNFG9Wxv8QS+eW1zE/Dwfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.2.tgz", + "integrity": "sha512-9Lu9mqJKRbIk0d6mKDjpvPURaPdwHVK5CYbcdd0/4vBgz4ldz/7TIOse3kNERsByGNfld58KEco507AztJWyTQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.2.tgz", + "integrity": "sha512-PxJi9LNV+7mulaYO77nd+oGurXGL1fhedXjHnB3pWLUcqDnQtF4VASoqnQGHgoXLByVdv5QJ/2kfwkGn5HTS8g==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.3.tgz", + "integrity": "sha512-66cWobCNzcMvlfoaDHCxuIv9TBUWG1BLOWdv9kHHFuCr1JdqAWPvTmsaEYDD5NRMyWZ5+7u0GqiCDfMXMqh3hg==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.1.tgz", + "integrity": "sha512-lA5gfN2yMvmChubmptnDiBfeGpHD7Yb1EbvCz0TZE0WHXhyR6W2allPuhKtzV5ILQRqeOdefF0qYtERReLUBDg==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-scale": "1 - 4" }, "engines": { "node": ">=12" } }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "license": "MIT", + "node_modules/d3-selection": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.1.tgz", + "integrity": "sha512-ODSf35vqCrlCfp7hrm0eZjvOosSXSYLjqvIlhMBV9q9VOx7EBJG6pc+uXBswEZNJgCGJ+qnKYcWutbZDIxe/Xg==", + "license": "ISC", "engines": { - "node": ">=0.8" + "node": ">=12" } }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dev": true, - "license": "MIT", + "node_modules/d3-shape": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.1.tgz", + "integrity": "sha512-dm2o+lbKdzg9tY7GEag/dB9hcbodLfwkE/DkvqbELJXaMz0yKzWWhZqhbI7CKubukA7HnRyz0avdtvi7EPxPvw==", + "license": "ISC", "dependencies": { - "mimic-response": "^1.0.0" + "d3-path": "^3.1.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=12" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", + "node_modules/d3-time": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.1.tgz", + "integrity": "sha512-Ze7rjrhMzdeulh0/hjcfFj7XLAHOfowtEMfsoG6DU866HPynQNW7Wfen6hzXcqKTv8XzxvF6Hzr5XD6bMHr2ug==", + "license": "ISC", "dependencies": { - "color-name": "~1.1.4" + "d3-array": "2 - 3" }, "engines": { - "node": ">=7.0.0" + "node": ">=12" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", + "node_modules/d3-time-format": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.1.tgz", + "integrity": "sha512-22XaqjyCjpqgda20erhsOjGmC3vvYwTNYniKAd45W5UWLDF43moeOuV9qYpRqobIlsnK0UhnKZjUUJm3VgORUA==", + "license": "ISC", "dependencies": { - "delayed-stream": "~1.0.0" + "d3-time": "1 - 3" }, "engines": { - "node": ">= 0.8" - } - }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" + "node": ">=12" } }, - "node_modules/compare-version": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", - "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", - "dev": true, - "license": "MIT", + "node_modules/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-1Il1aliroVSErrv5aulRBxAY+7+DnaoLIWGd1C6SsBTWDeobWiP/V69vwb8vT6abR1kKwaNe1196pmaQ1fK90A==", + "license": "ISC", "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/cpu-features": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", - "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", - "hasInstallScript": true, - "optional": true, + "node_modules/d3-transition": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.2.tgz", + "integrity": "sha512-RcjCsdFgyFoDB+QXLC5m60ZkTN0k9zH+ovoLlTdJnDS1AbfbcuowiM4pCKSTOclNibS4f4J4cbv2m7O7y8NTOQ==", + "license": "ISC", "dependencies": { - "buildcheck": "~0.0.6", - "nan": "^2.19.0" + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" }, "engines": { - "node": ">=10.0.0" + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" } }, - "node_modules/crc": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", - "dev": true, - "license": "MIT", - "optional": true, + "node_modules/d3-zoom": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.1.tgz", + "integrity": "sha512-kfjD3BDQu/FOatpZ6JdMSyIoDwTxIpI29ySTI2J5ZlDaH1WbuJGQJ2Q9oJkWTEZEW98j6idezoomSD3Cn2zwiw==", + "license": "ISC", "dependencies": { - "buffer": "^5.1.0" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, + "node_modules/dagre-d3-es": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" + "d3": "^7.9.0", + "lodash-es": "^4.17.21" } }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "node_modules/dayjs": { + "version": "1.11.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", "license": "MIT" }, "node_modules/debug": { @@ -4878,6 +5723,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -5558,6 +6412,18 @@ "node": ">= 0.4" } }, + "node_modules/es-toolkit": { + "version": "1.53.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.53.1.tgz", + "integrity": "sha512-VhMc6IXmexu8iYFfL2aaAww6IvJTdHlqBLruLoEUs33YD4IcP5DdZcLOybMsMmmLsiy1c8PsiLsrM0DdIuuRUA==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types", + "tests/browser-compat" + ] + }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -5721,6 +6587,15 @@ "dev": true, "license": "MIT" }, + "node_modules/fastdom": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastdom/-/fastdom-1.0.12.tgz", + "integrity": "sha512-LB+xjSTEbjHE1cWsxu+tN2Xqr1kpi+V9aADI7sVM5ZMaXyYGPHULQMzpJMYqOTULK/73pUkWVzzObFRBkPr+hg==", + "license": "MIT", + "dependencies": { + "strictdom": "^1.0.1" + } + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -6085,6 +6960,12 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6362,7 +7243,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -6392,6 +7272,16 @@ ], "license": "BSD-3-Clause" }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -6444,6 +7334,15 @@ "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", "license": "MIT" }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/ip-address": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", @@ -6703,6 +7602,31 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6713,6 +7637,17 @@ "json-buffer": "3.0.1" } }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, "node_modules/lazy-val": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", @@ -6987,6 +7922,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, "node_modules/lodash.escaperegexp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", @@ -7462,6 +8403,57 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mermaid": { + "version": "11.17.2", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.17.2.tgz", + "integrity": "sha512-V6K3C8EBdEsPFZXSKMJe6ppQOENxuHARr9GvHX4hh47lAbhMRD9qf4oEK7LoaRQxULMa80/qt5gHO73aCleBBg==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.1.2", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.2.1", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.34.0", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.21", + "dompurify": "^3.3.3", + "es-toolkit": "^1.45.1", + "fastdom": "1.0.12", + "katex": "^0.16.47", + "khroma": "^2.1.0", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" + } + }, + "node_modules/mermaid/node_modules/dompurify": { + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.17.tgz", + "integrity": "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/mermaid/node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", @@ -8868,6 +9860,12 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/package-manager-detector": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.9.0.tgz", + "integrity": "sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA==", + "license": "MIT" + }, "node_modules/parse-entities": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", @@ -8893,6 +9891,12 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -9023,6 +10027,22 @@ "node": ">=10.4.0" } }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, "node_modules/postcss": { "version": "8.5.9", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", @@ -9480,6 +10500,12 @@ "node": ">=8.0" } }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, "node_modules/rolldown": { "version": "1.0.0-rc.15", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", @@ -9521,6 +10547,24 @@ "dev": true, "license": "MIT" }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -9863,6 +10907,12 @@ "dev": true, "license": "MIT" }, + "node_modules/strictdom": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strictdom/-/strictdom-1.0.1.tgz", + "integrity": "sha512-cEmp9QeXXRmjj/rVp9oyiqcvyocWab/HaoN4+bwFeZ7QzykJD6L3yD4v12K1x0tHpqRqVpJevN3gW7kyM39Bqg==", + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -9963,6 +11013,12 @@ "inline-style-parser": "0.2.7" } }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, "node_modules/sumchecker": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", @@ -10153,10 +11209,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", - "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", - "dev": true, + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "license": "MIT", "engines": { "node": ">=18" @@ -10239,6 +11294,15 @@ "utf8-byte-length": "^1.0.1" } }, + "node_modules/ts-dedent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", + "integrity": "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -10478,6 +11542,19 @@ "dev": true, "license": "MIT" }, + "node_modules/uuid": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.3.tgz", + "integrity": "sha512-Sc5u1VVln93y1Xnz4R+CunOZc7R9LUcTmhj7Rc7Eg3HVcf7rVn2FOoa812fe0W8p3jD7xeoGSYbfqCGGH2B6xw==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/verror": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", diff --git a/package.json b/package.json index 3c842e8b..1823bfd9 100644 --- a/package.json +++ b/package.json @@ -170,6 +170,7 @@ "framer-motion": "^12.38.0", "highlight.js": "^11.11.1", "lucide-react": "^1.8.0", + "mermaid": "^11.17.2", "monaco-editor": "^0.55.1", "node-pty": "^1.1.0", "node-ssh": "^13.2.1", diff --git a/resources/mcp-bridge.js b/resources/mcp-bridge.js index 495ee25f..872a1b6a 100644 --- a/resources/mcp-bridge.js +++ b/resources/mcp-bridge.js @@ -480,7 +480,7 @@ const TOOLS = [ briefing: { type: 'string', description: - "Markdown for the user, not the author: the case for this change, so they can understand it and decide whether to merge without reading the diff first. Cover what it does and why, how it works (an ASCII diagram in a code fence for any flow, state or data-shape change — the user learns visually), your critique ranked by severity with blockers clearly separated from non-blocking notes, and a closing recommendation. Optional, but leave it out only for a trivial change." + "Markdown for the user, not the author: the case for this change, so they can understand it and decide whether to merge without reading the diff first. Cover what it does and why, how it works (a diagram for any flow, state or data-shape change — the user learns visually; a ```mermaid code block renders as a real diagram), your critique ranked by severity with blockers clearly separated from non-blocking notes, and a closing recommendation. Optional, but leave it out only for a trivial change." }, tab: { type: 'string', diff --git a/src/main/persistence.ts b/src/main/persistence.ts index b29363e0..d9247463 100644 --- a/src/main/persistence.ts +++ b/src/main/persistence.ts @@ -481,6 +481,8 @@ Prefer these over blind curl/fetch — or shelling out to \`open \`, which Show your work with screenshots — this applies in Chat tabs, whose transcript is rendered markdown; a terminal tab is an xterm and will show the markdown as literal text. Every screenshot_tab result comes back with the path it was saved to, and embedding an absolute image path as markdown (\`![caption](/path/to/shot.png)\`) renders it inline — so when you've changed something visual, end your reply with the screenshot that proves it rather than only describing it. This works for any image file on this machine, not just screenshot_tab captures. Append a \`#w=\` fragment to set the displayed width: \`![caption](path#w=500)\` is 500px, \`#w=50%\` fills half the column, \`#w=400x300\` sets both dimensions, and no fragment means a default size. Use the fragment rather than a \`|\` size spec in the alt text — inside a markdown table a pipe is a column delimiter, so the cell splits and the embed renders as broken literal text. Be selective: embed the one or two shots that show the result, not every frame you took while finding your way around. +Chat tabs also draw diagrams: a fenced \`\`\`mermaid code block (flowchart, sequenceDiagram, stateDiagram-v2, classDiagram, erDiagram, …) renders as a real diagram, with a toggle to its source. Reach for one when a flow, a sequence of calls, a state machine or an architecture is easier seen than read; a small ASCII sketch is still fine for something tiny. Quote node labels that contain punctuation (\`A["parse()"]\`) — a syntax error falls back to showing the source. In a terminal tab it is just text. + Ness also exposes shell tabs for long-running processes — anything that wouldn't naturally exit within a few seconds (dev servers, watchers, \`tail -f\`, REPL-style tools, long builds). Drive them via the ness-control shell tools (scoped to this worktree only): - create_shell: spawn a shell tab, optionally with a command to run (\`zsh -ilc \`). Returns an id — keep it for later reads. diff --git a/src/renderer/components/JsonModeChat.tsx b/src/renderer/components/JsonModeChat.tsx index dd2e0fed..8285a5f2 100644 --- a/src/renderer/components/JsonModeChat.tsx +++ b/src/renderer/components/JsonModeChat.tsx @@ -14,7 +14,9 @@ import ReactMarkdown, { defaultUrlTransform, type Components } from 'react-markd import { rehypeHighlightShared } from '../rehype-highlight-shared' import remarkGfm from 'remark-gfm' import { remarkInsight } from '../remark-insight' +import { remarkMermaid } from '../remark-mermaid' import { InsightCard } from './InsightCard' +import { MarkdownPre } from './MermaidDiagram' import { AlertOctagon, AlertTriangle, @@ -95,7 +97,8 @@ import { useFindController } from './JsonModeChatFind' -const REMARK_PLUGINS = [remarkGfm, remarkInsight] +// remarkMermaid must run after remarkInsight, which rebuilds the tree. +const REMARK_PLUGINS = [remarkGfm, remarkInsight, remarkMermaid] const REHYPE_PLUGINS = [rehypeHighlightShared, rehypeColorHex] /** Markdown `![alt](…)` in a message. An agent that just took a browser @@ -160,7 +163,8 @@ function MarkdownImage({ // element. const MARKDOWN_COMPONENTS = { 'insight-card': InsightCard, - img: MarkdownImage + img: MarkdownImage, + pre: MarkdownPre } as unknown as Components // react-markdown's default transform blanks any src whose protocol isn't diff --git a/src/renderer/components/MermaidDiagram.tsx b/src/renderer/components/MermaidDiagram.tsx new file mode 100644 index 00000000..cf15e672 --- /dev/null +++ b/src/renderer/components/MermaidDiagram.tsx @@ -0,0 +1,273 @@ +import { memo, useEffect, useState, useSyncExternalStore, type ReactNode } from 'react' +import { Code2, Workflow } from 'lucide-react' + +// Mermaid is ~1MB, so it loads on the first diagram rather than at boot. +// Renders are serialized because mermaid.initialize is global config and +// mermaid.render measures text in a shared scratch element; two renders +// interleaving across an await can draw with each other's theme. + +type MermaidApi = typeof import('mermaid').default + +let mermaidPromise: Promise | null = null +function loadMermaid(): Promise { + mermaidPromise ??= import('mermaid').then((m) => m.default) + return mermaidPromise +} + +const THEME_VARS = [ + 'app', + 'panel', + 'panel-raised', + 'surface', + 'border', + 'border-strong', + 'fg', + 'fg-bright', + 'muted', + 'accent' +] as const + +type ThemeColors = Record<(typeof THEME_VARS)[number], string> + +function readThemeColors(): ThemeColors { + const style = getComputedStyle(document.documentElement) + return Object.fromEntries( + THEME_VARS.map((v) => [v, style.getPropertyValue(`--color-${v}`).trim()]) + ) as ThemeColors +} + +// Theme switches rewrite [data-theme] and, for custom themes and uiScale, +// inline styles on . One observer for every diagram on screen. +let themeKey = '' +const themeListeners = new Set<() => void>() +let themeObserver: MutationObserver | null = null +function subscribeTheme(cb: () => void): () => void { + themeListeners.add(cb) + if (!themeObserver) { + themeObserver = new MutationObserver(() => { + const next = currentThemeKey() + if (next === themeKey) return + themeKey = next + for (const l of themeListeners) l() + }) + themeObserver.observe(document.documentElement, { + attributes: true, + attributeFilter: ['data-theme', 'style', 'class'] + }) + } + return () => { + themeListeners.delete(cb) + if (themeListeners.size === 0) { + themeObserver?.disconnect() + themeObserver = null + } + } +} +function getThemeKey(): string { + if (!themeKey) themeKey = currentThemeKey() + return themeKey +} + +function currentThemeKey(): string { + return `${JSON.stringify(readThemeColors())}|${getComputedStyle(document.documentElement).fontSize}` +} + +function isDark(hex: string): boolean { + const m = /^#([0-9a-f]{6})/i.exec(hex) + if (!m) return true + const n = parseInt(m[1], 16) + const lum = 0.299 * ((n >> 16) & 255) + 0.587 * ((n >> 8) & 255) + 0.114 * (n & 255) + return lum < 128 +} + +const MIN_SCALE = 0.65 + +function mermaidConfig(c: ThemeColors): Parameters[0] { + // Mermaid sizes text in px. Derive it from the root font-size so + // diagrams follow uiScale like the rest of the UI (13px at the default). + const fontPx = Math.round(parseFloat(getComputedStyle(document.documentElement).fontSize) * 0.8125) + return { + startOnLoad: false, + securityLevel: 'strict', + suppressErrorRendering: true, + theme: 'base', + fontFamily: getComputedStyle(document.body).fontFamily, + sequence: { actorFontSize: fontPx, messageFontSize: fontPx, noteFontSize: fontPx }, + themeVariables: { + darkMode: isDark(c.app), + background: c.panel, + fontSize: `${fontPx}px`, + primaryColor: c.surface, + primaryTextColor: c['fg-bright'], + primaryBorderColor: c['border-strong'], + secondaryColor: c['panel-raised'], + tertiaryColor: c.panel, + lineColor: c.muted, + textColor: c.fg, + mainBkg: c.surface, + nodeBorder: c['border-strong'], + clusterBkg: c['panel-raised'], + clusterBorder: c.border, + edgeLabelBackground: c.panel, + actorBkg: c.surface, + actorBorder: c['border-strong'], + actorTextColor: c['fg-bright'], + signalColor: c.muted, + signalTextColor: c.fg, + labelBoxBkgColor: c.surface, + noteBkgColor: c['panel-raised'], + noteTextColor: c.fg, + noteBorderColor: c.accent + } + } +} + +type RenderResult = { svg: string } | { error: string } + +// Keyed by theme + source so chat rows that unmount and remount while +// scrolling show their diagram straight away instead of re-rendering. +const MAX_CACHE = 64 +const cache = new Map() +let queue: Promise = Promise.resolve() +let seq = 0 + +function renderMermaid(source: string, key: string): Promise { + const run = async (): Promise => { + const hit = cache.get(key) + if (hit) return hit + let result: RenderResult + try { + const mermaid = await loadMermaid() + mermaid.initialize(mermaidConfig(readThemeColors())) + const { svg } = await mermaid.render(`ness-mermaid-${++seq}`, source) + // Mermaid emits width="100%" capped at the natural width, so a wide + // diagram shrinks until its labels are unreadable. Floor the shrink + // and let the container scroll past that. + const natural = Number(/max-width:\s*([\d.]+)px/.exec(svg)?.[1]) + result = { + svg: natural + ? svg.replace(/max-width:\s*[\d.]+px;?/, `max-width: ${natural}px; min-width: ${Math.round(natural * MIN_SCALE)}px;`) + : svg + } + } catch (err) { + result = { error: err instanceof Error ? err.message : String(err) } + } + cache.set(key, result) + if (cache.size > MAX_CACHE) cache.delete(cache.keys().next().value as string) + return result + } + const p = queue.then(run, run) + queue = p + return p +} + +/** A ```mermaid block drawn as a diagram, with a toggle back to its + * source. Shows the source while rendering and when it fails to parse, + * so a typo costs the picture, never the content. `fallback` is the + * normal code block for that source. */ +export const MermaidDiagram = memo(function MermaidDiagram({ + source, + fallback +}: { + source: string + fallback: ReactNode +}): JSX.Element { + const theme = useSyncExternalStore(subscribeTheme, getThemeKey) + const key = `${theme}\n${source}` + const [result, setResult] = useState(() => cache.get(key) ?? null) + const [showSource, setShowSource] = useState(false) + + useEffect(() => { + const hit = cache.get(key) + if (hit) return setResult(hit) + let cancelled = false + void renderMermaid(source, key).then((r) => { + if (!cancelled) setResult(r) + }) + return () => { + cancelled = true + } + }, [key, source]) + + if (!result || showSource || 'error' in result) { + return ( +
+ {fallback} + {result && 'error' in result && ( +
Diagram didn’t render: {result.error.split('\n')[0]}
+ )} + {result && 'svg' in result && ( + setShowSource(false)} label="Show diagram"> + + + )} +
+ ) + } + + return ( +
+
{ + if ((e.target as Element).closest('a')) e.preventDefault() + }} + dangerouslySetInnerHTML={{ __html: result.svg }} + /> + setShowSource(true)} label="Show source"> + + +
+ ) +}) + +function ToggleButton({ + onClick, + label, + children +}: { + onClick: () => void + label: string + children: ReactNode +}): JSX.Element { + return ( + + ) +} + +type HastNode = { + type: string + tagName?: string + value?: string + properties?: Record + children?: HastNode[] +} + +function hastText(node: HastNode): string { + if (node.type === 'text') return node.value ?? '' + return (node.children ?? []).map(hastText).join('') +} + +/** react-markdown `pre` override: a finished ```mermaid block (marked by + * remarkMermaid) becomes a diagram, everything else renders as usual. */ +export function MarkdownPre({ + node, + ...props +}: React.HTMLAttributes & { node?: HastNode }): JSX.Element { + const code = node?.children?.find((c) => c.tagName === 'code') + const pre =
+  if (code?.properties?.dataMermaid !== 'closed') return pre
+  return 
+}
diff --git a/src/renderer/components/ReviewMarkdown.tsx b/src/renderer/components/ReviewMarkdown.tsx
index a69f2355..8a2b3c60 100644
--- a/src/renderer/components/ReviewMarkdown.tsx
+++ b/src/renderer/components/ReviewMarkdown.tsx
@@ -2,12 +2,15 @@ import { memo, useLayoutEffect, useRef, useState } from 'react'
 import ReactMarkdown, { type Components } from 'react-markdown'
 import remarkGfm from 'remark-gfm'
 import { rehypeHighlightShared } from '../rehype-highlight-shared'
+import { remarkMermaid } from '../remark-mermaid'
+import { MarkdownPre } from './MermaidDiagram'
 
 /** GFM for review comments, replies, summaries and request context. Same
  *  `.markdown` typography as chat. No rehype-raw, so raw HTML stays inert,
- *  and the default urlTransform still blanks `javascript:` and friends. */
+ *  and the default urlTransform still blanks `javascript:` and friends.
+ *  ```mermaid blocks render as diagrams. */
 
-const REMARK_PLUGINS = [remarkGfm]
+const REMARK_PLUGINS = [remarkGfm, remarkMermaid]
 const REHYPE_PLUGINS = [rehypeHighlightShared]
 
 // Rows that hold markdown are themselves clickable (jump to the diff), so
@@ -16,7 +19,8 @@ const REHYPE_PLUGINS = [rehypeHighlightShared]
 // browser and silently drops anything else — so relative file links
 // render as text rather than as links that do nothing.
 const COMPONENTS: Components = {
-  a: ({ href, children }) =>
+  pre: MarkdownPre,
+  a:({ href, children }) =>
     href && /^(https?:|mailto:)/i.test(href) ? (
        e.stopPropagation()}>
         {children}
diff --git a/src/renderer/remark-mermaid.test.ts b/src/renderer/remark-mermaid.test.ts
new file mode 100644
index 00000000..46251eff
--- /dev/null
+++ b/src/renderer/remark-mermaid.test.ts
@@ -0,0 +1,54 @@
+import { describe, it, expect } from 'vitest'
+import { unified } from 'unified'
+import remarkParse from 'remark-parse'
+import remarkGfm from 'remark-gfm'
+import type { Code, Root } from 'mdast'
+import { closedMermaidBodies, remarkMermaid } from './remark-mermaid'
+import { remarkInsight } from './remark-insight'
+
+function codeNodes(md: string, plugins: Array<() => (tree: Root, file?: { value?: unknown }) => void>): Code[] {
+  let processor = unified().use(remarkParse).use(remarkGfm)
+  for (const p of plugins) processor = processor.use(p)
+  const tree = processor.runSync(processor.parse(md), md) as Root
+  const out: Code[] = []
+  const walk = (n: { type: string; children?: unknown[] }): void => {
+    if (n.type === 'code') out.push(n as Code)
+    for (const c of (n.children ?? []) as Array<{ type: string; children?: unknown[] }>) walk(c)
+  }
+  walk(tree)
+  return out
+}
+
+const closed = (c: Code): boolean =>
+  (c.data?.hProperties as Record | undefined)?.dataMermaid === 'closed'
+
+describe('closedMermaidBodies', () => {
+  it('collects only fences that have closed', () => {
+    const md = '```mermaid\ngraph TD\n  A-->B\n```\n\n```mermaid\ngraph LR\n  X-->'
+    expect([...closedMermaidBodies(md)]).toEqual(['graph TD\n  A-->B'])
+  })
+
+  it('needs the same fence character and at least the same length', () => {
+    expect(closedMermaidBodies('````mermaid\na\n```\n').size).toBe(0)
+    expect(closedMermaidBodies('~~~mermaid\na\n```\n').size).toBe(0)
+    expect(closedMermaidBodies('~~~mermaid\na\n~~~~\n').size).toBe(1)
+  })
+})
+
+describe('remarkMermaid', () => {
+  it('marks a finished mermaid block and leaves one still streaming', () => {
+    const done = codeNodes('```mermaid\ngraph TD\n  A-->B\n```\n', [remarkMermaid])
+    expect(done.map(closed)).toEqual([true])
+    const streaming = codeNodes('```mermaid\ngraph TD\n  A-->B\n', [remarkMermaid])
+    expect(streaming.map(closed)).toEqual([false])
+  })
+
+  it('ignores other languages', () => {
+    expect(codeNodes('```ts\nconst a = 1\n```\n', [remarkMermaid]).map(closed)).toEqual([false])
+  })
+
+  it('still finds a block inside an Insight card, which is re-parsed', () => {
+    const md = '★ Insight ─────\n```mermaid\ngraph TD\n  A-->B\n```\n─────\n'
+    expect(codeNodes(md, [remarkInsight, remarkMermaid]).map(closed)).toEqual([true])
+  })
+})
diff --git a/src/renderer/remark-mermaid.ts b/src/renderer/remark-mermaid.ts
new file mode 100644
index 00000000..e72967e3
--- /dev/null
+++ b/src/renderer/remark-mermaid.ts
@@ -0,0 +1,57 @@
+import type { Root, Code, Parent } from 'mdast'
+
+// Marks ```mermaid blocks whose closing fence has arrived, so the renderer
+// draws a diagram only for finished source. A block still streaming in is
+// left as a code block: rendering a half-written diagram on every token
+// would fail to parse most of the time and re-run mermaid per token.
+//
+// Closedness is read from the raw source rather than node positions,
+// because remarkInsight re-parses Insight bodies and their positions are
+// relative to the segment, not the message. A block counts as closed when
+// some closed mermaid fence in the source has the same body.
+
+const OPEN_RE = /^ {0,3}(`{3,}|~{3,})\s*mermaid\s*$/i
+const FENCE_RE = /^ {0,3}(`{3,}|~{3,})\s*$/
+
+export function closedMermaidBodies(source: string): Set {
+  const closed = new Set()
+  const lines = source.split('\n')
+  for (let i = 0; i < lines.length; i++) {
+    const open = OPEN_RE.exec(lines[i])?.[1]
+    if (!open) continue
+    for (let j = i + 1; j < lines.length; j++) {
+      const fence = FENCE_RE.exec(lines[j])?.[1]
+      if (fence && fence[0] === open[0] && fence.length >= open.length) {
+        closed.add(lines.slice(i + 1, j).join('\n').trim())
+        i = j
+        break
+      }
+    }
+  }
+  return closed
+}
+
+function walk(node: Root | Parent, closed: Set): void {
+  for (const child of node.children) {
+    if (child.type === 'code') {
+      const code = child as Code
+      if (code.lang?.toLowerCase() === 'mermaid' && closed.has(code.value.trim())) {
+        code.data = {
+          ...code.data,
+          hProperties: { ...code.data?.hProperties, dataMermaid: 'closed' }
+        }
+      }
+    } else if ('children' in child) {
+      walk(child as Parent, closed)
+    }
+  }
+}
+
+export function remarkMermaid() {
+  return (tree: Root, file?: { value?: unknown }) => {
+    const source = typeof file?.value === 'string' ? file.value : null
+    if (source === null || !/mermaid/i.test(source)) return
+    const closed = closedMermaidBodies(source)
+    if (closed.size > 0) walk(tree, closed)
+  }
+}
diff --git a/src/shared/state/settings.ts b/src/shared/state/settings.ts
index a0f6616f..fae0a18f 100644
--- a/src/shared/state/settings.ts
+++ b/src/shared/state/settings.ts
@@ -244,7 +244,7 @@ export function nessieColorById(id: string): NessieColor {
 /** Default kickoff prompt for "Open PR as worktree". Editable globally in
  *  Settings (`prReviewPrompt`) and per-creation in the New Worktree screen. */
 export const DEFAULT_PR_REVIEW_PROMPT =
-  "Review this PR. Read the diff, then check for correctness issues, design problems, security concerns, and missing edge cases. Cite file paths and line numbers for anything you flag.\n\nI usually haven't looked at the PR yet, so start by explaining it: what it does and why, and how it works, so I understand the change fundamentally. I'm a visual learner — use ASCII diagrams for flows, state and data-shape changes.\n\nThen give your critique, ranked by severity. I do want to merge this, so keep real blockers (bugs, security, data loss) separate from non-blocking notes, and finish with a recommendation."
+  "Review this PR. Read the diff, then check for correctness issues, design problems, security concerns, and missing edge cases. Cite file paths and line numbers for anything you flag.\n\nI usually haven't looked at the PR yet, so start by explaining it: what it does and why, and how it works, so I understand the change fundamentally. I'm a visual learner — use diagrams for flows, state and data-shape changes. A ```mermaid code block renders as a real diagram here.\n\nThen give your critique, ranked by severity. I do want to merge this, so keep real blockers (bugs, security, data loss) separate from non-blocking notes, and finish with a recommendation."
 
 /** Primary wake-lock mode. The actual hold = (this mode wants it) OR (the
  *  transient `preventSleepUntil` timer is still live). Single-select; the
@@ -271,7 +271,7 @@ Look for: correctness bugs and unhandled edge cases, design problems, security c
 
 The user has usually not looked at this change yet, so the briefing is as important as the comments. Write it so they understand the change fundamentally and can decide whether to merge it without reading the diff first:
 - What it does and why — the problem, then the approach, in plain language.
-- How it works — walk through the moving parts and how they connect. The user is a visual learner: include an ASCII diagram in a code fence for any flow, state machine, data shape or before/after that changed.
+- How it works — walk through the moving parts and how they connect. The user is a visual learner: include a diagram for any flow, state machine, sequence of calls or before/after that changed. A \`\`\`mermaid code block renders as a real diagram in Ness (flowchart, sequenceDiagram, stateDiagram-v2, classDiagram, erDiagram); quote node labels that contain punctuation. A small ASCII sketch is fine for something tiny.
 - Your critique — ranked by severity. Keep real blockers (bugs, data loss, security) clearly apart from non-blocking notes; the user wants to merge, so anything that is not mission critical is a note, not a blocker.
 - Recommendation — merge, merge with follow-ups, or hold, and why in one or two sentences.
 

From 1bef1a59a766ceaaf131d6739024c868e0d4da3e Mon Sep 17 00:00:00 2001
From: Mike Lyons 
Date: Wed, 4 Nov 2026 17:18:47 -0500
Subject: [PATCH 5/6] Add theme-aware color classes for mermaid diagrams

Agents can't see the user's theme, so hex colors they pick often vanish
against it. A node marked :::added, :::changed, :::removed or :::focus
is now colored from the success/warning/danger/accent theme variables
(removed is also struck through), in flowcharts, state and class
diagrams. The rules use !important because mermaid's own style block is
scoped by the svg id. The system prompt and reviewer prompts point agents
at these classes instead of raw colors.

Co-Authored-By: Claude Opus 5.5 
---
 resources/mcp-bridge.js      |  2 +-
 src/main/persistence.ts      |  2 +-
 src/renderer/styles.css      | 17 +++++++++++++++++
 src/shared/state/settings.ts |  2 +-
 4 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/resources/mcp-bridge.js b/resources/mcp-bridge.js
index 872a1b6a..c1258f03 100644
--- a/resources/mcp-bridge.js
+++ b/resources/mcp-bridge.js
@@ -480,7 +480,7 @@ const TOOLS = [
         briefing: {
           type: 'string',
           description:
-            "Markdown for the user, not the author: the case for this change, so they can understand it and decide whether to merge without reading the diff first. Cover what it does and why, how it works (a diagram for any flow, state or data-shape change — the user learns visually; a ```mermaid code block renders as a real diagram), your critique ranked by severity with blockers clearly separated from non-blocking notes, and a closing recommendation. Optional, but leave it out only for a trivial change."
+            "Markdown for the user, not the author: the case for this change, so they can understand it and decide whether to merge without reading the diff first. Cover what it does and why, how it works (a diagram for any flow, state or data-shape change — the user learns visually; a ```mermaid code block renders as a real diagram; mark the nodes this change touches with :::added, :::changed or :::removed, which follow the user's theme), your critique ranked by severity with blockers clearly separated from non-blocking notes, and a closing recommendation. Optional, but leave it out only for a trivial change."
         },
         tab: {
           type: 'string',
diff --git a/src/main/persistence.ts b/src/main/persistence.ts
index d9247463..f9795a4c 100644
--- a/src/main/persistence.ts
+++ b/src/main/persistence.ts
@@ -481,7 +481,7 @@ Prefer these over blind curl/fetch — or shelling out to \`open \`, which
 
 Show your work with screenshots — this applies in Chat tabs, whose transcript is rendered markdown; a terminal tab is an xterm and will show the markdown as literal text. Every screenshot_tab result comes back with the path it was saved to, and embedding an absolute image path as markdown (\`![caption](/path/to/shot.png)\`) renders it inline — so when you've changed something visual, end your reply with the screenshot that proves it rather than only describing it. This works for any image file on this machine, not just screenshot_tab captures. Append a \`#w=\` fragment to set the displayed width: \`![caption](path#w=500)\` is 500px, \`#w=50%\` fills half the column, \`#w=400x300\` sets both dimensions, and no fragment means a default size. Use the fragment rather than a \`|\` size spec in the alt text — inside a markdown table a pipe is a column delimiter, so the cell splits and the embed renders as broken literal text. Be selective: embed the one or two shots that show the result, not every frame you took while finding your way around.
 
-Chat tabs also draw diagrams: a fenced \`\`\`mermaid code block (flowchart, sequenceDiagram, stateDiagram-v2, classDiagram, erDiagram, …) renders as a real diagram, with a toggle to its source. Reach for one when a flow, a sequence of calls, a state machine or an architecture is easier seen than read; a small ASCII sketch is still fine for something tiny. Quote node labels that contain punctuation (\`A["parse()"]\`) — a syntax error falls back to showing the source. In a terminal tab it is just text.
+Chat tabs also draw diagrams: a fenced \`\`\`mermaid code block (flowchart, sequenceDiagram, stateDiagram-v2, classDiagram, erDiagram, …) renders as a real diagram, with a toggle to its source. Reach for one when a flow, a sequence of calls, a state machine or an architecture is easier seen than read; a small ASCII sketch is still fine for something tiny. Quote node labels that contain punctuation (\`A["parse()"]\`) — a syntax error falls back to showing the source. To color nodes, use Ness's theme-aware classes instead of hex colors, which you can't check against the user's theme: \`A:::added\` (green), \`A:::changed\` (amber), \`A:::removed\` (red, struck through), \`A:::focus\` (accent) — in flowcharts, state and class diagrams. Use them to mean something, like which parts a change touches, not for decoration. In a terminal tab it is just text.
 
 Ness also exposes shell tabs for long-running processes — anything that wouldn't naturally exit within a few seconds (dev servers, watchers, \`tail -f\`, REPL-style tools, long builds). Drive them via the ness-control shell tools (scoped to this worktree only):
 
diff --git a/src/renderer/styles.css b/src/renderer/styles.css
index 98266e32..f5276a4c 100644
--- a/src/renderer/styles.css
+++ b/src/renderer/styles.css
@@ -410,6 +410,23 @@ mark.find-hit-current {
   display: none;
 }
 
+/* Semantic node classes for mermaid diagrams (`A:::added`). Agents can't
+   see the theme, so instead of picking hex colors they name a meaning and
+   the theme supplies the color. !important because mermaid's own