fix(simulation): validate semantic click identity (#37808)

This commit is contained in:
Kit Langton 2026-07-19 16:43:53 -04:00 committed by GitHub
commit 71cb419570
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 118 additions and 3 deletions

View file

@ -72,6 +72,58 @@ test("clicks a target at relative coordinates through descendant text", async ()
)
})
test("rejects a semantic click when the live identity does not match", async () => {
await Effect.runPromise(
Effect.scoped(
Effect.gen(function* () {
const renderer = yield* SimulationRenderer.create({})
let clicks = 0
const button = new BoxRenderable(renderer, {
id: "session.permission.action.once",
width: 12,
height: 1,
onMouseUp: () => clicks++,
})
SimulationSemantics.bind(() => ({
instance: "permission-2",
role: "option",
label: "Allow once",
}))(button)
renderer.root.add(button)
const harness = createHarness(renderer)
yield* Effect.promise(() => harness.renderOnce())
const error = yield* execute(harness, {
type: "ui.click",
target: button.num,
x: 1,
y: 0,
semantic: {
id: "session.permission.action.once",
instance: "permission-1",
element: button.num,
},
}).pipe(Effect.flip)
expect(error.message).toContain("semantic click target is stale or unavailable")
expect(clicks).toBe(0)
yield* execute(harness, {
type: "ui.click",
target: button.num,
x: 1,
y: 0,
semantic: {
id: "session.permission.action.once",
instance: "permission-2",
element: button.num,
},
})
expect(clicks).toBe(1)
}),
),
)
})
test("snapshots lazy semantic hierarchy and interaction state", async () => {
await Effect.runPromise(
Effect.scoped(

View file

@ -39,7 +39,12 @@ test("scopes the frontend control server and reports malformed JSON", async () =
protocolVersion: 1,
role: "ui",
server: { name: "opencode", version: expect.any(String) },
capabilities: expect.arrayContaining(["ui.state", "ui.snapshot", "ui.capture"]),
capabilities: expect.arrayContaining([
"ui.state",
"ui.snapshot",
"ui.click.semantic",
"ui.capture",
]),
},
})

View file

@ -21,6 +21,35 @@ test("decodes ui.matches text params", () => {
).toThrow()
})
test("decodes semantic click identity", () => {
expect(
Frontend.decodeRequest({
jsonrpc: "2.0",
id: 1,
method: "ui.click",
params: {
target: 12,
x: 4,
y: 0,
semantic: {
id: "session.permission.action.once",
instance: "permission-1",
element: 12,
},
},
}),
).toMatchObject({
method: "ui.click",
params: {
semantic: {
id: "session.permission.action.once",
instance: "permission-1",
element: 12,
},
},
})
})
test("decodes semantic UI snapshots", () => {
expect(
Frontend.decodeRequest({