fix(mobile): dispatch leading terminal scroll without a timer (#31472)

Dispatch the leading terminal scroll immediately and coalesce subsequent movement behind the outstanding request. Preserve queue expiry, limits and ordering.

Closes #30299.
Adapted from #30311.

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
This commit is contained in:
Neil
2026-10-21 13:42:30 -07:00
committed by GitHub
co-authored by Wooseong Kim
parent 65035bc52c
commit 0b82f88e9f
7 changed files with 829 additions and 716 deletions
@@ -145,6 +145,48 @@
}
}
},
"47c387a75ffc": {
"name": "terminal.send#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "terminal.send"
},
{
"name": "params",
"value": {
"client": {
"id": "device-token-1",
"type": "mobile"
},
"enter": false,
"terminal": "terminal-1",
"text": "\u001b[<64;10;5M"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 16,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"send": {
"accepted": true
}
}
}
}
},
"60ae3b4c26a5": {
"name": "orchestration.workerTerminalUserInput#1",
"ordinal": 3,
@@ -265,15 +307,6 @@
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"terminal.clearBuffer\",\"params\":{\"terminal\":\"terminal-1\"}}"
},
"93092a2f06c5": {
"bucketTokens": 63,
"crash": {
"$rpc": "null"
},
"inFlight": false,
"queuedBytes": "\u001b[<64;10;5M",
"queuedSequences": 1
},
"94ce15ec36dc": {
"name": "orchestration.workerTerminalUserInput#1",
"ordinal": 3,
@@ -383,6 +416,38 @@
}
}
},
"9fa6cd0b7fa0": {
"name": "terminal.send#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "terminal.send"
},
{
"name": "params",
"value": {
"client": {
"id": "device-token-1",
"type": "mobile"
},
"enter": false,
"terminal": "terminal-1",
"text": "\u001b[<64;10;5M"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"aa1058fa6654": {
"name": "orchestration.workerTerminalUserInput#1",
"ordinal": 3,
@@ -443,48 +508,6 @@
"startedAt": 16
}
},
"c9e4131930e5": {
"name": "terminal.send#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "terminal.send"
},
{
"name": "params",
"value": {
"client": {
"id": "device-token-1",
"type": "mobile"
},
"enter": false,
"terminal": "terminal-1",
"text": "\u001b[<64;10;5M"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 16,
"settledAt": 16,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"send": {
"accepted": true
}
}
}
}
},
"caa6ece1bdab": {
"bucketTokens": 63,
"crash": {
@@ -609,38 +632,6 @@
}
}
}
},
"fd81ada317ab": {
"name": "terminal.send#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "terminal.send"
},
{
"name": "params",
"value": {
"client": {
"id": "device-token-1",
"type": "mobile"
},
"enter": false,
"terminal": "terminal-1",
"text": "\u001b[<64;10;5M"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 16
}
}
},
"recording": {
@@ -649,20 +640,20 @@
{
"id": "terminal-gesture-flush-and-clear.prelude:queued",
"observation": {
"sender": [],
"payloads": [],
"sender": ["9fa6cd0b7fa0"],
"payloads": ["8224ccdb2cd8"],
"settlements": {
"mount": "eb79a9b3682a",
"gesture": "eb79a9b3682a"
},
"state": "93092a2f06c5",
"state": "caa6ece1bdab",
"effects": []
}
},
{
"id": "terminal-gesture-flush-and-clear.prelude:flushing",
"observation": {
"sender": ["fd81ada317ab"],
"sender": ["9fa6cd0b7fa0"],
"payloads": ["8224ccdb2cd8"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -675,7 +666,7 @@
{
"id": "terminal-gesture-flush-and-clear.prelude:sent",
"observation": {
"sender": ["c9e4131930e5", "77d8a44e0dcc"],
"sender": ["47c387a75ffc", "77d8a44e0dcc"],
"payloads": ["8224ccdb2cd8", "85a38010bc83"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -688,7 +679,7 @@
{
"id": "terminal-gesture-flush-and-clear.normal:reported",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc"],
"sender": ["47c387a75ffc", "94ce15ec36dc"],
"payloads": ["8224ccdb2cd8", "85a38010bc83"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -701,7 +692,7 @@
{
"id": "terminal-gesture-flush-and-clear.normal:clearing",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc", "afb61324f54a"],
"sender": ["47c387a75ffc", "94ce15ec36dc", "afb61324f54a"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -715,7 +706,7 @@
{
"id": "terminal-gesture-flush-and-clear.normal:cleared",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc", "ebaa6b64425b"],
"sender": ["47c387a75ffc", "94ce15ec36dc", "ebaa6b64425b"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -729,7 +720,7 @@
{
"id": "terminal-gesture-flush-and-clear.result-absent:reported",
"observation": {
"sender": ["c9e4131930e5", "e24bc71ad7f1"],
"sender": ["47c387a75ffc", "e24bc71ad7f1"],
"payloads": ["8224ccdb2cd8", "85a38010bc83"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -742,7 +733,7 @@
{
"id": "terminal-gesture-flush-and-clear.result-absent:clearing",
"observation": {
"sender": ["c9e4131930e5", "e24bc71ad7f1", "afb61324f54a"],
"sender": ["47c387a75ffc", "e24bc71ad7f1", "afb61324f54a"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -756,7 +747,7 @@
{
"id": "terminal-gesture-flush-and-clear.result-absent:cleared",
"observation": {
"sender": ["c9e4131930e5", "e24bc71ad7f1", "ebaa6b64425b"],
"sender": ["47c387a75ffc", "e24bc71ad7f1", "ebaa6b64425b"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -770,7 +761,7 @@
{
"id": "terminal-gesture-flush-and-clear.result-null:reported",
"observation": {
"sender": ["c9e4131930e5", "9c7f27a40956"],
"sender": ["47c387a75ffc", "9c7f27a40956"],
"payloads": ["8224ccdb2cd8", "85a38010bc83"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -783,7 +774,7 @@
{
"id": "terminal-gesture-flush-and-clear.result-null:clearing",
"observation": {
"sender": ["c9e4131930e5", "9c7f27a40956", "afb61324f54a"],
"sender": ["47c387a75ffc", "9c7f27a40956", "afb61324f54a"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -797,7 +788,7 @@
{
"id": "terminal-gesture-flush-and-clear.result-null:cleared",
"observation": {
"sender": ["c9e4131930e5", "9c7f27a40956", "ebaa6b64425b"],
"sender": ["47c387a75ffc", "9c7f27a40956", "ebaa6b64425b"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -811,7 +802,7 @@
{
"id": "terminal-gesture-flush-and-clear.inner-ok-missing:reported",
"observation": {
"sender": ["c9e4131930e5", "0bbc8e9d2e60"],
"sender": ["47c387a75ffc", "0bbc8e9d2e60"],
"payloads": ["8224ccdb2cd8", "85a38010bc83"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -824,7 +815,7 @@
{
"id": "terminal-gesture-flush-and-clear.inner-ok-missing:clearing",
"observation": {
"sender": ["c9e4131930e5", "0bbc8e9d2e60", "afb61324f54a"],
"sender": ["47c387a75ffc", "0bbc8e9d2e60", "afb61324f54a"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -838,7 +829,7 @@
{
"id": "terminal-gesture-flush-and-clear.inner-ok-missing:cleared",
"observation": {
"sender": ["c9e4131930e5", "0bbc8e9d2e60", "ebaa6b64425b"],
"sender": ["47c387a75ffc", "0bbc8e9d2e60", "ebaa6b64425b"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -852,7 +843,7 @@
{
"id": "terminal-gesture-flush-and-clear.inner-false-string-error:reported",
"observation": {
"sender": ["c9e4131930e5", "41eb762c53f0"],
"sender": ["47c387a75ffc", "41eb762c53f0"],
"payloads": ["8224ccdb2cd8", "85a38010bc83"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -865,7 +856,7 @@
{
"id": "terminal-gesture-flush-and-clear.inner-false-string-error:clearing",
"observation": {
"sender": ["c9e4131930e5", "41eb762c53f0", "afb61324f54a"],
"sender": ["47c387a75ffc", "41eb762c53f0", "afb61324f54a"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -879,7 +870,7 @@
{
"id": "terminal-gesture-flush-and-clear.inner-false-string-error:cleared",
"observation": {
"sender": ["c9e4131930e5", "41eb762c53f0", "ebaa6b64425b"],
"sender": ["47c387a75ffc", "41eb762c53f0", "ebaa6b64425b"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -893,7 +884,7 @@
{
"id": "terminal-gesture-flush-and-clear.inner-false-object-error:reported",
"observation": {
"sender": ["c9e4131930e5", "8412fcb9c620"],
"sender": ["47c387a75ffc", "8412fcb9c620"],
"payloads": ["8224ccdb2cd8", "85a38010bc83"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -906,7 +897,7 @@
{
"id": "terminal-gesture-flush-and-clear.inner-false-object-error:clearing",
"observation": {
"sender": ["c9e4131930e5", "8412fcb9c620", "afb61324f54a"],
"sender": ["47c387a75ffc", "8412fcb9c620", "afb61324f54a"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -920,7 +911,7 @@
{
"id": "terminal-gesture-flush-and-clear.inner-false-object-error:cleared",
"observation": {
"sender": ["c9e4131930e5", "8412fcb9c620", "ebaa6b64425b"],
"sender": ["47c387a75ffc", "8412fcb9c620", "ebaa6b64425b"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -934,7 +925,7 @@
{
"id": "terminal-gesture-flush-and-clear.outer-refused:reported",
"observation": {
"sender": ["c9e4131930e5", "dfe8d1bab9c5"],
"sender": ["47c387a75ffc", "dfe8d1bab9c5"],
"payloads": ["8224ccdb2cd8", "85a38010bc83"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -947,7 +938,7 @@
{
"id": "terminal-gesture-flush-and-clear.outer-refused:clearing",
"observation": {
"sender": ["c9e4131930e5", "dfe8d1bab9c5", "afb61324f54a"],
"sender": ["47c387a75ffc", "dfe8d1bab9c5", "afb61324f54a"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -961,7 +952,7 @@
{
"id": "terminal-gesture-flush-and-clear.outer-refused:cleared",
"observation": {
"sender": ["c9e4131930e5", "dfe8d1bab9c5", "ebaa6b64425b"],
"sender": ["47c387a75ffc", "dfe8d1bab9c5", "ebaa6b64425b"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -975,7 +966,7 @@
{
"id": "terminal-gesture-flush-and-clear.outer-refused-no-message:reported",
"observation": {
"sender": ["c9e4131930e5", "9850afd5b555"],
"sender": ["47c387a75ffc", "9850afd5b555"],
"payloads": ["8224ccdb2cd8", "85a38010bc83"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -988,7 +979,7 @@
{
"id": "terminal-gesture-flush-and-clear.outer-refused-no-message:clearing",
"observation": {
"sender": ["c9e4131930e5", "9850afd5b555", "afb61324f54a"],
"sender": ["47c387a75ffc", "9850afd5b555", "afb61324f54a"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -1002,7 +993,7 @@
{
"id": "terminal-gesture-flush-and-clear.outer-refused-no-message:cleared",
"observation": {
"sender": ["c9e4131930e5", "9850afd5b555", "ebaa6b64425b"],
"sender": ["47c387a75ffc", "9850afd5b555", "ebaa6b64425b"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -1016,7 +1007,7 @@
{
"id": "terminal-gesture-flush-and-clear.method-not-found:reported",
"observation": {
"sender": ["c9e4131930e5", "31a1ebf3ac68"],
"sender": ["47c387a75ffc", "31a1ebf3ac68"],
"payloads": ["8224ccdb2cd8", "85a38010bc83"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -1029,7 +1020,7 @@
{
"id": "terminal-gesture-flush-and-clear.method-not-found:clearing",
"observation": {
"sender": ["c9e4131930e5", "31a1ebf3ac68", "afb61324f54a"],
"sender": ["47c387a75ffc", "31a1ebf3ac68", "afb61324f54a"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -1043,7 +1034,7 @@
{
"id": "terminal-gesture-flush-and-clear.method-not-found:cleared",
"observation": {
"sender": ["c9e4131930e5", "31a1ebf3ac68", "ebaa6b64425b"],
"sender": ["47c387a75ffc", "31a1ebf3ac68", "ebaa6b64425b"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -1057,7 +1048,7 @@
{
"id": "terminal-gesture-flush-and-clear.transport-rejection:reported",
"observation": {
"sender": ["c9e4131930e5", "aa1058fa6654"],
"sender": ["47c387a75ffc", "aa1058fa6654"],
"payloads": ["8224ccdb2cd8", "85a38010bc83"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -1070,7 +1061,7 @@
{
"id": "terminal-gesture-flush-and-clear.transport-rejection:clearing",
"observation": {
"sender": ["c9e4131930e5", "aa1058fa6654", "afb61324f54a"],
"sender": ["47c387a75ffc", "aa1058fa6654", "afb61324f54a"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -1084,7 +1075,7 @@
{
"id": "terminal-gesture-flush-and-clear.transport-rejection:cleared",
"observation": {
"sender": ["c9e4131930e5", "aa1058fa6654", "ebaa6b64425b"],
"sender": ["47c387a75ffc", "aa1058fa6654", "ebaa6b64425b"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -1098,7 +1089,7 @@
{
"id": "terminal-gesture-flush-and-clear.transport-rejection-no-message:reported",
"observation": {
"sender": ["c9e4131930e5", "60ae3b4c26a5"],
"sender": ["47c387a75ffc", "60ae3b4c26a5"],
"payloads": ["8224ccdb2cd8", "85a38010bc83"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -1111,7 +1102,7 @@
{
"id": "terminal-gesture-flush-and-clear.transport-rejection-no-message:clearing",
"observation": {
"sender": ["c9e4131930e5", "60ae3b4c26a5", "afb61324f54a"],
"sender": ["47c387a75ffc", "60ae3b4c26a5", "afb61324f54a"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -1125,7 +1116,7 @@
{
"id": "terminal-gesture-flush-and-clear.transport-rejection-no-message:cleared",
"observation": {
"sender": ["c9e4131930e5", "60ae3b4c26a5", "ebaa6b64425b"],
"sender": ["47c387a75ffc", "60ae3b4c26a5", "ebaa6b64425b"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -170,6 +170,48 @@
"message": "Terminal cleared"
}
},
"47c387a75ffc": {
"name": "terminal.send#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "terminal.send"
},
{
"name": "params",
"value": {
"client": {
"id": "device-token-1",
"type": "mobile"
},
"enter": false,
"terminal": "terminal-1",
"text": "\u001b[<64;10;5M"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 16,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"send": {
"accepted": true
}
}
}
}
},
"4dd92d5c81be": {
"name": "terminal.clearBuffer#1",
"ordinal": 5,
@@ -352,15 +394,6 @@
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"terminal.clearBuffer\",\"params\":{\"terminal\":\"terminal-1\"}}"
},
"93092a2f06c5": {
"bucketTokens": 63,
"crash": {
"$rpc": "null"
},
"inFlight": false,
"queuedBytes": "\u001b[<64;10;5M",
"queuedSequences": 1
},
"94ce15ec36dc": {
"name": "orchestration.workerTerminalUserInput#1",
"ordinal": 3,
@@ -397,6 +430,38 @@
}
}
},
"9fa6cd0b7fa0": {
"name": "terminal.send#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "terminal.send"
},
{
"name": "params",
"value": {
"client": {
"id": "device-token-1",
"type": "mobile"
},
"enter": false,
"terminal": "terminal-1",
"text": "\u001b[<64;10;5M"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"a2ee798dd5e4": {
"name": "terminal.clearBuffer#1",
"ordinal": 5,
@@ -490,48 +555,6 @@
"startedAt": 16
}
},
"c9e4131930e5": {
"name": "terminal.send#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "terminal.send"
},
{
"name": "params",
"value": {
"client": {
"id": "device-token-1",
"type": "mobile"
},
"enter": false,
"terminal": "terminal-1",
"text": "\u001b[<64;10;5M"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 16,
"settledAt": 16,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"send": {
"accepted": true
}
}
}
}
},
"caa6ece1bdab": {
"bucketTokens": 63,
"crash": {
@@ -629,38 +652,6 @@
"durationMs": 1500,
"message": "Couldn't clear terminal"
}
},
"fd81ada317ab": {
"name": "terminal.send#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "terminal.send"
},
{
"name": "params",
"value": {
"client": {
"id": "device-token-1",
"type": "mobile"
},
"enter": false,
"terminal": "terminal-1",
"text": "\u001b[<64;10;5M"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 16
}
}
},
"recording": {
@@ -669,20 +660,20 @@
{
"id": "terminal-gesture-flush-and-clear.prelude:queued",
"observation": {
"sender": [],
"payloads": [],
"sender": ["9fa6cd0b7fa0"],
"payloads": ["8224ccdb2cd8"],
"settlements": {
"mount": "eb79a9b3682a",
"gesture": "eb79a9b3682a"
},
"state": "93092a2f06c5",
"state": "caa6ece1bdab",
"effects": []
}
},
{
"id": "terminal-gesture-flush-and-clear.prelude:flushing",
"observation": {
"sender": ["fd81ada317ab"],
"sender": ["9fa6cd0b7fa0"],
"payloads": ["8224ccdb2cd8"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -695,7 +686,7 @@
{
"id": "terminal-gesture-flush-and-clear.prelude:sent",
"observation": {
"sender": ["c9e4131930e5", "77d8a44e0dcc"],
"sender": ["47c387a75ffc", "77d8a44e0dcc"],
"payloads": ["8224ccdb2cd8", "85a38010bc83"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -708,7 +699,7 @@
{
"id": "terminal-gesture-flush-and-clear.prelude:reported",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc"],
"sender": ["47c387a75ffc", "94ce15ec36dc"],
"payloads": ["8224ccdb2cd8", "85a38010bc83"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -721,7 +712,7 @@
{
"id": "terminal-gesture-flush-and-clear.prelude:clearing",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc", "afb61324f54a"],
"sender": ["47c387a75ffc", "94ce15ec36dc", "afb61324f54a"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -735,7 +726,7 @@
{
"id": "terminal-gesture-flush-and-clear.prelude:cleanup",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc", "a2ee798dd5e4"],
"sender": ["47c387a75ffc", "94ce15ec36dc", "a2ee798dd5e4"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -749,7 +740,7 @@
{
"id": "terminal-gesture-flush-and-clear.normal:cleared",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc", "ebaa6b64425b"],
"sender": ["47c387a75ffc", "94ce15ec36dc", "ebaa6b64425b"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -763,7 +754,7 @@
{
"id": "terminal-gesture-flush-and-clear.result-absent:cleared",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc", "18f37862e26f"],
"sender": ["47c387a75ffc", "94ce15ec36dc", "18f37862e26f"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -777,7 +768,7 @@
{
"id": "terminal-gesture-flush-and-clear.result-null:cleared",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc", "4dd92d5c81be"],
"sender": ["47c387a75ffc", "94ce15ec36dc", "4dd92d5c81be"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -791,7 +782,7 @@
{
"id": "terminal-gesture-flush-and-clear.inner-ok-missing:cleared",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc", "86e68519e4fc"],
"sender": ["47c387a75ffc", "94ce15ec36dc", "86e68519e4fc"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -805,7 +796,7 @@
{
"id": "terminal-gesture-flush-and-clear.inner-false-string-error:cleared",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc", "13e21e9d5a19"],
"sender": ["47c387a75ffc", "94ce15ec36dc", "13e21e9d5a19"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -819,7 +810,7 @@
{
"id": "terminal-gesture-flush-and-clear.inner-false-object-error:cleared",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc", "0db561233383"],
"sender": ["47c387a75ffc", "94ce15ec36dc", "0db561233383"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -833,7 +824,7 @@
{
"id": "terminal-gesture-flush-and-clear.outer-refused:cleared",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc", "af47e4950d3c"],
"sender": ["47c387a75ffc", "94ce15ec36dc", "af47e4950d3c"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -847,7 +838,7 @@
{
"id": "terminal-gesture-flush-and-clear.outer-refused-no-message:cleared",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc", "52f4b33bfffe"],
"sender": ["47c387a75ffc", "94ce15ec36dc", "52f4b33bfffe"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -861,7 +852,7 @@
{
"id": "terminal-gesture-flush-and-clear.method-not-found:cleared",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc", "e9ab8b56056e"],
"sender": ["47c387a75ffc", "94ce15ec36dc", "e9ab8b56056e"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -875,7 +866,7 @@
{
"id": "terminal-gesture-flush-and-clear.transport-rejection:cleared",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc", "85797d45b4ec"],
"sender": ["47c387a75ffc", "94ce15ec36dc", "85797d45b4ec"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -889,7 +880,7 @@
{
"id": "terminal-gesture-flush-and-clear.transport-rejection-no-message:cleared",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc", "14826318e3f0"],
"sender": ["47c387a75ffc", "94ce15ec36dc", "14826318e3f0"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -35,6 +35,48 @@
"message": "Terminal cleared"
}
},
"47c387a75ffc": {
"name": "terminal.send#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "terminal.send"
},
{
"name": "params",
"value": {
"client": {
"id": "device-token-1",
"type": "mobile"
},
"enter": false,
"terminal": "terminal-1",
"text": "\u001b[<64;10;5M"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 16,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"send": {
"accepted": true
}
}
}
}
},
"77d8a44e0dcc": {
"name": "orchestration.workerTerminalUserInput#1",
"ordinal": 3,
@@ -82,15 +124,6 @@
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"terminal.clearBuffer\",\"params\":{\"terminal\":\"terminal-1\"}}"
},
"93092a2f06c5": {
"bucketTokens": 63,
"crash": {
"$rpc": "null"
},
"inFlight": false,
"queuedBytes": "\u001b[<64;10;5M",
"queuedSequences": 1
},
"94ce15ec36dc": {
"name": "orchestration.workerTerminalUserInput#1",
"ordinal": 3,
@@ -127,33 +160,7 @@
}
}
},
"afb61324f54a": {
"name": "terminal.clearBuffer#1",
"ordinal": 5,
"args": [
{
"name": "method",
"value": "terminal.clearBuffer"
},
{
"name": "params",
"value": {
"terminal": "terminal-1"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 16
}
},
"c9e4131930e5": {
"9fa6cd0b7fa0": {
"name": "terminal.send#1",
"ordinal": 1,
"args": [
@@ -181,18 +188,34 @@
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 16,
"settledAt": 16,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"send": {
"accepted": true
}
"status": "pending",
"startedAt": 0
}
},
"afb61324f54a": {
"name": "terminal.clearBuffer#1",
"ordinal": 5,
"args": [
{
"name": "method",
"value": "terminal.clearBuffer"
},
{
"name": "params",
"value": {
"terminal": "terminal-1"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 16
}
},
"caa6ece1bdab": {
@@ -249,38 +272,6 @@
}
}
}
},
"fd81ada317ab": {
"name": "terminal.send#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "terminal.send"
},
{
"name": "params",
"value": {
"client": {
"id": "device-token-1",
"type": "mobile"
},
"enter": false,
"terminal": "terminal-1",
"text": "\u001b[<64;10;5M"
}
},
{
"name": "options",
"value": {
"failWhenDisconnected": true
}
}
],
"settlement": {
"status": "pending",
"startedAt": 16
}
}
},
"recording": {
@@ -289,20 +280,20 @@
{
"id": "queued",
"observation": {
"sender": [],
"payloads": [],
"sender": ["9fa6cd0b7fa0"],
"payloads": ["8224ccdb2cd8"],
"settlements": {
"mount": "eb79a9b3682a",
"gesture": "eb79a9b3682a"
},
"state": "93092a2f06c5",
"state": "caa6ece1bdab",
"effects": []
}
},
{
"id": "flushing",
"observation": {
"sender": ["fd81ada317ab"],
"sender": ["9fa6cd0b7fa0"],
"payloads": ["8224ccdb2cd8"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -315,7 +306,7 @@
{
"id": "sent",
"observation": {
"sender": ["c9e4131930e5", "77d8a44e0dcc"],
"sender": ["47c387a75ffc", "77d8a44e0dcc"],
"payloads": ["8224ccdb2cd8", "85a38010bc83"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -328,7 +319,7 @@
{
"id": "reported",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc"],
"sender": ["47c387a75ffc", "94ce15ec36dc"],
"payloads": ["8224ccdb2cd8", "85a38010bc83"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -341,7 +332,7 @@
{
"id": "clearing",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc", "afb61324f54a"],
"sender": ["47c387a75ffc", "94ce15ec36dc", "afb61324f54a"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -355,7 +346,7 @@
{
"id": "cleared",
"observation": {
"sender": ["c9e4131930e5", "94ce15ec36dc", "ebaa6b64425b"],
"sender": ["47c387a75ffc", "94ce15ec36dc", "ebaa6b64425b"],
"payloads": ["8224ccdb2cd8", "85a38010bc83", "8716af94ca80"],
"settlements": {
"mount": "eb79a9b3682a",
@@ -12,7 +12,6 @@ export const MOBILE_SESSION_STATUS_LABELS: Record<ConnectionState, string> = {
export const TERMINAL_GESTURE_INPUT_BUCKET_CAPACITY = 64
export const TERMINAL_GESTURE_INPUT_REFILL_PER_SECOND = 120
export const TERMINAL_GESTURE_INPUT_FLUSH_DELAY_MS = 16
export const TERMINAL_GESTURE_INPUT_MAX_PENDING_SEQUENCES = 32
export const TERMINAL_GESTURE_INPUT_MAX_QUEUE_AGE_MS = 250
@@ -0,0 +1,152 @@
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
import { createElement } from 'react'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { useMobileSessionTerminalInput } from './use-mobile-session-terminal-input'
import { terminalInputSend } from '../terminal/mobile-terminal-operations'
import type { ConnectionState } from '../transport/types'
vi.mock('../terminal/mobile-terminal-operations', () => ({
terminalInputSend: {
request: vi.fn(),
interpret: (response: { result: { send: { accepted: boolean } } }) =>
response.result.send.accepted
},
terminalBufferClear: {}
}))
vi.mock('../terminal/worker-terminal-takeover-report', () => ({
reportWorkerTerminalUserInput: vi.fn()
}))
const handle = 'term-gesture'
const up = '\x1b[A'
const down = '\x1b[B'
const ref = <T>(current: T) => ({ current })
const request = vi.mocked(terminalInputSend.request)
const renderers: ReactTestRenderer[] = []
const accepted: Awaited<ReturnType<typeof terminalInputSend.request>> = {
id: 'synthetic-rpc',
ok: true,
result: { send: { accepted: true } }
}
function fixture() {
const client = {}
const scope = {
client,
clientRef: ref<object | null>(client),
connState: 'connected',
connStateRef: ref<ConnectionState>('connected'),
activeHandle: handle,
activeHandleRef: ref<string | null>(handle),
activeSessionTabTypeRef: ref<string | null>('terminal'),
ptyModesRef: ref(new Map([[handle, { altScreen: true }]])),
terminalGestureInputBucketsRef: ref(new Map()),
terminalGestureInputQueuesRef: ref(new Map()),
terminalGestureInputInFlightRef: ref(new Set()),
deviceTokenRef: ref('synthetic-device')
}
let actions!: ReturnType<typeof useMobileSessionTerminalInput>
function Harness() {
actions = useMobileSessionTerminalInput(
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: this fixture supplies every field reached by the gesture path; unrelated controller capabilities are not invoked.
scope as unknown as Parameters<typeof useMobileSessionTerminalInput>[0]
)
return null
}
act(() => {
renderers.push(create(createElement(Harness)))
})
return { actions, scope }
}
function holdFirstRequest() {
let release!: () => void
const pending = new Promise<typeof accepted>((resolve) => {
release = () => resolve(accepted)
})
request.mockReturnValueOnce(pending)
return release
}
async function settle() {
await vi.advanceTimersByTimeAsync(0)
}
beforeEach(() => {
vi.useFakeTimers()
request.mockReset()
request.mockResolvedValue(accepted)
})
afterEach(() => {
act(() => renderers.splice(0).forEach((renderer) => renderer.unmount()))
vi.clearAllTimers()
vi.useRealTimers()
})
it('sends the first scroll input without waiting for a timer', async () => {
const { actions } = fixture()
await actions.handleTerminalInput(handle, up)
expect(request).toHaveBeenCalledTimes(1)
expect(request).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ terminal: handle, text: up, enter: false }),
expect.anything()
)
})
it('keeps one request in flight and drains later movement in order after its acknowledgement', async () => {
const release = holdFirstRequest()
const { actions } = fixture()
await actions.handleTerminalInput(handle, up)
await actions.handleTerminalInput(handle, down)
await actions.handleTerminalInput(handle, up)
expect(request).toHaveBeenCalledTimes(1)
await vi.advanceTimersByTimeAsync(16)
expect(request).toHaveBeenCalledTimes(1)
release()
await settle()
expect(request).toHaveBeenCalledTimes(2)
expect(request.mock.calls.map((call) => call[1])).toEqual([
expect.objectContaining({ text: up }),
expect.objectContaining({ text: down + up })
])
})
it.each(['disconnected', 'inactive', 'expired'] as const)(
'drops queued movement that becomes %s before the preceding request settles',
async (reason) => {
const release = holdFirstRequest()
const { actions, scope } = fixture()
await actions.handleTerminalInput(handle, up)
await actions.handleTerminalInput(handle, down)
if (reason === 'disconnected') {
scope.connStateRef.current = 'reconnecting'
}
if (reason === 'inactive') {
scope.activeHandleRef.current = 'another-terminal'
}
if (reason === 'expired') {
await vi.advanceTimersByTimeAsync(251)
}
release()
await settle()
expect(request).toHaveBeenCalledTimes(1)
expect(scope.terminalGestureInputQueuesRef.current.size).toBe(0)
}
)
it('retains gesture validation and the per-sequence rate limit', async () => {
const release = holdFirstRequest()
const { actions } = fixture()
await actions.handleTerminalInput(handle, 'plain text')
await actions.handleTerminalInput('another-terminal', up)
expect(request).not.toHaveBeenCalled()
await actions.handleTerminalInput(handle, up.repeat(32))
await actions.handleTerminalInput(handle, down.repeat(32))
await actions.handleTerminalInput(handle, up)
expect(request).toHaveBeenCalledTimes(1)
release()
await settle()
expect(request).toHaveBeenCalledTimes(2)
expect(request.mock.calls[1]?.[1]).toEqual(expect.objectContaining({ text: down.repeat(32) }))
})
@@ -14,7 +14,6 @@ import { countTerminalGestureInputSequences } from '../terminal/terminal-gesture
import {
isGestureMouseTrackingMode,
TERMINAL_GESTURE_INPUT_BUCKET_CAPACITY,
TERMINAL_GESTURE_INPUT_FLUSH_DELAY_MS,
TERMINAL_GESTURE_INPUT_MAX_PENDING_SEQUENCES,
TERMINAL_GESTURE_INPUT_MAX_QUEUE_AGE_MS,
TERMINAL_GESTURE_INPUT_REFILL_PER_SECOND
@@ -154,6 +153,9 @@ export function useMobileSessionTerminalInput(scope: MobileSessionFileActionsMod
current.bytes += bytes
current.sequenceCount += sequenceCount
current.lastUpdatedMs = now
if (!terminalGestureInputInFlightRef.current.has(handle)) {
void flushTerminalGestureInput(handle)
}
return
}
@@ -168,10 +170,6 @@ export function useMobileSessionTerminalInput(scope: MobileSessionFileActionsMod
current.bytes += bytes
current.sequenceCount += sequenceCount
current.lastUpdatedMs = now
current.timer = setTimeout(() => {
current.timer = null
void flushTerminalGestureInput(handle)
}, TERMINAL_GESTURE_INPUT_FLUSH_DELAY_MS)
return
}
}
@@ -182,11 +180,11 @@ export function useMobileSessionTerminalInput(scope: MobileSessionFileActionsMod
timer: null,
lastUpdatedMs: now
}
queued.timer = setTimeout(() => {
queued.timer = null
void flushTerminalGestureInput(handle)
}, TERMINAL_GESTURE_INPUT_FLUSH_DELAY_MS)
terminalGestureInputQueuesRef.current.set(handle, queued)
// Send the leading edge now; an in-flight request's acknowledgement drains later movement.
if (!terminalGestureInputInFlightRef.current.has(handle)) {
void flushTerminalGestureInput(handle)
}
},
[flushTerminalGestureInput]
)