Skip to content

Commit 8f6c4f8

Browse files
author
SqlRush
committed
Map DOM mouse button aliases
1 parent 45b3994 commit 8f6c4f8

5 files changed

Lines changed: 74 additions & 0 deletions

File tree

docs/cc-100-roadmap.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,8 @@ M7 补充:prompt history `LogEntry` 读取现在接受 `sessionID`/`session`/`
700700

701701
本轮补充:interaction script mouse payload 现在可从 `wheel`/`mousewheel``scrollUp`/`scrollDown``direction``deltaY` 和旧式 `wheelDelta` 推导 SGR wheel button,录制的 DOM/compact 滚轮事件可直接驱动 viewport 滚动。
702702

703+
本轮补充:interaction script mouse payload 现在接受 DOM `which``buttons`/`buttonState` bitmask,并映射到 SGR left/middle/right button,避免录制脚本的右键/中键被误当成 primary click。
704+
703705
本轮补充:prompt/image history 的 `ImageDimensions` 读取 `width`/`height` 或仅 original 尺寸时,会默认 display 尺寸等于 original 尺寸,避免只有单尺寸字段的 image fixture 丢失 source metadata。
704706

705707
本轮补充:prompt history 的 pasted-content 类型现在会归一化 `inputImage`/`pasted-image`/`input_text`/`pasted-text` 等别名,runtime history 和 stored history 恢复都会映射到规范 `image`/`text`

docs/claude-code-go-rewrite-plan.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ test/parity/ # golden tests against TS/official behavior
234234
- 本轮补充:interaction script key 字段现在接受 DOM-style key event object,可从 `key`/`code``ctrlKey`/`altKey`/`metaKey`/`shiftKey``modifiers` 数组还原现有 key 名,wrapper payload 中的 key event 也可驱动脚本回放。
235235
- 本轮补充:interaction script mouse payload 现在可从 `mouseup``pointerUp``touchend` 等 event type 推导 release 状态;显式 release bool 仍优先生效。
236236
- 本轮补充:interaction script mouse payload 现在可从 `wheel`/`mousewheel``scrollUp`/`scrollDown``direction``deltaY` 和旧式 `wheelDelta` 推导 SGR wheel button,录制的 DOM/compact 滚轮事件可直接驱动 viewport 滚动。
237+
- 本轮补充:interaction script mouse payload 现在接受 DOM `which``buttons`/`buttonState` bitmask,并映射到 SGR left/middle/right button,避免录制脚本的右键/中键被误当成 primary click。
237238
- 本轮补充:嵌套 contract message 接受 `messageId`/`messageID`/`message_id``messageUuid`/`messageUUID`/`message_uuid` 作为自身 ID/UUID 别名,indexed resume 会保留 payload 自带的 nested message id。
238239
- 本轮补充:嵌套 contract message 的 primary `id` 现在接受 JSON number,`LoadTranscript` 和 indexed resume 会保留为字符串 message id。
239240
- 本轮补充:基础 `SessionEntry` JSONL loader 接受 `role`/`entryType`/`messageType`、message ID/UUID、parent ID/UUID 和 `sessionID`/`session`/session UUID 别名,旧 entry 文件可通过 `session.Load` 保留类型、parent 和 session。

docs/first-second-parity-audit.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ M7 progress now includes:
225225
- `internal/tui`: interaction script key fields now accept DOM-style key event objects with `key`/`code`, modifier booleans such as `ctrlKey`/`altKey`/`metaKey`/`shiftKey`, and modifier arrays, including wrapper payloads and modifier-only event filtering.
226226
- `internal/tui`: interaction script mouse payloads now infer release state from event type aliases such as `mouseup`, `pointerUp`, and `touchend`, while explicit release booleans still take precedence.
227227
- `internal/tui`: interaction script mouse payloads now infer SGR wheel buttons from DOM/compact wheel inputs such as `wheel`, `mousewheel`, `scrollUp`/`scrollDown`, `direction`, `deltaY`, and legacy `wheelDelta`, allowing recorded wheel events to drive viewport scrolling.
228+
- `internal/tui`: interaction script mouse payloads now accept DOM `which` and `buttons`/`buttonState` bitmasks and map them to SGR left/middle/right buttons so recorded secondary or auxiliary clicks are not treated as primary clicks.
228229
- `internal/tui`: task runtime now canonicalizes state aliases such as `active`/`in_progress`, `success`/`done`, `error`, and `canceled` before status-line summaries, task dialogs, ordering, cancellation, and scripted task expectations.
229230
- `internal/tui`: scripted task runtime payloads and task expectations now accept adjacent fields such as `taskID`, `jobId`, `runId`, `label`, `displayName`, `phase`, `taskState`, `message`, `currentStep`, `percent`, `percentage`, and numeric/string task IDs or progress values.
230231
- `internal/tui`: permission runtime now treats action aliases such as `Reject`, `deny`, `decline`, `disallow`, and `no` as denied results, `Cancel`/`abort` as cancelled results, and keeps allowed aliases canonical for scripted dialog-result status checks.

internal/tui/script_aliases.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3923,6 +3923,8 @@ func (mouse *ScriptMouse) UnmarshalJSON(data []byte) error {
39233923
mouse.Button = *button
39243924
} else if button, ok := scriptMouseWheelButtonFromFields(fields); ok {
39253925
mouse.Button = button
3926+
} else if button, ok := scriptMouseDOMButtonFromFields(fields); ok {
3927+
mouse.Button = button
39263928
} else if button := intPtrJSONField(fields, "Button", "button", "Btn", "btn", "Code", "code", "Mask", "mask"); button != nil {
39273929
mouse.Button = *button
39283930
}
@@ -3968,6 +3970,30 @@ func scriptMouseWheelButtonFromFields(fields map[string]json.RawMessage) (int, b
39683970
}
39693971
}
39703972

3973+
func scriptMouseDOMButtonFromFields(fields map[string]json.RawMessage) (int, bool) {
3974+
if which := intPtrJSONField(fields, "Which", "which"); which != nil {
3975+
switch *which {
3976+
case 1:
3977+
return 0, true
3978+
case 2:
3979+
return 1, true
3980+
case 3:
3981+
return 2, true
3982+
}
3983+
}
3984+
if buttons := intPtrJSONField(fields, "Buttons", "buttons", "ButtonState", "button_state", "buttonState", "ButtonsMask", "buttons_mask", "buttonsMask"); buttons != nil {
3985+
switch {
3986+
case *buttons&1 != 0:
3987+
return 0, true
3988+
case *buttons&4 != 0:
3989+
return 1, true
3990+
case *buttons&2 != 0:
3991+
return 2, true
3992+
}
3993+
}
3994+
return 0, false
3995+
}
3996+
39713997
func scriptMouseWheelDirection(fields map[string]json.RawMessage) int {
39723998
direction := stringJSONField(fields, "Direction", "direction", "WheelDirection", "wheel_direction", "wheelDirection", "ScrollDirection", "scroll_direction", "scrollDirection")
39733999
switch scriptNormalizeName(direction) {

internal/tui/tui_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5074,6 +5074,25 @@ func TestRunInteractionScriptAcceptsStructuredMouse(t *testing.T) {
50745074
}
50755075
}
50765076

5077+
func TestRunInteractionScriptAcceptsDOMMouseButtonAliases(t *testing.T) {
5078+
steps, err := ParseInteractionScript([]byte(`[
5079+
{"dialog":{"title":"Permission","body":"Allow?","actions":["Allow","Deny"],"id":"perm_dom","kind":"permission"}},
5080+
{"mouseEvent":{"buttons":2,"clientX":13,"clientY":5},"expectNoEvent":true,"expectDialog":{"active":true,"id":"perm_dom"}},
5081+
{"mouseEvent":{"which":1,"clientX":13,"clientY":5},"expectEvent":{"type":"dialog_action","value":"Deny","dialogId":"perm_dom","dialogKind":"permission"}}
5082+
]`))
5083+
if err != nil {
5084+
t.Fatal(err)
5085+
}
5086+
screen := NewREPLScreen(40, 8, nil)
5087+
result, err := RunInteractionScriptChecked(&screen, steps)
5088+
if err != nil {
5089+
t.Fatal(err)
5090+
}
5091+
if len(result.Events) != 1 || result.Events[0].Type != ScreenEventDialogAction || result.Events[0].DialogID != "perm_dom" || result.Events[0].Value != "Deny" {
5092+
t.Fatalf("events = %#v", result.Events)
5093+
}
5094+
}
5095+
50775096
func TestScriptMouseAcceptsCoordinateAliases(t *testing.T) {
50785097
for _, tc := range []struct {
50795098
name string
@@ -5143,6 +5162,31 @@ func TestScriptMouseDerivesWheelButtonFromEventPayload(t *testing.T) {
51435162
}
51445163
}
51455164

5165+
func TestScriptMouseAcceptsDOMButtonAliases(t *testing.T) {
5166+
for _, tc := range []struct {
5167+
name string
5168+
raw string
5169+
button int
5170+
}{
5171+
{name: "which left", raw: `{"which":1,"clientX":13,"clientY":5}`, button: 0},
5172+
{name: "which middle", raw: `{"which":2,"clientX":13,"clientY":5}`, button: 1},
5173+
{name: "which right", raw: `{"which":3,"clientX":13,"clientY":5}`, button: 2},
5174+
{name: "buttons left", raw: `{"buttons":1,"clientX":13,"clientY":5}`, button: 0},
5175+
{name: "buttons right", raw: `{"buttons":2,"clientX":13,"clientY":5}`, button: 2},
5176+
{name: "buttons middle", raw: `{"buttons":4,"clientX":13,"clientY":5}`, button: 1},
5177+
} {
5178+
t.Run(tc.name, func(t *testing.T) {
5179+
var mouse ScriptMouse
5180+
if err := json.Unmarshal([]byte(tc.raw), &mouse); err != nil {
5181+
t.Fatal(err)
5182+
}
5183+
if mouse.Button != tc.button || mouse.X != 13 || mouse.Y != 5 {
5184+
t.Fatalf("mouse = %#v, button want %d", mouse, tc.button)
5185+
}
5186+
})
5187+
}
5188+
}
5189+
51465190
func TestScriptBooleanAliasesAcceptNonStrictValues(t *testing.T) {
51475191
var mouse ScriptMouse
51485192
if err := json.Unmarshal([]byte(`{"button":0,"x":13,"y":5,"mouseUp":"yes"}`), &mouse); err != nil {

0 commit comments

Comments
 (0)