From 23eaf32eae76204357c1406b4a7d01bcece6b815 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 22 Aug 2026 12:38:52 +0200 Subject: [PATCH] Compose operation and component binding captures --- packages/eve-browser-lowering/dist/index.js | 6 +- packages/eve-browser-lowering/src/index.ts | 8 ++- .../test/host-isolation.test.mjs | 69 +++++++++++++++++++ 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/packages/eve-browser-lowering/dist/index.js b/packages/eve-browser-lowering/dist/index.js index a4d7c65..64e786c 100644 --- a/packages/eve-browser-lowering/dist/index.js +++ b/packages/eve-browser-lowering/dist/index.js @@ -1696,7 +1696,11 @@ export function createEveCommandIntent(commandId, props = {}, options = {}) { } const operationId = commandId || stringProp(action.type, "invoke"); const descriptor = surface?.commands?.find(command => command.command === operationId); - const captureBindings = stringArray(descriptor?.captureBindings ?? action.captureBindings ?? props.captureBindings); + const captureBindings = [...new Set([ + ...stringArray(descriptor?.captureBindings), + ...stringArray(action.captureBindings), + ...stringArray(props.captureBindings), + ])]; const bindings = captureBindingValues(surface?.surface?.root, providerId, surfaceId, captureBindings, options.draftStore); const payload = commandPayload(action); if (captureBindings.length) diff --git a/packages/eve-browser-lowering/src/index.ts b/packages/eve-browser-lowering/src/index.ts index d9eb699..2be87c0 100644 --- a/packages/eve-browser-lowering/src/index.ts +++ b/packages/eve-browser-lowering/src/index.ts @@ -1995,9 +1995,11 @@ export function createEveCommandIntent( } const operationId = commandId || stringProp(action.type, "invoke"); const descriptor = surface?.commands?.find(command => command.command === operationId); - const captureBindings = stringArray( - descriptor?.captureBindings ?? action.captureBindings ?? props.captureBindings, - ); + const captureBindings = [...new Set([ + ...stringArray(descriptor?.captureBindings), + ...stringArray(action.captureBindings), + ...stringArray(props.captureBindings), + ])]; const bindings = captureBindingValues( surface?.surface?.root, providerId, diff --git a/packages/eve-browser-lowering/test/host-isolation.test.mjs b/packages/eve-browser-lowering/test/host-isolation.test.mjs index d41e384..581480d 100644 --- a/packages/eve-browser-lowering/test/host-isolation.test.mjs +++ b/packages/eve-browser-lowering/test/host-isolation.test.mjs @@ -325,6 +325,75 @@ test("operations capture untouched authored choice defaults", () => { } }); +test("component captures extend rather than disappear behind operation descriptors", () => { + const restoreDom = installDom(); + try { + const commands = []; + const host = document.querySelector("#a"); + const provider = { + providerId: "ghostlight", + surfaces: [{ surfaceId: "ghostlight.play", worldInteraction: { + commandBoundary: "ghostlight.eve.commands", + receiptSchema: "gamecult.eve.command_result.v1", + } }], + }; + renderEveSurface({ + providerId: "ghostlight", + version: 17, + commands: [{ + command: "session_zero.decision.resolve", + payloadSchema: "session_zero.decision.resolve.v1", + captureBindings: [], + }], + surface: { + id: "ghostlight.play", + root: { id: "root", kind: "column", children: [ + { + id: "counter", + kind: "control.input.textarea", + props: { label: "Counterproposal" }, + stateBindings: [{ + targetProp: "value", + pointerId: "draft:counter", + sourceId: "renderer", + schemaId: "gamecult.eve.local_draft.v1", + routeKind: "local", + bindingName: "counter", + valueKind: "string", + accessMode: "local-draft", + authority: "renderer-ephemeral", + }], + }, + { + id: "counter-decision", + kind: "control.button", + props: { + label: "Counter", + command: "session_zero.decision.resolve", + action: { decision_id: "decision:one", accept: false }, + captureBindings: ["counter"], + }, + }, + ] }, + }, + }, host, { + provider, + draftStore: new EveBrowserDraftStore(), + commandSink: intent => commands.push(intent), + }); + + const counter = host.querySelector("textarea"); + counter.value = "Contamination fades, but scars remain."; + counter.dispatchEvent(new window.Event("input", { bubbles: true })); + host.querySelector("button").click(); + assert.deepEqual(commands[0].payload.bindings, { + counter: "Contamination fades, but scars remain.", + }); + } finally { + restoreDom(); + } +}); + test("authoritative refresh preserves the renderer-owned command result", () => { const restoreDom = installDom(); try {