Compose operation and component binding captures

This commit is contained in:
Meta Vens 2026-08-22 12:38:52 +02:00
commit 23eaf32eae
3 changed files with 79 additions and 4 deletions

View file

@ -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)

View file

@ -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,

View file

@ -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 {