diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 986bfbdd..1e3c7d74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,9 @@ jobs: - name: Test (agent-sdk vitest suite) run: pnpm --filter @iqlabs-official/agent-sdk test + env: + # the panel Chrome layout pass must run here; a missing browser is a failure, never a skip + PANEL_CHROME_REQUIRED: "1" # The runtime smoke test (pnpm run test:run) is intentionally NOT run here: # it drives the real claude + codex CLIs and needs them installed and logged @@ -33,6 +36,10 @@ jobs: - name: Typecheck agent-sdk run: pnpm --filter @iqlabs-official/agent-sdk exec tsc --noEmit + # the panel modules need the DOM lib, which the root tsconfig excludes (cli/localhost typecheck + # core with lib ES2022 only), so they get their own tsconfig and their own gate + - name: Typecheck panel (DOM) + run: pnpm --filter @iqlabs-official/agent-sdk typecheck:panel - name: Typecheck CLI run: pnpm --filter agentnet-cli exec tsc --noEmit - name: Typecheck localhost diff --git a/package.json b/package.json index adb70311..1e1e1a66 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "test:run": "pnpm --filter @iqlabs-official/agent-sdk test:run", "build:core": "pnpm --filter @iqlabs-official/agent-sdk build", + "build:panel": "pnpm --filter @iqlabs-official/agent-sdk build:panel", "build:vscode": "pnpm --filter agentnet-vscode build", "build:cli": "pnpm --filter agentnet-cli build", "dev:cli": "pnpm --filter agentnet-cli dev" diff --git a/packages/core/package.json b/packages/core/package.json index 3f6c9ef3..d7d8fdaf 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -14,7 +14,10 @@ "test": "vitest run", "test:run": "tsx test/test-runtime.ts", "test:memory": "tsx test/test-memory.ts", - "test:skills": "tsx test/test-skills.ts" + "test:skills": "tsx test/test-skills.ts", + "build:panel": "tsx scripts/buildPanel.ts", + "typecheck:panel": "tsc -p src/chat/ui/panel/tsconfig.json", + "test:panel:chrome": "PANEL_CHROME_REQUIRED=1 vitest run src/chat/ui/panel.chrome.spec.ts" }, "peerDependencies": { "@iqlabs-official/solana-sdk": "^0.1.28", @@ -23,7 +26,9 @@ "devDependencies": { "@iqlabs-official/solana-sdk": "^0.1.28", "@solana/web3.js": "^1.98.0", + "@types/jsdom": "^30.0.0", "@types/node": "^20.0.0", + "jsdom": "^30.0.1", "tsup": "^8.0.0", "tsx": "^4.19.2", "tweetnacl": "^1.0.3", diff --git a/packages/core/scripts/buildPanel.ts b/packages/core/scripts/buildPanel.ts new file mode 100644 index 00000000..cb53595f --- /dev/null +++ b/packages/core/scripts/buildPanel.ts @@ -0,0 +1,110 @@ +// Builds the chat panel script from src/chat/ui/panel/main.ts (real ES modules) into ONE browser +// IIFE and writes it into src/chat/ui/panel.generated.ts as a string constant. WHY a committed +// generated file, like mdLibs.generated.ts: core is consumed as raw .ts by every surface's bundler +// and typechecker, so chatHtml() must inline the panel with zero runtime file access, and an +// ignored artifact would add a mandatory pre-step to every tsc/tsup/vsce invocation. +// panel.generated.spec.ts rebuilds the bundle in-process and fails when the committed file is stale. +// +// pnpm build:panel rebuild once +// pnpm build:panel --watch rebuild on every change under panel/ +import { build, type Options } from "tsup"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const CORE_ROOT = join(dirname(fileURLToPath(import.meta.url)), ".."); +const ENTRY = join(CORE_ROOT, "src", "chat", "ui", "panel", "main.ts"); +const DEST = join(CORE_ROOT, "src", "chat", "ui", "panel.generated.ts"); +const OUT_NAME = "panel.global.js"; // tsup's file name for an iife entry called "panel" + +function buildOptions(outDir: string): Options { + return { + entry: { panel: ENTRY }, + format: ["iife"], + platform: "browser", + target: "es2022", + // tsup resolves a tsconfig from process.cwd() unless told otherwise, so without this the + // output depended on the invoking directory: the panel's own config is the one that + // describes these modules, and the strict directive is emitted deliberately below + // instead of being inherited from whichever tsconfig happened to be found. + tsconfig: join(CORE_ROOT, "src", "chat", "ui", "panel", "tsconfig.json"), + banner: { js: '"use strict";' }, + // tsup's rollup treeshake pass rewrote data (it dropped CHAT_MODEL_OPTIONS properties and an + // unused `let` write when this migration was tried); esbuild alone keeps the code verbatim. + treeshake: false, + minify: false, + sourcemap: false, + dts: false, + splitting: false, + clean: false, + silent: true, + config: false, + outDir, + esbuildOptions(o) { + o.charset = "utf8"; // keep the Korean UI strings literal instead of \u escapes + o.legalComments = "none"; + o.absWorkingDir = CORE_ROOT; // path comments must not depend on the invoking cwd (determinism) + }, + }; +} + +// The un-escape sentinels: the legacy script was a template literal whose backslashes were +// doubled, so a surviving `\\n` or a ` { throw new Error("panel bundle: " + why); }; + if (!js.startsWith('"use strict";')) fail("does not start with \"use strict\""); + if (js.includes(" tag)"); + if (js.includes("process.")) fail("contains process. (node-only code reached the browser bundle)"); + if (js.includes("require(")) fail("contains require( (a CommonJS dependency reached the bundle)"); + const escaped = js.split("\n").filter((l) => l.includes("\\\\n") && !l.trim().startsWith("//")); + if (escaped.length) fail("double-escaped newline outside a comment: " + escaped[0].trim().slice(0, 80)); +} + +export async function bundlePanel(): Promise { + const outDir = mkdtempSync(join(tmpdir(), "agentnet-panel-")); + try { + await build(buildOptions(outDir)); + const js = readFileSync(join(outDir, OUT_NAME), "utf8"); + assertBundle(js); + return js; + } finally { + rmSync(outDir, { recursive: true, force: true }); + } +} + +// A JSON string literal is a valid JS string literal: no decoder is needed and the text stays +// grep-able; non-ASCII stays literal (the file is UTF-8 like webview.ts). +export function renderPanelModule(js: string): string { + return ( + "// AUTO-GENERATED by scripts/buildPanel.ts from src/chat/ui/panel/ -- DO NOT EDIT; re-run pnpm build:panel\n" + + `export const PANEL_SCRIPT = ${JSON.stringify(js)};\n` + ); +} + +function writeGenerated(js: string): void { + writeFileSync(DEST, renderPanelModule(js), "utf8"); + console.log(`wrote ${DEST} (${(js.length / 1024).toFixed(0)} KB)`); +} + +async function main(): Promise { + if (!process.argv.includes("--watch")) { + writeGenerated(await bundlePanel()); + return; + } + // watch mode keeps its temp dir for the process lifetime; each successful rebuild rewrites DEST + const outDir = mkdtempSync(join(tmpdir(), "agentnet-panel-watch-")); + await build({ + ...buildOptions(outDir), + watch: true, + onSuccess: async () => { + const js = readFileSync(join(outDir, OUT_NAME), "utf8"); + assertBundle(js); + writeGenerated(js); + }, + }); +} + +if (import.meta.url === pathToFileURL(process.argv[1]).href) { + main().catch((e) => { console.error(e); process.exit(1); }); +} diff --git a/packages/core/src/chat/ui/avatar.spec.ts b/packages/core/src/chat/ui/avatar.spec.ts new file mode 100644 index 00000000..21a4c01a --- /dev/null +++ b/packages/core/src/chat/ui/avatar.spec.ts @@ -0,0 +1,25 @@ +// Pins hashSeed/avatarSvg to the goldens captured from the string-shipped AVATAR_SCRIPT right +// before the #215 module port, so the real exports cannot drift from what the panel rendered. +import { describe, it, expect } from "vitest"; +import { hashSeed, avatarSvg } from "./avatar.js"; +import goldens from "../../../test/fixtures/panel/avatarGoldens.json"; + +describe("chat/ui/avatar: hashSeed and avatarSvg match the pre-migration goldens", () => { + it("reproduces every golden byte-for-byte", () => { + expect(goldens).toHaveLength(5); + for (const g of goldens) { + expect(hashSeed(g.seed)).toBe(g.hash); + expect(avatarSvg(g.seed)).toBe(g.svg); + } + }); + + it("drops the shared `; -// The client-side generator the webview runs (string injected into its - + `; } diff --git a/packages/core/src/notes/quoteRefs.spec.ts b/packages/core/src/notes/quoteRefs.spec.ts index 6108f8fa..237898cb 100644 --- a/packages/core/src/notes/quoteRefs.spec.ts +++ b/packages/core/src/notes/quoteRefs.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { extractQuoteRefs, parseNoteRef, QUOTE_REFS_MAX } from "./quoteRefs.js"; +import { extractQuoteRefs, parseNoteRef, splitQuoteRefs, QUOTE_REFS_MAX } from "./quoteRefs.js"; const W = "C3EPAsjHq6DHLDzG2bXySFpUYmQ5AUqDXDfEiEsCekrH"; const id = (n: string) => `note:${W}:1787813695000:${n}`; @@ -59,3 +59,65 @@ describe("notes/quoteRefs — the >>note: text convention", () => { expect(parseNoteRef(undefined)).toBeNull(); }); }); + +describe("notes/quoteRefs: splitQuoteRefs, the panel's inline-render input", () => { + it("alternates text and ref segments in order and skips empty text runs", () => { + expect(splitQuoteRefs(`see >>${id("aaaaaa")} and >>${id("bbbbbb")}!`)).toEqual([ + { text: "see " }, + { ref: id("aaaaaa"), author: W }, + { text: " and " }, + { ref: id("bbbbbb"), author: W }, + { text: "!" }, + ]); + expect(splitQuoteRefs(`>>${id("aaaaaa")}`)).toEqual([{ ref: id("aaaaaa"), author: W }]); + expect(splitQuoteRefs(`>>${id("aaaaaa")}>>${id("bbbbbb")}`)).toEqual([ + { ref: id("aaaaaa"), author: W }, + { ref: id("bbbbbb"), author: W }, + ]); + expect(splitQuoteRefs("")).toEqual([]); + expect(splitQuoteRefs("plain text")).toEqual([{ text: "plain text" }]); + }); + + it("keeps duplicates and never caps: the per-view accounting belongs to the renderer", () => { + const many = Array.from({ length: QUOTE_REFS_MAX + 3 }, () => `>>${id("aaaaaa")}`).join(" "); + expect(splitQuoteRefs(many).filter((s) => "ref" in s)).toHaveLength(QUOTE_REFS_MAX + 3); + }); + + it("refuses a ref glued to alphanumerics and accepts 4-6 char nonces", () => { + expect(splitQuoteRefs(`>>${id("9n4iry")}glued`)).toEqual([{ text: `>>${id("9n4iry")}glued` }]); + expect(splitQuoteRefs(`>>${id("9n4iry")}9`)).toEqual([{ text: `>>${id("9n4iry")}9` }]); + expect(splitQuoteRefs(`>>${id("ab12")}`)).toEqual([{ ref: id("ab12"), author: W }]); + expect(splitQuoteRefs(`>>${id("ab12cd")}`)).toEqual([{ ref: id("ab12cd"), author: W }]); + expect(splitQuoteRefs(`>>${id("ab12cd3")}`)).toEqual([{ text: `>>${id("ab12cd3")}` }]); + expect(splitQuoteRefs(`>>${id("ab1")}`)).toEqual([{ text: `>>${id("ab1")}` }]); + }); + + it("splits exactly like the retired panel regex did", () => { + // webview.ts W:4115 before #215, kept here only as the equivalence oracle: split() with the + // capture group put refs at odd indexes and (possibly empty) text runs at even ones. + const LEGACY_PANEL_SPLIT_RE = /(>>note:[1-9A-HJ-NP-Za-km-z]{32,44}:[0-9]{10,16}:[a-z0-9]{4,6})(?![A-Za-z0-9])/; + const texts = [ + "", + "no refs here", + `>>${id("aaaaaa")}`, + `lead >>${id("aaaaaa")}`, + `>>${id("aaaaaa")} trail`, + `a >>${id("aaaaaa")} b >>${id("bbbbbb")} c`, + `>>${id("aaaaaa")}>>${id("bbbbbb")}`, + `>>${id("aaaaaa")} twice >>${id("aaaaaa")}`, + `glued >>${id("aaaaaa")}x and ok >>${id("bbbbbb")}.`, + `line one\n>>${id("ab12")}\nline three`, + `한글 앞 >>${id("cd34ef")} 한글 뒤`, + `>>tx:${W} and (>>${id("aaaaaa")}) and >>${id("ab12cd3")}`, + ]; + for (const text of texts) { + const parts = text.split(LEGACY_PANEL_SPLIT_RE); + const legacy = parts.filter((p, i) => i % 2 === 1 || p); + const segs = splitQuoteRefs(text).map((s) => ("ref" in s ? ">>" + s.ref : s.text)); + expect(segs).toEqual(legacy); + expect(splitQuoteRefs(text).filter((s) => "ref" in s).map((s) => (s as { author: string }).author)).toEqual( + parts.filter((_, i) => i % 2 === 1).map(() => W), + ); + } + }); +}); diff --git a/packages/core/src/notes/quoteRefs.ts b/packages/core/src/notes/quoteRefs.ts index be377bc5..4fd22f2d 100644 --- a/packages/core/src/notes/quoteRefs.ts +++ b/packages/core/src/notes/quoteRefs.ts @@ -17,8 +17,7 @@ // but shorter when the random tail is, so 4-6 (slice(2, 8) caps at 6). The trailing lookahead refuses a // ref glued to alphanumeric text: a corrupted id would fetch a nonexistent post // and render a deadlink for a ref that actually resolves, so no match is safer. -// The VS Code panel template cannot import this (it is emitted browser JS), so -// webview.ts carries a byte-identical copy: change BOTH or the surfaces diverge. +// The VS Code panel (chat/ui/panel/quotes.ts) imports this module, so there is one grammar. const QUOTE_REF = />>(note:([1-9A-HJ-NP-Za-km-z]{32,44}):(\d{10,16}):([a-z0-9]{4,6}))(?![A-Za-z0-9])/g; export interface QuoteRef { @@ -50,3 +49,20 @@ export function parseNoteRef(id: string | undefined): QuoteRef | null { const refs = extractQuoteRefs(`>>${id}`); return refs.length === 1 && refs[0].ref === id ? refs[0] : null; } + +/** + * The text as alternating text and ref segments, in order, so a renderer can interleave text + * nodes and quote markers. No cap and no dedupe: those are per-view policies (extractQuoteRefs + * caps the reads; the panel counts unique cards per render pass). Empty text runs are skipped. + */ +export function splitQuoteRefs(text: string): Array<{ text: string } | QuoteRef> { + const out: Array<{ text: string } | QuoteRef> = []; + let last = 0; + for (const m of text.matchAll(QUOTE_REF)) { + if (m.index > last) out.push({ text: text.slice(last, m.index) }); + out.push({ ref: m[1], author: m[2] }); + last = m.index + m[0].length; + } + if (last < text.length) out.push({ text: text.slice(last) }); + return out; +} diff --git a/packages/core/test/fixtures/panel/avatarGoldens.json b/packages/core/test/fixtures/panel/avatarGoldens.json new file mode 100644 index 00000000..36e583e7 --- /dev/null +++ b/packages/core/test/fixtures/panel/avatarGoldens.json @@ -0,0 +1,27 @@ +[ + { + "seed": "default", + "hash": 2470140894, + "svg": "" + }, + { + "seed": "", + "hash": 2166136261, + "svg": "" + }, + { + "seed": "9xQeWvG816bUx9EPjHmaT23yvVM2ZZBq7ftHbAr3o1z", + "hash": 3241799227, + "svg": "" + }, + { + "seed": "11111111111111111111111111111111", + "hash": 551368101, + "svg": "" + }, + { + "seed": "So11111111111111111111111111111111111111112", + "hash": 296707927, + "svg": "" + } +] diff --git a/packages/core/test/fixtures/panel/contract.ts b/packages/core/test/fixtures/panel/contract.ts new file mode 100644 index 00000000..23541d5d --- /dev/null +++ b/packages/core/test/fixtures/panel/contract.ts @@ -0,0 +1,123 @@ +// The panel's host message contract, captured from the legacy webview script before the #215 +// module cut (plan 4.1) so the artifact and render specs can pin it: every type the panel posts +// (vscode.postMessage sites, including the ones built as an object first), every type the +// message listener handles in chain order, and the boot posts in the order they leave. +export const PANEL_OUTBOUND_TYPES: string[] = [ + "installEngine", + "model", + "mode", + "effort", + "platform", + "approvalDecision", + "delete", + "open", + "interrupt", + "claudeAuthCode", + "startClaudeLogin", + "startCodexLogin", + "logoutEngine", + "new", + "clear", + "slashCommand", + "send", + "newTab", + "openCloud", + "disconnectCloud", + "pickCloud", + "wallet", + "publishSkill", + "getBlogPost", + "listAgents", + "getBlogFeed", + "getBlogComments", + "postBlogComment", + "postAgentNote", + "getAgentProfile", + "getGithubStatus", + "submitGithubToken", + "registerWorkRepo", + "buyAllSkills", + "getBalance", + "ownedSkills", + "setSkillShopping", + "getSkillShopping", + "searchSkills", + "getSkillDetail", + "buySkill", + "getSkillDoc", + "postNote", + "reEquipSkill", + "disposeSkill", + "airdrop", + "setHeliusKey", + "useDefaultRpc", + "getRpcStatus", + "reconnectCloud", + "disconnectWallet", + "loadMore", + "ready", +]; + +export const PANEL_INBOUND_TYPES: string[] = [ + "message", + "sessions", + "notice", + "status", + "loading", + "clear", + "turnEnd", + "modelOptions", + "usage", + "skillActive", + "rpcStatus", + "skillShopping", + "searchResults", + "searchError", + "skillDetail", + "skillDoc", + "postNoteResult", + "notes", + "ownedSkills", + "buyResult", + "airdropResult", + "githubStatus", + "workRepoRegistered", + "disposeResult", + "reEquipResult", + "agents", + "agentProfile", + "blogFeed", + "blogPost", + "blogComments", + "blogCommentResult", + "buyAllResult", + "agentNoteResult", + "balance", + "publishProgress", + "publishResult", + "platform", + "cliStatus", + "engineUpdate", + "claudeLoginUrl", + "claudeLoginStatus", + "codexLoginChallenge", + "codexLoginStatus", + "toast", + "openUrl", + "storage", + "cloudSync", + "wallet", + "page", + "older", + "approval", + "approvalDismiss", +]; + +export const PANEL_BOOT_POSTS: string[] = [ + "ownedSkills", + "getSkillShopping", + "getRpcStatus", + "ready", + "wallet", + "getBalance", +]; diff --git a/packages/core/test/fixtures/panel/inbound.ts b/packages/core/test/fixtures/panel/inbound.ts new file mode 100644 index 00000000..0495bf74 --- /dev/null +++ b/packages/core/test/fixtures/panel/inbound.ts @@ -0,0 +1,61 @@ +// One minimal host -> panel payload per inbound type (plan 4.1), in PANEL_INBOUND_TYPES order, +// each carrying only the fields its listener branch reads. The jsdom render pass dispatches +// every one of them to prove the listener never throws on the host's smallest legal shapes. +export const WALLET = "9xQeWvG816bUx9EPjHmaT23yvVM2ZZBq7ftHbAr3o1z"; +export const NOTE_ID = `note:${WALLET}:1725400000000:k7x2mq`; +export const MINT = "MintAAAA1111111111111111111111111111111111"; + +export const PANEL_INBOUND_PAYLOADS: Array<{ type: string } & Record> = [ + { type: "message", msg: { role: "assistant", cli: "claude", text: "hello **world**", durationMs: 1200, model: "opus" } }, + { type: "sessions", list: [{ sessionId: "s1", title: "first session", ts: Date.now() }], cloud: "none", activeId: "s1" }, + { type: "notice", text: "a notice" }, + { type: "status", status: { cli: "claude", sessionId: "s1", model: "opus", mode: "acceptEdits", effort: "default", contextTokens: 1500 } }, + { type: "loading" }, + { type: "clear" }, + { type: "turnEnd" }, + { type: "modelOptions", cli: "claude", options: [{ value: "opus", label: "Opus" }] }, + { type: "usage", contextTokens: 12000 }, + { type: "skillActive", origin: "nft", mint: MINT, name: "code-review" }, + { type: "rpcStatus", status: { dasReady: true, hasKey: true, masked: "...ab12", network: "devnet" } }, + { type: "skillShopping", on: true }, + { type: "searchResults", results: [{ id: MINT, name: "code-review", type: "skill" }] }, + { type: "searchError", message: "gateway down" }, + { type: "skillDetail", detail: { card: { id: MINT, name: "code-review", type: "skill" }, skillText: "# code-review" } }, + { type: "skillDoc", name: "code-review", text: "# code-review\nbody" }, + { type: "postNoteResult", ok: true }, + { type: "notes", skillId: MINT, notes: [] }, + { type: "ownedSkills", names: ["code-review"], mints: { "code-review": MINT }, meta: {}, disposedMints: {}, workflowMints: [] }, + { type: "buyResult", ok: false, error: "insufficient funds", code: "insufficient_funds" }, + { type: "airdropResult", ok: true }, + { type: "githubStatus", hasToken: false }, + { type: "workRepoRegistered", ok: false, error: "not a public repo" }, + { type: "disposeResult", ok: false, error: "unequip failed" }, + { type: "reEquipResult", ok: false, error: "re-equip failed" }, + { type: "agents", agents: [] }, + { type: "agentProfile", profile: { wallet: WALLET, self: false, createdSkills: [], ownedSkills: [] } }, + { type: "blogFeed", posts: [] }, + { type: "blogPost", postId: NOTE_ID, post: null }, + { type: "blogComments", postId: NOTE_ID, threads: [] }, + { type: "blogCommentResult", postId: NOTE_ID, ok: true }, + { type: "buyAllResult", ok: true, bought: 0, failed: 0 }, + { type: "agentNoteResult", ok: true, agentWallet: WALLET }, + { type: "balance", lamports: 1_500_000_000 }, + { type: "publishProgress", phase: "mint", signed: 1, total: 3 }, + { type: "publishResult", ok: false, error: "publish failed" }, + { type: "platform", cli: "claude" }, + { type: "cliStatus", claude: "ok", codex: "ok" }, + { type: "engineUpdate", cli: "codex" }, + { type: "claudeLoginUrl", url: "https://example.invalid/claude-login" }, + { type: "claudeLoginStatus", status: "done" }, + { type: "codexLoginChallenge", url: "https://example.invalid/codex-login", code: "ABCD-EFGH" }, + { type: "codexLoginStatus", status: "done" }, + { type: "toast", text: "a toast" }, + { type: "openUrl", url: "https://example.invalid/open" }, + { type: "storage", info: { connected: false }, options: [] }, + { type: "cloudSync", status: { ok: true } }, + { type: "wallet", address: WALLET }, + { type: "page", hasMore: false, cursor: null }, + { type: "older", messages: [{ role: "user", text: "older question" }, { role: "assistant", cli: "claude", text: "older answer" }], hasMore: false, cursor: null }, + { type: "approval", req: { id: "ap1", kind: "bash", tool: "Bash", title: "Run a command", command: "ls -la", cli: "claude" } }, + { type: "approvalDismiss", id: "ap1" }, +]; diff --git a/packages/core/test/fixtures/panel/sigilGoldens.json b/packages/core/test/fixtures/panel/sigilGoldens.json new file mode 100644 index 00000000..50f9a726 --- /dev/null +++ b/packages/core/test/fixtures/panel/sigilGoldens.json @@ -0,0 +1,22 @@ +[ + { + "name": "", + "svg": "" + }, + { + "name": "code-review", + "svg": "" + }, + { + "name": "iq-onchain-db", + "svg": "" + }, + { + "name": "한글 스킬", + "svg": "" + }, + { + "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "svg": "" + } +] diff --git a/packages/core/test/test-skills.ts b/packages/core/test/test-skills.ts index 2f469c5c..eda5de6b 100644 --- a/packages/core/test/test-skills.ts +++ b/packages/core/test/test-skills.ts @@ -58,8 +58,9 @@ console.log("2. Preserve publisher-authored frontmatter"); }; const md = toSkillMd(meta, MINT); check("no second frontmatter block", (md.match(/^---/gm) || []).length === 2); - check("authored name kept", md.includes("name: my-skill")); - check("authored user-invocable kept", md.includes("user-invocable: true")); + // quote-agnostic: normalizeFrontmatter re-emits plain scalars quoted so codex's strict YAML loads them + check("authored name kept", /^name: ["']?my-skill["']?$/m.test(md)); + check("authored user-invocable kept", /^user-invocable: ["']?true["']?$/m.test(md)); check("synthesized description NOT injected", !md.includes('description: "ignored"')); } @@ -116,10 +117,11 @@ console.log("5. codex skill-firing detection from the output stream"); check("plain message → no skill signal", msgOnly.skill === undefined); } -// 6. Message contract ↔ VSCode webview agreement. The webview is an HTML string -// (no compile-time typecheck), so guard that every marketplace `type` it emits/ -// handles is a real message in the shared contract — a typo or a removed message -// fails here instead of silently no-op'ing on that surface. +// 6. Message contract ↔ VSCode webview agreement. The panel script is a typechecked module +// bundle now (chat/ui/panel/), but its message types are still string literals inlined +// into the HTML, so guard that every marketplace `type` it emits/handles is a real message +// in the shared contract: a typo or a removed message fails here instead of silently +// no-op'ing on that surface. Quote-agnostic, because esbuild emits double quotes. console.log("6. webview market messages match the shared contract"); { const html = chatHtml(); @@ -127,10 +129,10 @@ console.log("6. webview market messages match the shared contract"); const REQUESTS = ["searchSkills", "buySkill", "buyAllSkills", "ownedSkills"]; const EVENTS = ["searchResults", "buyResult", "buyAllResult", "ownedSkills", "skillActive"]; for (const t of REQUESTS) { - check(`webview sends '${t}'`, html.includes(`type: '${t}'`)); + check(`webview sends '${t}'`, new RegExp(`type: ['"]${t}['"]`).test(html)); } for (const t of EVENTS) { - check(`webview handles '${t}'`, html.includes(`m.type === '${t}'`)); + check(`webview handles '${t}'`, new RegExp(`m\\.type === ['"]${t}['"]`).test(html)); } } diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 7b5da3b4..964d461e 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -11,5 +11,6 @@ "outDir": "dist", "types": ["node"] }, - "include": ["src", "test"] + "include": ["src", "test"], + "exclude": ["src/chat/ui/panel"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00fb4078..f57ff507 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,9 +44,15 @@ importers: '@solana/web3.js': specifier: ^1.98.0 version: 1.98.4(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) + '@types/jsdom': + specifier: ^30.0.0 + version: 30.0.0 '@types/node': specifier: ^20.0.0 version: 20.19.42 + jsdom: + specifier: ^30.0.1 + version: 30.0.1(@noble/hashes@2.2.0) tsup: specifier: ^8.0.0 version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.22.4)(typescript@5.9.3) @@ -61,7 +67,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.1.8 - version: 4.1.8(@types/node@20.19.42)(vite@7.3.5(@types/node@20.19.42)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.22.4)) + version: 4.1.8(@types/node@20.19.42)(jsdom@30.0.1(@noble/hashes@2.2.0))(vite@7.3.5(@types/node@20.19.42)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.22.4)) packages/mcp: devDependencies: @@ -273,6 +279,14 @@ packages: zod: optional: true + '@asamuzakjp/css-color@6.0.7': + resolution: {integrity: sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==} + engines: {node: ^22.13.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@8.3.2': + resolution: {integrity: sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==} + engines: {node: ^22.13.0 || >=24.0.0} + '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} @@ -360,6 +374,10 @@ packages: resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + '@coral-xyz/anchor-errors@0.31.1': resolution: {integrity: sha512-NhNEku4F3zzUSBtrYz84FzYWm48+9OvmT1Hhnwr6GnPQry2dsEqH/ti/7ASjjpoFTWRnPXrjAIT1qM6Isop+LQ==} engines: {node: '>=10'} @@ -374,6 +392,42 @@ packages: peerDependencies: '@solana/web3.js': ^1.69.0 + '@csstools/color-helpers@6.1.1': + resolution: {integrity: sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.3.0': + resolution: {integrity: sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.2.2': + resolution: {integrity: sha512-3QKjR/vxyjcSXBLgb6lP0S3MGdvwbmqSsvLPbYdVORqPDc8FX1HAJ0Spk38bxaRXgvENTA47tlhhbb5Z2e8hEg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.12': + resolution: {integrity: sha512-3vLQK+dXxhBMR2Wx99PTCifE+vHtW2ndZWyla8yK813ev6oGhyn8Lja8jCyGAWTJ+LEYZK7EVtJxrDj8ztevJw==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + '@esbuild/aix-ppc64@0.27.7': resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} engines: {node: '>=18'} @@ -686,6 +740,15 @@ packages: cpu: [x64] os: [win32] + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + '@hono/node-server@1.19.14': resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} engines: {node: '>=18.14.1'} @@ -1124,6 +1187,9 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/jsdom@30.0.0': + resolution: {integrity: sha512-uAHGxujGE0cDaKGdK28zgDotFtNA7MKq5DXl8LrfdxdCI8VHcg15oJz+amHTChPNI5JpgEPQWc2xFdrw3em/nQ==} + '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} @@ -1144,6 +1210,9 @@ packages: '@types/react@19.2.17': resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -1260,6 +1329,9 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + bigint-buffer@1.1.5: resolution: {integrity: sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==} engines: {node: '>= 10.0.0'} @@ -1448,9 +1520,17 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1460,6 +1540,9 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -1519,6 +1602,10 @@ packages: resolution: {integrity: sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ==} engines: {node: '>=10.13.0'} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} @@ -1707,6 +1794,10 @@ packages: resolution: {integrity: sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==} engines: {node: '>=16.9.0'} + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -1802,6 +1893,9 @@ packages: resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==} engines: {node: '>=0.10.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} @@ -1840,6 +1934,15 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + jsdom@30.0.1: + resolution: {integrity: sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + peerDependencies: + canvas: ^3.2.3 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1952,6 +2055,10 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -1967,6 +2074,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + media-typer@1.1.0: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} @@ -2061,6 +2171,9 @@ packages: parse5@6.0.1: resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -2126,6 +2239,10 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + qrcode.react@4.2.0: resolution: {integrity: sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==} peerDependencies: @@ -2211,6 +2328,10 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} @@ -2337,6 +2458,9 @@ packages: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tailwindcss@4.3.0: resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} @@ -2372,6 +2496,13 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tldts-core@7.4.11: + resolution: {integrity: sha512-CW3WN2rIIE/Of21mulhgnGOwoDyEFNygyIBOONSdyAuSATgMMUCpLeUlB+E8sAwA5xRV9hYPl+kyZ9citHCaKg==} + + tldts@7.4.11: + resolution: {integrity: sha512-aBiNayCfTQxuIJBm06M+xR14cYaYlDlSXZbgsnKzKNxDKUVq7KFwTjwBSsb7m9Y5xO8WfPnBc63WaYFMTGlvqw==} + hasBin: true + toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -2379,9 +2510,17 @@ packages: toml@3.0.0: resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + tough-cookie@6.0.2: + resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} + engines: {node: '>=16'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -2441,6 +2580,13 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@8.10.2: + resolution: {integrity: sha512-7/+aSjzkUoLc92hV22bTW4aGanXf800zbwguhcICs0OAoCF9wDOE4wkopQ+SqfhXZm8mCK8gHpdTs7pZUWzK3w==} + + undici@8.10.2: + resolution: {integrity: sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==} + engines: {node: '>=22.19.0'} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -2549,9 +2695,29 @@ packages: jsdom: optional: true + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + whatwg-url@17.1.0: + resolution: {integrity: sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==} + engines: {node: ^22.14.0 || >=24.0.0} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -2613,6 +2779,13 @@ packages: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -2692,6 +2865,21 @@ snapshots: optionalDependencies: zod: 4.4.3 + '@asamuzakjp/css-color@6.0.7': + dependencies: + '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.2.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + lru-cache: 11.5.2 + + '@asamuzakjp/dom-selector@8.3.2': + dependencies: + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.2 + '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 @@ -2806,6 +2994,10 @@ snapshots: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + '@coral-xyz/anchor-errors@0.31.1': {} '@coral-xyz/anchor@0.32.1(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)': @@ -2835,6 +3027,30 @@ snapshots: bn.js: 5.2.3 buffer-layout: 1.2.2 + '@csstools/color-helpers@6.1.1': {} + + '@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.2.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.1.1 + '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.12(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + '@esbuild/aix-ppc64@0.27.7': optional: true @@ -2991,6 +3207,10 @@ snapshots: '@esbuild/win32-x64@0.28.0': optional: true + '@exodus/bytes@1.15.1(@noble/hashes@2.2.0)': + optionalDependencies: + '@noble/hashes': 2.2.0 + '@hono/node-server@1.19.14(hono@4.12.25)': dependencies: hono: 4.12.25 @@ -3427,6 +3647,13 @@ snapshots: '@types/estree@1.0.9': {} + '@types/jsdom@30.0.0': + dependencies: + '@types/node': 20.19.42 + '@types/tough-cookie': 4.0.5 + parse5: 8.0.1 + undici-types: 8.10.2 + '@types/node@12.20.55': {} '@types/node@20.19.42': @@ -3448,6 +3675,8 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/tough-cookie@4.0.5': {} + '@types/trusted-types@2.0.7': optional: true @@ -3566,6 +3795,10 @@ snapshots: baseline-browser-mapping@2.10.36: {} + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + bigint-buffer@1.1.5: dependencies: bindings: 1.5.0 @@ -3748,12 +3981,26 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + csstype@3.2.3: {} + data-urls@7.0.0(@noble/hashes@2.2.0): + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1(@noble/hashes@2.2.0) + transitivePeerDependencies: + - '@noble/hashes' + debug@4.4.3: dependencies: ms: 2.1.3 + decimal.js@10.6.0: {} + deepmerge@4.3.1: {} default-browser-id@5.0.1: {} @@ -3800,6 +4047,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.3 + entities@8.0.0: {} + environment@1.1.0: {} es-define-property@1.0.1: {} @@ -4028,6 +4277,12 @@ snapshots: hono@4.12.25: {} + html-encoding-sniffer@6.0.0(@noble/hashes@2.2.0): + dependencies: + '@exodus/bytes': 1.15.1(@noble/hashes@2.2.0) + transitivePeerDependencies: + - '@noble/hashes' + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -4129,6 +4384,8 @@ snapshots: dependencies: kind-of: 3.2.2 + is-potential-custom-element-name@1.0.1: {} + is-promise@4.0.0: {} is-unicode-supported@2.1.0: {} @@ -4169,6 +4426,32 @@ snapshots: js-tokens@4.0.0: {} + jsdom@30.0.1(@noble/hashes@2.2.0): + dependencies: + '@asamuzakjp/css-color': 6.0.7 + '@asamuzakjp/dom-selector': 8.3.2 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.12(css-tree@3.2.1) + '@exodus/bytes': 1.15.1(@noble/hashes@2.2.0) + css-tree: 3.2.1 + data-urls: 7.0.0(@noble/hashes@2.2.0) + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0(@noble/hashes@2.2.0) + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.2 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.2 + undici: 8.10.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 17.1.0(@noble/hashes@2.2.0) + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + jsesc@3.1.0: {} json-schema-to-ts@3.1.1: @@ -4247,6 +4530,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + lru-cache@11.5.2: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -4259,6 +4544,8 @@ snapshots: math-intrinsics@1.1.0: {} + mdn-data@2.27.1: {} + media-typer@1.1.0: {} merge-descriptors@2.0.0: {} @@ -4334,6 +4621,10 @@ snapshots: parse5@6.0.1: {} + parse5@8.0.1: + dependencies: + entities: 8.0.0 + parseurl@1.3.3: {} patch-console@2.0.0: {} @@ -4383,6 +4674,8 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + punycode@2.3.1: {} + qrcode.react@4.2.0(react@19.2.7): dependencies: react: 19.2.7 @@ -4494,6 +4787,10 @@ snapshots: safer-buffer@2.1.2: {} + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 @@ -4644,6 +4941,8 @@ snapshots: dependencies: has-flag: 4.0.0 + symbol-tree@3.2.4: {} + tailwindcss@4.3.0: {} tapable@2.3.3: {} @@ -4671,12 +4970,26 @@ snapshots: tinyrainbow@3.1.0: {} + tldts-core@7.4.11: {} + + tldts@7.4.11: + dependencies: + tldts-core: 7.4.11 + toidentifier@1.0.1: {} toml@3.0.0: {} + tough-cookie@6.0.2: + dependencies: + tldts: 7.4.11 + tr46@0.0.3: {} + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + tree-kill@1.2.2: {} ts-algebra@2.0.0: {} @@ -4735,6 +5048,10 @@ snapshots: undici-types@6.21.0: {} + undici-types@8.10.2: {} + + undici@8.10.2: {} + unpipe@1.0.0: {} update-browserslist-db@1.2.3(browserslist@4.28.2): @@ -4769,7 +5086,7 @@ snapshots: lightningcss: 1.32.0 tsx: 4.22.4 - vitest@4.1.8(@types/node@20.19.42)(vite@7.3.5(@types/node@20.19.42)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.22.4)): + vitest@4.1.8(@types/node@20.19.42)(jsdom@30.0.1(@noble/hashes@2.2.0))(vite@7.3.5(@types/node@20.19.42)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.22.4)): dependencies: '@vitest/expect': 4.1.8 '@vitest/mocker': 4.1.8(vite@7.3.5(@types/node@20.19.42)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.22.4)) @@ -4793,11 +5110,36 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.19.42 + jsdom: 30.0.1(@noble/hashes@2.2.0) transitivePeerDependencies: - msw + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + webidl-conversions@3.0.1: {} + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1(@noble/hashes@2.2.0): + dependencies: + '@exodus/bytes': 1.15.1(@noble/hashes@2.2.0) + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + whatwg-url@17.1.0(@noble/hashes@2.2.0): + dependencies: + '@exodus/bytes': 1.15.1(@noble/hashes@2.2.0) + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -4849,6 +5191,10 @@ snapshots: dependencies: is-wsl: 3.1.1 + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + y18n@5.0.8: {} yallist@3.1.1: {}