From 940b04b0efd09a9ee009c3db638cc0ed35bdf2aa Mon Sep 17 00:00:00 2001 From: Steven Vandevelde Date: Tue, 1 Sep 2026 00:18:50 +0200 Subject: [PATCH] fix: tests --- src/common/element.js | 1 + .../refiner/initial-contents/element.js | 25 +++++++++++++------ src/facets/misc/webmcp/index.inline.js | 10 ++++++++ .../output/refiner/initial-contents/test.ts | 8 ++++++ 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/common/element.js b/src/common/element.js index 8ff6e819..c54179a6 100644 --- a/src/common/element.js +++ b/src/common/element.js @@ -392,6 +392,7 @@ export class BroadcastableDiffuseElement extends DiffuseElement { // Forwarding unconditionally, rather than only when some lock // happens to be pending, is what keeps a follower's writes from // being silently lost. + /** @param {Parameters} args */ wrapFn = (...args) => { proxy[action](...args).catch(() => {}); return ogFn(...args); diff --git a/src/components/transformer/output/refiner/initial-contents/element.js b/src/components/transformer/output/refiner/initial-contents/element.js index 9ddd7df7..7e4586aa 100644 --- a/src/components/transformer/output/refiner/initial-contents/element.js +++ b/src/components/transformer/output/refiner/initial-contents/element.js @@ -40,7 +40,9 @@ class InitialContentsTransformer extends OutputTransformer { // covering data arriving from another device via sync. this.effect(() => { const col = base.facets.collection(); - if (col.state !== "loaded" || col.data.length === 0) return; + if (col.state !== "loaded") return; + const data = Array.isArray(col.data) ? col.data : []; + if (data.length === 0) return; this.#markInitialized(); }); @@ -52,12 +54,16 @@ class InitialContentsTransformer extends OutputTransformer { const col = base.facets.collection(); if (col.state !== "loaded") return col; - if (col.data.length > 0) { - return { state: "loaded", data: col.data }; + // An empty backing output reports "loaded" with no data yet + // (e.g. an indexed-db output returns undefined for a missing + // key). Treat that as an empty collection. + const data = Array.isArray(col.data) ? col.data : []; + if (data.length > 0) { + return { state: "loaded", data }; } if (this.#isInitialized()) { - return { state: "loaded", data: col.data }; + return { state: "loaded", data }; } // Determine starting set @@ -89,14 +95,19 @@ class InitialContentsTransformer extends OutputTransformer { // METHODS /** - * The id of the currently selected output, or "local" when no custom - * output is selected (data lives in the default/local storage). + * The id of the current output, used to scope the "initialized" flag per + * output. When connected to an output configurator this is the id of the + * selected sub-output; when connected directly to an output element it is + * that element's own id. Falls back to "local" (the default/local storage). */ #selectedId() { const output = /** @type {OutputConfiguratorElement | undefined} */ ( this.output.signal() ); - return output?.selected()?.id ?? "local"; + if (!output) return "local"; + return typeof output.selected === "function" + ? output.selected()?.id ?? "local" + : output.id || "local"; } /** diff --git a/src/facets/misc/webmcp/index.inline.js b/src/facets/misc/webmcp/index.inline.js index 5a6ec8af..22affd44 100644 --- a/src/facets/misc/webmcp/index.inline.js +++ b/src/facets/misc/webmcp/index.inline.js @@ -8,6 +8,8 @@ import * as Output from "~/common/output.js"; */ const FACET_TYPE = "sh.diffuse.output.facet"; + +/** @param {string} text */ const ok = (text) => ({ content: [{ type: "text", text }] }); /** @param {string} html */ @@ -40,6 +42,14 @@ async function registerFacetTools() { const modelContext = /** @type {any} */ (document).modelContext; if (!modelContext?.registerTool) return; + /** + * @param {{ + * name: string, + * description: string, + * inputSchema: Record, + * execute: (args: Record) => Promise, + * }} tool + */ const register = async (tool) => { try { await modelContext.registerTool(tool); diff --git a/tests/components/transformer/output/refiner/initial-contents/test.ts b/tests/components/transformer/output/refiner/initial-contents/test.ts index 4cef6011..eb04eaba 100644 --- a/tests/components/transformer/output/refiner/initial-contents/test.ts +++ b/tests/components/transformer/output/refiner/initial-contents/test.ts @@ -91,6 +91,7 @@ describe("components/transformer/output/refiner/initial-contents", () => { // Explicit save marks as initialized (even with empty array) await t.facets.save([]); + col = t.facets.collection(); if (col.state !== "loaded") return null; return (col.data as unknown[]).length; }); @@ -115,6 +116,13 @@ describe("components/transformer/output/refiner/initial-contents", () => { t.setAttribute("output-selector", "#test-idb-data"); document.body.append(t); + // Settle the backing output's initial (empty) load before saving, so + // the asynchronous load can't overwrite the data saved below. + const settle = Date.now() + 2000; + while (Date.now() < settle && t.facets.collection().state !== "loaded") { + await new Promise((r) => setTimeout(r, 20)); + } + await t.facets.save([ { $type: "sh.diffuse.output.facet", -- 2.51.2