diff --git a/mobile/rpc-foundation/goldens/matrix-git.changes-load-git.branchcompare-1.json b/mobile/rpc-foundation/goldens/matrix-git.changes-load-git.branchcompare-1.json
index ecd62d820e2..1cf8b9ecc75 100644
--- a/mobile/rpc-foundation/goldens/matrix-git.changes-load-git.branchcompare-1.json
+++ b/mobile/rpc-foundation/goldens/matrix-git.changes-load-git.branchcompare-1.json
@@ -163,11 +163,6 @@
}
}
},
- "2bf960db8adf": {
- "name": "git.status#1",
- "ordinal": 4,
- "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"git.status\",\"params\":{\"worktree\":\"id:repo42::/p\",\"includeLineStats\":false}}"
- },
"2cb17031e16a": {
"branchCompareState": {
"kind": "error",
@@ -381,6 +376,62 @@
"startedAt": 0
}
},
+ "6a5580a606dc": {
+ "name": "git.status#1",
+ "ordinal": 2,
+ "args": [
+ {
+ "name": "method",
+ "value": "git.status"
+ },
+ {
+ "name": "params",
+ "value": {
+ "worktree": "id:repo42::/p"
+ }
+ },
+ {
+ "name": "options",
+ "value": {
+ "$rpc": "absent"
+ }
+ }
+ ],
+ "settlement": {
+ "status": "fulfilled",
+ "startedAt": 0,
+ "settledAt": 0,
+ "value": {
+ "id": "frame-2",
+ "ok": true,
+ "result": {
+ "branch": "feature",
+ "entries": [
+ {
+ "added": 4,
+ "area": "unstaged",
+ "path": "src/app.ts",
+ "removed": 1,
+ "status": "modified"
+ },
+ {
+ "area": "staged",
+ "oldPath": "src/older.ts",
+ "path": "src/old.ts",
+ "status": "renamed"
+ }
+ ],
+ "head": "head-oid",
+ "upstreamStatus": {
+ "ahead": 1,
+ "behind": 0,
+ "hasUpstream": true,
+ "upstreamName": "origin/feature"
+ }
+ }
+ }
+ }
+ },
"6d220bb7b42a": {
"name": "worktree.show#1",
"ordinal": 5,
@@ -407,6 +458,32 @@
"startedAt": 0
}
},
+ "72e251ae531b": {
+ "name": "git.status#1",
+ "ordinal": 2,
+ "args": [
+ {
+ "name": "method",
+ "value": "git.status"
+ },
+ {
+ "name": "params",
+ "value": {
+ "worktree": "id:repo42::/p"
+ }
+ },
+ {
+ "name": "options",
+ "value": {
+ "$rpc": "absent"
+ }
+ }
+ ],
+ "settlement": {
+ "status": "pending",
+ "startedAt": 0
+ }
+ },
"830aa65db9d0": {
"branchCompareState": {
"kind": "idle"
@@ -645,6 +722,11 @@
}
}
},
+ "973ac78137f2": {
+ "name": "git.status#1",
+ "ordinal": 4,
+ "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"git.status\",\"params\":{\"worktree\":\"id:repo42::/p\"}}"
+ },
"9b643a151075": {
"name": "git.branchCompare#1",
"ordinal": 11,
@@ -729,95 +811,11 @@
}
}
},
- "aa8c5813ba8a": {
- "name": "git.status#1",
- "ordinal": 2,
- "args": [
- {
- "name": "method",
- "value": "git.status"
- },
- {
- "name": "params",
- "value": {
- "includeLineStats": false,
- "worktree": "id:repo42::/p"
- }
- },
- {
- "name": "options",
- "value": {
- "$rpc": "absent"
- }
- }
- ],
- "settlement": {
- "status": "fulfilled",
- "startedAt": 0,
- "settledAt": 0,
- "value": {
- "id": "frame-2",
- "ok": true,
- "result": {
- "branch": "feature",
- "entries": [
- {
- "added": 4,
- "area": "unstaged",
- "path": "src/app.ts",
- "removed": 1,
- "status": "modified"
- },
- {
- "area": "staged",
- "oldPath": "src/older.ts",
- "path": "src/old.ts",
- "status": "renamed"
- }
- ],
- "head": "head-oid",
- "upstreamStatus": {
- "ahead": 1,
- "behind": 0,
- "hasUpstream": true,
- "upstreamName": "origin/feature"
- }
- }
- }
- }
- },
"ad2a2a1df35b": {
"name": "worktree.show#1",
"ordinal": 9,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"worktree.show\",\"params\":{\"worktree\":\"id:repo42::/p\"}}"
},
- "ae602a492660": {
- "name": "git.status#1",
- "ordinal": 2,
- "args": [
- {
- "name": "method",
- "value": "git.status"
- },
- {
- "name": "params",
- "value": {
- "includeLineStats": false,
- "worktree": "id:repo42::/p"
- }
- },
- {
- "name": "options",
- "value": {
- "$rpc": "absent"
- }
- }
- ],
- "settlement": {
- "status": "pending",
- "startedAt": 0
- }
- },
"c2212473f0c1": {
"name": "repo.list#1",
"ordinal": 10,
@@ -987,8 +985,8 @@
{
"id": "sc-changes-loaded.prelude:status-pending",
"observation": {
- "sender": ["e3973bb12da1", "ae602a492660"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "72e251ae531b"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -999,8 +997,8 @@
{
"id": "sc-changes-loaded.prelude:compare-pending",
"observation": {
- "sender": ["e3973bb12da1", "aa8c5813ba8a", "6d220bb7b42a", "587de4c7f7fc"],
- "payloads": ["f648b042d810", "2bf960db8adf", "ad2a2a1df35b", "c2212473f0c1"],
+ "sender": ["e3973bb12da1", "6a5580a606dc", "6d220bb7b42a", "587de4c7f7fc"],
+ "payloads": ["f648b042d810", "973ac78137f2", "ad2a2a1df35b", "c2212473f0c1"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -1013,14 +1011,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"9536c968b88a",
"35cc9e325d08"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -1037,14 +1035,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"9536c968b88a",
"893813584d8b"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -1061,14 +1059,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"9536c968b88a",
"55d62e9e07b4"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -1085,14 +1083,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"9536c968b88a",
"2b6257fd1172"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -1109,14 +1107,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"9536c968b88a",
"1650cbe07807"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -1133,14 +1131,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"9536c968b88a",
"a76331b97c21"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -1157,14 +1155,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"9536c968b88a",
"967f1b227050"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -1181,14 +1179,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"9536c968b88a",
"00fe64c7bc78"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -1205,14 +1203,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"9536c968b88a",
"c75999ea5a6c"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -1229,14 +1227,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"9536c968b88a",
"47205e8686ad"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -1253,14 +1251,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"9536c968b88a",
"9b643a151075"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
diff --git a/mobile/rpc-foundation/goldens/matrix-git.changes-load-git.status-1.json b/mobile/rpc-foundation/goldens/matrix-git.changes-load-git.status-1.json
index aade53b10dc..21a989a692d 100644
--- a/mobile/rpc-foundation/goldens/matrix-git.changes-load-git.status-1.json
+++ b/mobile/rpc-foundation/goldens/matrix-git.changes-load-git.status-1.json
@@ -4,6 +4,75 @@
"family": "git.changes-load",
"namedDeltas": [],
"values": {
+ "04f6ea51fb83": {
+ "name": "git.status#1",
+ "ordinal": 2,
+ "args": [
+ {
+ "name": "method",
+ "value": "git.status"
+ },
+ {
+ "name": "params",
+ "value": {
+ "worktree": "id:repo42::/p"
+ }
+ },
+ {
+ "name": "options",
+ "value": {
+ "$rpc": "absent"
+ }
+ }
+ ],
+ "settlement": {
+ "status": "fulfilled",
+ "startedAt": 0,
+ "settledAt": 0,
+ "value": {
+ "id": "frame-2",
+ "ok": true,
+ "result": {
+ "error": "refused"
+ }
+ }
+ }
+ },
+ "0a645f4241a6": {
+ "name": "git.status#1",
+ "ordinal": 2,
+ "args": [
+ {
+ "name": "method",
+ "value": "git.status"
+ },
+ {
+ "name": "params",
+ "value": {
+ "worktree": "id:repo42::/p"
+ }
+ },
+ {
+ "name": "options",
+ "value": {
+ "$rpc": "absent"
+ }
+ }
+ ],
+ "settlement": {
+ "status": "fulfilled",
+ "startedAt": 0,
+ "settledAt": 0,
+ "value": {
+ "error": {
+ "code": "method_not_found",
+ "message": "Unknown method"
+ },
+ "id": "frame-2",
+ "ok": false
+ }
+ }
+ },
"19bb6c737352": {
"branchCompareState": {
"kind": "idle"
@@ -65,6 +134,40 @@
}
}
},
+ "200efb648720": {
+ "name": "git.status#1",
+ "ordinal": 2,
+ "args": [
+ {
+ "name": "method",
+ "value": "git.status"
+ },
+ {
+ "name": "params",
+ "value": {
+ "worktree": "id:repo42::/p"
+ }
+ },
+ {
+ "name": "options",
+ "value": {
+ "$rpc": "absent"
+ }
+ }
+ ],
+ "settlement": {
+ "status": "fulfilled",
+ "startedAt": 0,
+ "settledAt": 0,
+ "value": {
+ "id": "frame-2",
+ "ok": true,
+ "result": {
+ "$rpc": "null"
+ }
+ }
+ }
+ },
"21ab22282843": {
"branchCompareState": {
"kind": "idle"
@@ -83,11 +186,6 @@
"message": "Update Orca desktop to use Source Control on mobile."
}
},
- "2bf960db8adf": {
- "name": "git.status#1",
- "ordinal": 4,
- "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"git.status\",\"params\":{\"worktree\":\"id:repo42::/p\",\"includeLineStats\":false}}"
- },
"35cc9e325d08": {
"name": "git.branchCompare#1",
"ordinal": 11,
@@ -148,6 +246,41 @@
"message": ""
}
},
+ "4d11fcc4af02": {
+ "name": "git.status#1",
+ "ordinal": 2,
+ "args": [
+ {
+ "name": "method",
+ "value": "git.status"
+ },
+ {
+ "name": "params",
+ "value": {
+ "worktree": "id:repo42::/p"
+ }
+ },
+ {
+ "name": "options",
+ "value": {
+ "$rpc": "absent"
+ }
+ }
+ ],
+ "settlement": {
+ "status": "fulfilled",
+ "startedAt": 0,
+ "settledAt": 0,
+ "value": {
+ "error": {
+ "code": "refused",
+ "message": ""
+ },
+ "id": "frame-2",
+ "ok": false
+ }
+ }
+ },
"587de4c7f7fc": {
"name": "repo.list#1",
"ordinal": 6,
@@ -183,7 +316,7 @@
"message": "outer refused"
}
},
- "60f6e2d48ef7": {
+ "6a5580a606dc": {
"name": "git.status#1",
"ordinal": 2,
"args": [
@@ -194,7 +327,6 @@
{
"name": "params",
"value": {
- "includeLineStats": false,
"worktree": "id:repo42::/p"
}
},
@@ -206,13 +338,37 @@
}
],
"settlement": {
- "status": "rejected",
+ "status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
- "error": {
- "category": "Error",
- "message": "transport failure",
- "isRpcDeliveryUnknown": true
+ "value": {
+ "id": "frame-2",
+ "ok": true,
+ "result": {
+ "branch": "feature",
+ "entries": [
+ {
+ "added": 4,
+ "area": "unstaged",
+ "path": "src/app.ts",
+ "removed": 1,
+ "status": "modified"
+ },
+ {
+ "area": "staged",
+ "oldPath": "src/older.ts",
+ "path": "src/old.ts",
+ "status": "renamed"
+ }
+ ],
+ "head": "head-oid",
+ "upstreamStatus": {
+ "ahead": 1,
+ "behind": 0,
+ "hasUpstream": true,
+ "upstreamName": "origin/feature"
+ }
+ }
}
}
},
@@ -242,16 +398,7 @@
"startedAt": 0
}
},
- "74b7ff5e1818": {
- "branchCompareState": {
- "kind": "idle"
- },
- "screenState": {
- "kind": "error",
- "message": "transport failure"
- }
- },
- "885a150a2ecd": {
+ "72e251ae531b": {
"name": "git.status#1",
"ordinal": 2,
"args": [
@@ -262,7 +409,6 @@
{
"name": "params",
"value": {
- "includeLineStats": false,
"worktree": "id:repo42::/p"
}
},
@@ -274,20 +420,20 @@
}
],
"settlement": {
- "status": "fulfilled",
- "startedAt": 0,
- "settledAt": 0,
- "value": {
- "error": {
- "code": "method_not_found",
- "message": "Unknown method"
- },
- "id": "frame-2",
- "ok": false
- }
+ "status": "pending",
+ "startedAt": 0
}
},
- "8af3432e9742": {
+ "74b7ff5e1818": {
+ "branchCompareState": {
+ "kind": "idle"
+ },
+ "screenState": {
+ "kind": "error",
+ "message": "transport failure"
+ }
+ },
+ "7bb6d5b22b62": {
"name": "git.status#1",
"ordinal": 2,
"args": [
@@ -298,7 +444,6 @@
{
"name": "params",
"value": {
- "includeLineStats": false,
"worktree": "id:repo42::/p"
}
},
@@ -317,7 +462,9 @@
"id": "frame-2",
"ok": true,
"result": {
- "error": "inner refused",
+ "error": {
+ "message": "inner refused"
+ },
"ok": false
}
}
@@ -392,6 +539,37 @@
}
}
},
+ "91448c30aa24": {
+ "name": "git.status#1",
+ "ordinal": 2,
+ "args": [
+ {
+ "name": "method",
+ "value": "git.status"
+ },
+ {
+ "name": "params",
+ "value": {
+ "worktree": "id:repo42::/p"
+ }
+ },
+ {
+ "name": "options",
+ "value": {
+ "$rpc": "absent"
+ }
+ }
+ ],
+ "settlement": {
+ "status": "fulfilled",
+ "startedAt": 0,
+ "settledAt": 0,
+ "value": {
+ "id": "frame-2",
+ "ok": true
+ }
+ }
+ },
"9536c968b88a": {
"name": "repo.list#1",
"ordinal": 6,
@@ -426,7 +604,12 @@
}
}
},
- "9cedf6bac284": {
+ "973ac78137f2": {
+ "name": "git.status#1",
+ "ordinal": 4,
+ "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"git.status\",\"params\":{\"worktree\":\"id:repo42::/p\"}}"
+ },
+ "9809f1b5d628": {
"name": "git.status#1",
"ordinal": 2,
"args": [
@@ -437,7 +620,6 @@
{
"name": "params",
"value": {
- "includeLineStats": false,
"worktree": "id:repo42::/p"
}
},
@@ -449,16 +631,13 @@
}
],
"settlement": {
- "status": "fulfilled",
+ "status": "rejected",
"startedAt": 0,
"settledAt": 0,
- "value": {
- "error": {
- "code": "refused",
- "message": ""
- },
- "id": "frame-2",
- "ok": false
+ "error": {
+ "category": "Error",
+ "message": "",
+ "isRpcDeliveryUnknown": true
}
}
},
@@ -470,47 +649,12 @@
"kind": "loading"
}
},
- "a0e97f94a377": {
- "name": "git.status#1",
- "ordinal": 2,
- "args": [
- {
- "name": "method",
- "value": "git.status"
- },
- {
- "name": "params",
- "value": {
- "includeLineStats": false,
- "worktree": "id:repo42::/p"
- }
- },
- {
- "name": "options",
- "value": {
- "$rpc": "absent"
- }
- }
- ],
- "settlement": {
- "status": "fulfilled",
- "startedAt": 0,
- "settledAt": 0,
- "value": {
- "id": "frame-2",
- "ok": true,
- "result": {
- "$rpc": "null"
- }
- }
- }
- },
"a6482f604002": {
"name": "source-control.status-load-success",
"ordinal": 8,
"value": {}
},
- "aa8c5813ba8a": {
+ "aae82d534914": {
"name": "git.status#1",
"ordinal": 2,
"args": [
@@ -521,7 +665,6 @@
{
"name": "params",
"value": {
- "includeLineStats": false,
"worktree": "id:repo42::/p"
}
},
@@ -540,29 +683,8 @@
"id": "frame-2",
"ok": true,
"result": {
- "branch": "feature",
- "entries": [
- {
- "added": 4,
- "area": "unstaged",
- "path": "src/app.ts",
- "removed": 1,
- "status": "modified"
- },
- {
- "area": "staged",
- "oldPath": "src/older.ts",
- "path": "src/old.ts",
- "status": "renamed"
- }
- ],
- "head": "head-oid",
- "upstreamStatus": {
- "ahead": 1,
- "behind": 0,
- "hasUpstream": true,
- "upstreamName": "origin/feature"
- }
+ "error": "inner refused",
+ "ok": false
}
}
}
@@ -572,7 +694,7 @@
"ordinal": 9,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"worktree.show\",\"params\":{\"worktree\":\"id:repo42::/p\"}}"
},
- "ae602a492660": {
+ "c212210ceca6": {
"name": "git.status#1",
"ordinal": 2,
"args": [
@@ -583,139 +705,6 @@
{
"name": "params",
"value": {
- "includeLineStats": false,
- "worktree": "id:repo42::/p"
- }
- },
- {
- "name": "options",
- "value": {
- "$rpc": "absent"
- }
- }
- ],
- "settlement": {
- "status": "pending",
- "startedAt": 0
- }
- },
- "b0d4cb96a6f3": {
- "name": "git.status#1",
- "ordinal": 2,
- "args": [
- {
- "name": "method",
- "value": "git.status"
- },
- {
- "name": "params",
- "value": {
- "includeLineStats": false,
- "worktree": "id:repo42::/p"
- }
- },
- {
- "name": "options",
- "value": {
- "$rpc": "absent"
- }
- }
- ],
- "settlement": {
- "status": "fulfilled",
- "startedAt": 0,
- "settledAt": 0,
- "value": {
- "id": "frame-2",
- "ok": true
- }
- }
- },
- "b660ecdc7911": {
- "name": "git.status#1",
- "ordinal": 2,
- "args": [
- {
- "name": "method",
- "value": "git.status"
- },
- {
- "name": "params",
- "value": {
- "includeLineStats": false,
- "worktree": "id:repo42::/p"
- }
- },
- {
- "name": "options",
- "value": {
- "$rpc": "absent"
- }
- }
- ],
- "settlement": {
- "status": "fulfilled",
- "startedAt": 0,
- "settledAt": 0,
- "value": {
- "id": "frame-2",
- "ok": true,
- "result": {
- "error": "refused"
- }
- }
- }
- },
- "bbdf2a03af16": {
- "name": "git.status#1",
- "ordinal": 2,
- "args": [
- {
- "name": "method",
- "value": "git.status"
- },
- {
- "name": "params",
- "value": {
- "includeLineStats": false,
- "worktree": "id:repo42::/p"
- }
- },
- {
- "name": "options",
- "value": {
- "$rpc": "absent"
- }
- }
- ],
- "settlement": {
- "status": "fulfilled",
- "startedAt": 0,
- "settledAt": 0,
- "value": {
- "id": "frame-2",
- "ok": true,
- "result": {
- "error": {
- "message": "inner refused"
- },
- "ok": false
- }
- }
- }
- },
- "c1923dfd79bf": {
- "name": "git.status#1",
- "ordinal": 2,
- "args": [
- {
- "name": "method",
- "value": "git.status"
- },
- {
- "name": "params",
- "value": {
- "includeLineStats": false,
"worktree": "id:repo42::/p"
}
},
@@ -754,6 +743,38 @@
}
}
},
+ "d858ce00f4f0": {
+ "name": "git.status#1",
+ "ordinal": 2,
+ "args": [
+ {
+ "name": "method",
+ "value": "git.status"
+ },
+ {
+ "name": "params",
+ "value": {
+ "worktree": "id:repo42::/p"
+ }
+ },
+ {
+ "name": "options",
+ "value": {
+ "$rpc": "absent"
+ }
+ }
+ ],
+ "settlement": {
+ "status": "rejected",
+ "startedAt": 0,
+ "settledAt": 0,
+ "error": {
+ "category": "Error",
+ "message": "transport failure",
+ "isRpcDeliveryUnknown": true
+ }
+ }
+ },
"e2d7409ece1c": {
"name": "git.branchCompare#1",
"ordinal": 12,
@@ -793,39 +814,6 @@
"$rpc": "undefined"
}
},
- "f5e9af0a8858": {
- "name": "git.status#1",
- "ordinal": 2,
- "args": [
- {
- "name": "method",
- "value": "git.status"
- },
- {
- "name": "params",
- "value": {
- "includeLineStats": false,
- "worktree": "id:repo42::/p"
- }
- },
- {
- "name": "options",
- "value": {
- "$rpc": "absent"
- }
- }
- ],
- "settlement": {
- "status": "rejected",
- "startedAt": 0,
- "settledAt": 0,
- "error": {
- "category": "Error",
- "message": "",
- "isRpcDeliveryUnknown": true
- }
- }
- },
"f648b042d810": {
"name": "settings.get#1",
"ordinal": 3,
@@ -838,8 +826,8 @@
{
"id": "sc-changes-loaded.prelude:status-pending",
"observation": {
- "sender": ["e3973bb12da1", "ae602a492660"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "72e251ae531b"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -850,8 +838,8 @@
{
"id": "sc-changes-loaded.normal:compare-pending",
"observation": {
- "sender": ["e3973bb12da1", "aa8c5813ba8a", "6d220bb7b42a", "587de4c7f7fc"],
- "payloads": ["f648b042d810", "2bf960db8adf", "ad2a2a1df35b", "c2212473f0c1"],
+ "sender": ["e3973bb12da1", "6a5580a606dc", "6d220bb7b42a", "587de4c7f7fc"],
+ "payloads": ["f648b042d810", "973ac78137f2", "ad2a2a1df35b", "c2212473f0c1"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -864,14 +852,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"9536c968b88a",
"35cc9e325d08"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -886,8 +874,8 @@
{
"id": "sc-changes-loaded.result-absent:compare-pending",
"observation": {
- "sender": ["e3973bb12da1", "b0d4cb96a6f3"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "91448c30aa24"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -898,8 +886,8 @@
{
"id": "sc-changes-loaded.result-absent:settled",
"observation": {
- "sender": ["e3973bb12da1", "b0d4cb96a6f3"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "91448c30aa24"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -910,8 +898,8 @@
{
"id": "sc-changes-loaded.result-null:compare-pending",
"observation": {
- "sender": ["e3973bb12da1", "a0e97f94a377"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "200efb648720"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -922,8 +910,8 @@
{
"id": "sc-changes-loaded.result-null:settled",
"observation": {
- "sender": ["e3973bb12da1", "a0e97f94a377"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "200efb648720"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -934,8 +922,8 @@
{
"id": "sc-changes-loaded.inner-ok-missing:compare-pending",
"observation": {
- "sender": ["e3973bb12da1", "b660ecdc7911"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "04f6ea51fb83"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -946,8 +934,8 @@
{
"id": "sc-changes-loaded.inner-ok-missing:settled",
"observation": {
- "sender": ["e3973bb12da1", "b660ecdc7911"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "04f6ea51fb83"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -958,8 +946,8 @@
{
"id": "sc-changes-loaded.inner-false-string-error:compare-pending",
"observation": {
- "sender": ["e3973bb12da1", "8af3432e9742"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "aae82d534914"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -970,8 +958,8 @@
{
"id": "sc-changes-loaded.inner-false-string-error:settled",
"observation": {
- "sender": ["e3973bb12da1", "8af3432e9742"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "aae82d534914"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -982,8 +970,8 @@
{
"id": "sc-changes-loaded.inner-false-object-error:compare-pending",
"observation": {
- "sender": ["e3973bb12da1", "bbdf2a03af16"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "7bb6d5b22b62"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -994,8 +982,8 @@
{
"id": "sc-changes-loaded.inner-false-object-error:settled",
"observation": {
- "sender": ["e3973bb12da1", "bbdf2a03af16"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "7bb6d5b22b62"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -1006,8 +994,8 @@
{
"id": "sc-changes-loaded.outer-refused:compare-pending",
"observation": {
- "sender": ["e3973bb12da1", "c1923dfd79bf"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "c212210ceca6"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -1018,8 +1006,8 @@
{
"id": "sc-changes-loaded.outer-refused:settled",
"observation": {
- "sender": ["e3973bb12da1", "c1923dfd79bf"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "c212210ceca6"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -1030,8 +1018,8 @@
{
"id": "sc-changes-loaded.outer-refused-no-message:compare-pending",
"observation": {
- "sender": ["e3973bb12da1", "9cedf6bac284"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "4d11fcc4af02"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -1042,8 +1030,8 @@
{
"id": "sc-changes-loaded.outer-refused-no-message:settled",
"observation": {
- "sender": ["e3973bb12da1", "9cedf6bac284"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "4d11fcc4af02"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -1054,8 +1042,8 @@
{
"id": "sc-changes-loaded.method-not-found:compare-pending",
"observation": {
- "sender": ["e3973bb12da1", "885a150a2ecd"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "0a645f4241a6"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -1066,8 +1054,8 @@
{
"id": "sc-changes-loaded.method-not-found:settled",
"observation": {
- "sender": ["e3973bb12da1", "885a150a2ecd"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "0a645f4241a6"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -1078,8 +1066,8 @@
{
"id": "sc-changes-loaded.transport-rejection:compare-pending",
"observation": {
- "sender": ["e3973bb12da1", "60f6e2d48ef7"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "d858ce00f4f0"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -1090,8 +1078,8 @@
{
"id": "sc-changes-loaded.transport-rejection:settled",
"observation": {
- "sender": ["e3973bb12da1", "60f6e2d48ef7"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "d858ce00f4f0"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -1102,8 +1090,8 @@
{
"id": "sc-changes-loaded.transport-rejection-no-message:compare-pending",
"observation": {
- "sender": ["e3973bb12da1", "f5e9af0a8858"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "9809f1b5d628"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -1114,8 +1102,8 @@
{
"id": "sc-changes-loaded.transport-rejection-no-message:settled",
"observation": {
- "sender": ["e3973bb12da1", "f5e9af0a8858"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "9809f1b5d628"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
diff --git a/mobile/rpc-foundation/goldens/matrix-git.changes-load-repo.list-1.json b/mobile/rpc-foundation/goldens/matrix-git.changes-load-repo.list-1.json
index 82cc3f5246d..7bc156d150a 100644
--- a/mobile/rpc-foundation/goldens/matrix-git.changes-load-repo.list-1.json
+++ b/mobile/rpc-foundation/goldens/matrix-git.changes-load-repo.list-1.json
@@ -159,11 +159,6 @@
}
}
},
- "2bf960db8adf": {
- "name": "git.status#1",
- "ordinal": 4,
- "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"git.status\",\"params\":{\"worktree\":\"id:repo42::/p\",\"includeLineStats\":false}}"
- },
"35cc9e325d08": {
"name": "git.branchCompare#1",
"ordinal": 11,
@@ -273,6 +268,62 @@
"startedAt": 0
}
},
+ "6a5580a606dc": {
+ "name": "git.status#1",
+ "ordinal": 2,
+ "args": [
+ {
+ "name": "method",
+ "value": "git.status"
+ },
+ {
+ "name": "params",
+ "value": {
+ "worktree": "id:repo42::/p"
+ }
+ },
+ {
+ "name": "options",
+ "value": {
+ "$rpc": "absent"
+ }
+ }
+ ],
+ "settlement": {
+ "status": "fulfilled",
+ "startedAt": 0,
+ "settledAt": 0,
+ "value": {
+ "id": "frame-2",
+ "ok": true,
+ "result": {
+ "branch": "feature",
+ "entries": [
+ {
+ "added": 4,
+ "area": "unstaged",
+ "path": "src/app.ts",
+ "removed": 1,
+ "status": "modified"
+ },
+ {
+ "area": "staged",
+ "oldPath": "src/older.ts",
+ "path": "src/old.ts",
+ "status": "renamed"
+ }
+ ],
+ "head": "head-oid",
+ "upstreamStatus": {
+ "ahead": 1,
+ "behind": 0,
+ "hasUpstream": true,
+ "upstreamName": "origin/feature"
+ }
+ }
+ }
+ }
+ },
"6b974d7f9507": {
"name": "repo.list#1",
"ordinal": 6,
@@ -371,6 +422,32 @@
"startedAt": 0
}
},
+ "72e251ae531b": {
+ "name": "git.status#1",
+ "ordinal": 2,
+ "args": [
+ {
+ "name": "method",
+ "value": "git.status"
+ },
+ {
+ "name": "params",
+ "value": {
+ "worktree": "id:repo42::/p"
+ }
+ },
+ {
+ "name": "options",
+ "value": {
+ "$rpc": "absent"
+ }
+ }
+ ],
+ "settlement": {
+ "status": "pending",
+ "startedAt": 0
+ }
+ },
"84280af3ca36": {
"name": "repo.list#1",
"ordinal": 6,
@@ -541,6 +618,11 @@
}
}
},
+ "973ac78137f2": {
+ "name": "git.status#1",
+ "ordinal": 4,
+ "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"git.status\",\"params\":{\"worktree\":\"id:repo42::/p\"}}"
+ },
"a0e527bdf205": {
"branchCompareState": {
"kind": "idle"
@@ -554,95 +636,11 @@
"ordinal": 8,
"value": {}
},
- "aa8c5813ba8a": {
- "name": "git.status#1",
- "ordinal": 2,
- "args": [
- {
- "name": "method",
- "value": "git.status"
- },
- {
- "name": "params",
- "value": {
- "includeLineStats": false,
- "worktree": "id:repo42::/p"
- }
- },
- {
- "name": "options",
- "value": {
- "$rpc": "absent"
- }
- }
- ],
- "settlement": {
- "status": "fulfilled",
- "startedAt": 0,
- "settledAt": 0,
- "value": {
- "id": "frame-2",
- "ok": true,
- "result": {
- "branch": "feature",
- "entries": [
- {
- "added": 4,
- "area": "unstaged",
- "path": "src/app.ts",
- "removed": 1,
- "status": "modified"
- },
- {
- "area": "staged",
- "oldPath": "src/older.ts",
- "path": "src/old.ts",
- "status": "renamed"
- }
- ],
- "head": "head-oid",
- "upstreamStatus": {
- "ahead": 1,
- "behind": 0,
- "hasUpstream": true,
- "upstreamName": "origin/feature"
- }
- }
- }
- }
- },
"ad2a2a1df35b": {
"name": "worktree.show#1",
"ordinal": 9,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"worktree.show\",\"params\":{\"worktree\":\"id:repo42::/p\"}}"
},
- "ae602a492660": {
- "name": "git.status#1",
- "ordinal": 2,
- "args": [
- {
- "name": "method",
- "value": "git.status"
- },
- {
- "name": "params",
- "value": {
- "includeLineStats": false,
- "worktree": "id:repo42::/p"
- }
- },
- {
- "name": "options",
- "value": {
- "$rpc": "absent"
- }
- }
- ],
- "settlement": {
- "status": "pending",
- "startedAt": 0
- }
- },
"bc1d0b381f25": {
"name": "repo.list#1",
"ordinal": 6,
@@ -774,8 +772,8 @@
{
"id": "sc-changes-loaded.prelude:status-pending",
"observation": {
- "sender": ["e3973bb12da1", "ae602a492660"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "72e251ae531b"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -786,8 +784,8 @@
{
"id": "sc-changes-loaded.prelude:compare-pending",
"observation": {
- "sender": ["e3973bb12da1", "aa8c5813ba8a", "6d220bb7b42a", "587de4c7f7fc"],
- "payloads": ["f648b042d810", "2bf960db8adf", "ad2a2a1df35b", "c2212473f0c1"],
+ "sender": ["e3973bb12da1", "6a5580a606dc", "6d220bb7b42a", "587de4c7f7fc"],
+ "payloads": ["f648b042d810", "973ac78137f2", "ad2a2a1df35b", "c2212473f0c1"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -800,14 +798,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"9536c968b88a",
"35cc9e325d08"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -824,14 +822,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"bc1d0b381f25",
"35cc9e325d08"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -848,14 +846,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"204b32e0c54e",
"35cc9e325d08"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -872,14 +870,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"042d01f3263f",
"35cc9e325d08"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -896,14 +894,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"26929519c12a",
"35cc9e325d08"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -920,14 +918,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"6b974d7f9507",
"35cc9e325d08"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -944,14 +942,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"877dcd616e1f",
"35cc9e325d08"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -968,14 +966,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"6b9dc51089ad",
"35cc9e325d08"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -992,14 +990,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"c7a8ab38e974",
"35cc9e325d08"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -1016,14 +1014,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"84280af3ca36",
"35cc9e325d08"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -1040,14 +1038,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"49197a7eac62",
"35cc9e325d08"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
diff --git a/mobile/rpc-foundation/goldens/matrix-git.changes-load-worktree.show-1.json b/mobile/rpc-foundation/goldens/matrix-git.changes-load-worktree.show-1.json
index 4d4f3b48e14..2157784b934 100644
--- a/mobile/rpc-foundation/goldens/matrix-git.changes-load-worktree.show-1.json
+++ b/mobile/rpc-foundation/goldens/matrix-git.changes-load-worktree.show-1.json
@@ -56,11 +56,6 @@
}
}
},
- "2bf960db8adf": {
- "name": "git.status#1",
- "ordinal": 4,
- "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"git.status\",\"params\":{\"worktree\":\"id:repo42::/p\",\"includeLineStats\":false}}"
- },
"35cc9e325d08": {
"name": "git.branchCompare#1",
"ordinal": 11,
@@ -209,6 +204,62 @@
"startedAt": 0
}
},
+ "6a5580a606dc": {
+ "name": "git.status#1",
+ "ordinal": 2,
+ "args": [
+ {
+ "name": "method",
+ "value": "git.status"
+ },
+ {
+ "name": "params",
+ "value": {
+ "worktree": "id:repo42::/p"
+ }
+ },
+ {
+ "name": "options",
+ "value": {
+ "$rpc": "absent"
+ }
+ }
+ ],
+ "settlement": {
+ "status": "fulfilled",
+ "startedAt": 0,
+ "settledAt": 0,
+ "value": {
+ "id": "frame-2",
+ "ok": true,
+ "result": {
+ "branch": "feature",
+ "entries": [
+ {
+ "added": 4,
+ "area": "unstaged",
+ "path": "src/app.ts",
+ "removed": 1,
+ "status": "modified"
+ },
+ {
+ "area": "staged",
+ "oldPath": "src/older.ts",
+ "path": "src/old.ts",
+ "status": "renamed"
+ }
+ ],
+ "head": "head-oid",
+ "upstreamStatus": {
+ "ahead": 1,
+ "behind": 0,
+ "hasUpstream": true,
+ "upstreamName": "origin/feature"
+ }
+ }
+ }
+ }
+ },
"6d220bb7b42a": {
"name": "worktree.show#1",
"ordinal": 5,
@@ -235,6 +286,32 @@
"startedAt": 0
}
},
+ "72e251ae531b": {
+ "name": "git.status#1",
+ "ordinal": 2,
+ "args": [
+ {
+ "name": "method",
+ "value": "git.status"
+ },
+ {
+ "name": "params",
+ "value": {
+ "worktree": "id:repo42::/p"
+ }
+ },
+ {
+ "name": "options",
+ "value": {
+ "$rpc": "absent"
+ }
+ }
+ ],
+ "settlement": {
+ "status": "pending",
+ "startedAt": 0
+ }
+ },
"7440046c1348": {
"name": "worktree.show#1",
"ordinal": 5,
@@ -441,6 +518,11 @@
}
}
},
+ "973ac78137f2": {
+ "name": "git.status#1",
+ "ordinal": 4,
+ "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"git.status\",\"params\":{\"worktree\":\"id:repo42::/p\"}}"
+ },
"a0e527bdf205": {
"branchCompareState": {
"kind": "idle"
@@ -454,63 +536,6 @@
"ordinal": 8,
"value": {}
},
- "aa8c5813ba8a": {
- "name": "git.status#1",
- "ordinal": 2,
- "args": [
- {
- "name": "method",
- "value": "git.status"
- },
- {
- "name": "params",
- "value": {
- "includeLineStats": false,
- "worktree": "id:repo42::/p"
- }
- },
- {
- "name": "options",
- "value": {
- "$rpc": "absent"
- }
- }
- ],
- "settlement": {
- "status": "fulfilled",
- "startedAt": 0,
- "settledAt": 0,
- "value": {
- "id": "frame-2",
- "ok": true,
- "result": {
- "branch": "feature",
- "entries": [
- {
- "added": 4,
- "area": "unstaged",
- "path": "src/app.ts",
- "removed": 1,
- "status": "modified"
- },
- {
- "area": "staged",
- "oldPath": "src/older.ts",
- "path": "src/old.ts",
- "status": "renamed"
- }
- ],
- "head": "head-oid",
- "upstreamStatus": {
- "ahead": 1,
- "behind": 0,
- "hasUpstream": true,
- "upstreamName": "origin/feature"
- }
- }
- }
- }
- },
"ad2a2a1df35b": {
"name": "worktree.show#1",
"ordinal": 9,
@@ -551,33 +576,6 @@
}
}
},
- "ae602a492660": {
- "name": "git.status#1",
- "ordinal": 2,
- "args": [
- {
- "name": "method",
- "value": "git.status"
- },
- {
- "name": "params",
- "value": {
- "includeLineStats": false,
- "worktree": "id:repo42::/p"
- }
- },
- {
- "name": "options",
- "value": {
- "$rpc": "absent"
- }
- }
- ],
- "settlement": {
- "status": "pending",
- "startedAt": 0
- }
- },
"b7a249bf3627": {
"name": "worktree.show#1",
"ordinal": 5,
@@ -805,8 +803,8 @@
{
"id": "sc-changes-loaded.prelude:status-pending",
"observation": {
- "sender": ["e3973bb12da1", "ae602a492660"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "72e251ae531b"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -817,8 +815,8 @@
{
"id": "sc-changes-loaded.prelude:compare-pending",
"observation": {
- "sender": ["e3973bb12da1", "aa8c5813ba8a", "6d220bb7b42a", "587de4c7f7fc"],
- "payloads": ["f648b042d810", "2bf960db8adf", "ad2a2a1df35b", "c2212473f0c1"],
+ "sender": ["e3973bb12da1", "6a5580a606dc", "6d220bb7b42a", "587de4c7f7fc"],
+ "payloads": ["f648b042d810", "973ac78137f2", "ad2a2a1df35b", "c2212473f0c1"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -831,14 +829,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"9536c968b88a",
"35cc9e325d08"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
@@ -855,14 +853,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"54cc20e9cfbc",
"9536c968b88a",
"f1bc948c977d"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"3ca74b9dc048"
@@ -879,14 +877,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8cf0ca1cc85d",
"9536c968b88a",
"f1bc948c977d"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"3ca74b9dc048"
@@ -903,14 +901,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"bb66129d9391",
"9536c968b88a",
"f1bc948c977d"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"3ca74b9dc048"
@@ -927,14 +925,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"ecfe4b0cfe19",
"9536c968b88a",
"f1bc948c977d"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"3ca74b9dc048"
@@ -951,14 +949,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"7440046c1348",
"9536c968b88a",
"f1bc948c977d"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"3ca74b9dc048"
@@ -975,14 +973,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"ae3822726c00",
"9536c968b88a",
"f1bc948c977d"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"3ca74b9dc048"
@@ -999,14 +997,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"4fadbf6b1661",
"9536c968b88a",
"f1bc948c977d"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"3ca74b9dc048"
@@ -1023,14 +1021,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"b7a249bf3627",
"9536c968b88a",
"f1bc948c977d"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"3ca74b9dc048"
@@ -1047,14 +1045,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"d65d39bc05d3",
"9536c968b88a",
"f1bc948c977d"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"3ca74b9dc048"
@@ -1071,14 +1069,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"7689d7b4e8e2",
"9536c968b88a",
"f1bc948c977d"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"3ca74b9dc048"
diff --git a/mobile/rpc-foundation/goldens/sc-changes-loaded.json b/mobile/rpc-foundation/goldens/sc-changes-loaded.json
index 0365526da70..0e95a43e419 100644
--- a/mobile/rpc-foundation/goldens/sc-changes-loaded.json
+++ b/mobile/rpc-foundation/goldens/sc-changes-loaded.json
@@ -56,11 +56,6 @@
}
}
},
- "2bf960db8adf": {
- "name": "git.status#1",
- "ordinal": 4,
- "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"git.status\",\"params\":{\"worktree\":\"id:repo42::/p\",\"includeLineStats\":false}}"
- },
"35cc9e325d08": {
"name": "git.branchCompare#1",
"ordinal": 11,
@@ -138,6 +133,62 @@
"startedAt": 0
}
},
+ "6a5580a606dc": {
+ "name": "git.status#1",
+ "ordinal": 2,
+ "args": [
+ {
+ "name": "method",
+ "value": "git.status"
+ },
+ {
+ "name": "params",
+ "value": {
+ "worktree": "id:repo42::/p"
+ }
+ },
+ {
+ "name": "options",
+ "value": {
+ "$rpc": "absent"
+ }
+ }
+ ],
+ "settlement": {
+ "status": "fulfilled",
+ "startedAt": 0,
+ "settledAt": 0,
+ "value": {
+ "id": "frame-2",
+ "ok": true,
+ "result": {
+ "branch": "feature",
+ "entries": [
+ {
+ "added": 4,
+ "area": "unstaged",
+ "path": "src/app.ts",
+ "removed": 1,
+ "status": "modified"
+ },
+ {
+ "area": "staged",
+ "oldPath": "src/older.ts",
+ "path": "src/old.ts",
+ "status": "renamed"
+ }
+ ],
+ "head": "head-oid",
+ "upstreamStatus": {
+ "ahead": 1,
+ "behind": 0,
+ "hasUpstream": true,
+ "upstreamName": "origin/feature"
+ }
+ }
+ }
+ }
+ },
"6d220bb7b42a": {
"name": "worktree.show#1",
"ordinal": 5,
@@ -164,6 +215,32 @@
"startedAt": 0
}
},
+ "72e251ae531b": {
+ "name": "git.status#1",
+ "ordinal": 2,
+ "args": [
+ {
+ "name": "method",
+ "value": "git.status"
+ },
+ {
+ "name": "params",
+ "value": {
+ "worktree": "id:repo42::/p"
+ }
+ },
+ {
+ "name": "options",
+ "value": {
+ "$rpc": "absent"
+ }
+ }
+ ],
+ "settlement": {
+ "status": "pending",
+ "startedAt": 0
+ }
+ },
"8c8e528ca222": {
"name": "worktree.show#1",
"ordinal": 5,
@@ -267,6 +344,11 @@
}
}
},
+ "973ac78137f2": {
+ "name": "git.status#1",
+ "ordinal": 4,
+ "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"git.status\",\"params\":{\"worktree\":\"id:repo42::/p\"}}"
+ },
"a0e527bdf205": {
"branchCompareState": {
"kind": "idle"
@@ -280,95 +362,11 @@
"ordinal": 8,
"value": {}
},
- "aa8c5813ba8a": {
- "name": "git.status#1",
- "ordinal": 2,
- "args": [
- {
- "name": "method",
- "value": "git.status"
- },
- {
- "name": "params",
- "value": {
- "includeLineStats": false,
- "worktree": "id:repo42::/p"
- }
- },
- {
- "name": "options",
- "value": {
- "$rpc": "absent"
- }
- }
- ],
- "settlement": {
- "status": "fulfilled",
- "startedAt": 0,
- "settledAt": 0,
- "value": {
- "id": "frame-2",
- "ok": true,
- "result": {
- "branch": "feature",
- "entries": [
- {
- "added": 4,
- "area": "unstaged",
- "path": "src/app.ts",
- "removed": 1,
- "status": "modified"
- },
- {
- "area": "staged",
- "oldPath": "src/older.ts",
- "path": "src/old.ts",
- "status": "renamed"
- }
- ],
- "head": "head-oid",
- "upstreamStatus": {
- "ahead": 1,
- "behind": 0,
- "hasUpstream": true,
- "upstreamName": "origin/feature"
- }
- }
- }
- }
- },
"ad2a2a1df35b": {
"name": "worktree.show#1",
"ordinal": 9,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"worktree.show\",\"params\":{\"worktree\":\"id:repo42::/p\"}}"
},
- "ae602a492660": {
- "name": "git.status#1",
- "ordinal": 2,
- "args": [
- {
- "name": "method",
- "value": "git.status"
- },
- {
- "name": "params",
- "value": {
- "includeLineStats": false,
- "worktree": "id:repo42::/p"
- }
- },
- {
- "name": "options",
- "value": {
- "$rpc": "absent"
- }
- }
- ],
- "settlement": {
- "status": "pending",
- "startedAt": 0
- }
- },
"c2212473f0c1": {
"name": "repo.list#1",
"ordinal": 10,
@@ -434,8 +432,8 @@
{
"id": "status-pending",
"observation": {
- "sender": ["e3973bb12da1", "ae602a492660"],
- "payloads": ["f648b042d810", "2bf960db8adf"],
+ "sender": ["e3973bb12da1", "72e251ae531b"],
+ "payloads": ["f648b042d810", "973ac78137f2"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -446,8 +444,8 @@
{
"id": "compare-pending",
"observation": {
- "sender": ["e3973bb12da1", "aa8c5813ba8a", "6d220bb7b42a", "587de4c7f7fc"],
- "payloads": ["f648b042d810", "2bf960db8adf", "ad2a2a1df35b", "c2212473f0c1"],
+ "sender": ["e3973bb12da1", "6a5580a606dc", "6d220bb7b42a", "587de4c7f7fc"],
+ "payloads": ["f648b042d810", "973ac78137f2", "ad2a2a1df35b", "c2212473f0c1"],
"settlements": {
"mount": "eb79a9b3682a"
},
@@ -460,14 +458,14 @@
"observation": {
"sender": [
"e3973bb12da1",
- "aa8c5813ba8a",
+ "6a5580a606dc",
"8c8e528ca222",
"9536c968b88a",
"35cc9e325d08"
],
"payloads": [
"f648b042d810",
- "2bf960db8adf",
+ "973ac78137f2",
"ad2a2a1df35b",
"c2212473f0c1",
"e2d7409ece1c"
diff --git a/mobile/rpc-foundation/pilot-scenarios.json b/mobile/rpc-foundation/pilot-scenarios.json
index 2376579cee6..b429d9d6347 100644
--- a/mobile/rpc-foundation/pilot-scenarios.json
+++ b/mobile/rpc-foundation/pilot-scenarios.json
@@ -20348,8 +20348,7 @@
{
"complete": "git.status#1",
"params": {
- "worktree": "id:repo42::/p",
- "includeLineStats": false
+ "worktree": "id:repo42::/p"
},
"reply": {
"ok": true,
diff --git a/mobile/src/components/nav-bar/PullDownMenu.tsx b/mobile/src/components/nav-bar/PullDownMenu.tsx
index 7e8f95d77d2..029dc1aadc6 100644
--- a/mobile/src/components/nav-bar/PullDownMenu.tsx
+++ b/mobile/src/components/nav-bar/PullDownMenu.tsx
@@ -5,6 +5,7 @@ import {
Modal,
Platform,
Pressable,
+ ScrollView,
StyleSheet,
Text,
View,
@@ -13,7 +14,7 @@ import {
import { Check } from 'lucide-react-native'
import { useReducedMotionEnabled } from '../../platform/use-reduced-motion'
import { useBackClaim } from '../../navigation/use-back-claim'
-import { colors, radii, spacing } from '../../theme/mobile-theme'
+import { colors, radii, spacing, typography } from '../../theme/mobile-theme'
import { themedStyleSheet } from '../../theme/themed-styles'
import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../../i18n/use-mobile-translation'
@@ -153,6 +154,10 @@ export function PullDownMenu({ visible, anchorRef, groups, onClose }: PullDownMe
style={[
styles.menu,
anchor,
+ // Long menus scroll instead of running off a short (landscape) screen.
+ {
+ maxHeight: window.height - ('top' in anchor ? anchor.top : anchor.bottom) - EDGE_INSET
+ },
{
opacity: appear.interpolate({
inputRange: [0, 1],
@@ -173,44 +178,54 @@ export function PullDownMenu({ visible, anchorRef, groups, onClose }: PullDownMe
]}
accessibilityRole="menu"
>
- {shown.map((group, groupIndex) => (
- 0 && styles.groupGap}>
- {group.map((action, index) => {
- const Icon = action.icon
- const tint = action.destructive ? colors.statusRed : colors.textPrimary
- return (
- [
- styles.item,
- index > 0 && styles.itemDivider,
- pressed && styles.itemPressed,
- action.disabled && styles.itemDisabled
- ]}
- disabled={action.disabled || action.loading}
- onPress={() => press(action)}
- accessibilityRole="menuitem"
- accessibilityLabel={action.label}
- accessibilityHint={action.hint}
- accessibilityState={
- action.checked === undefined ? undefined : { checked: action.checked }
- }
- >
-
- {action.label}
-
- {action.checked ? (
-
- ) : action.renderIcon ? (
- action.renderIcon()
- ) : (
- Icon &&
- )}
-
- )
- })}
-
- ))}
+
+ {shown.map((group, groupIndex) => (
+ 0 && styles.groupGap}>
+ {group.map((action, index) => {
+ const Icon = action.icon
+ const tint = action.destructive ? colors.statusRed : colors.textPrimary
+ return (
+ [
+ styles.item,
+ index > 0 && styles.itemDivider,
+ pressed && styles.itemPressed,
+ action.disabled && styles.itemDisabled
+ ]}
+ disabled={action.disabled || action.loading}
+ onPress={() => press(action)}
+ accessibilityRole="menuitem"
+ accessibilityLabel={action.label}
+ accessibilityHint={action.hint}
+ accessibilityState={
+ action.checked === undefined ? undefined : { checked: action.checked }
+ }
+ >
+
+
+ {action.label}
+
+ {/* No tooltips on a phone: a disabled item says why, a destructive one what it replaces. */}
+ {(action.disabled || action.destructive) && action.hint ? (
+
+ {action.hint}
+
+ ) : null}
+
+ {action.checked ? (
+
+ ) : action.renderIcon ? (
+ action.renderIcon()
+ ) : (
+ Icon &&
+ )}
+
+ )
+ })}
+
+ ))}
+
) : null}
@@ -254,8 +269,16 @@ const styles = themedStyleSheet(() => ({
itemDisabled: {
opacity: 0.4
},
+ labelBlock: {
+ flexShrink: 1,
+ gap: 2
+ },
label: {
flexShrink: 1,
fontSize: 15.5
+ },
+ hint: {
+ color: colors.textSecondary,
+ fontSize: typography.metaSize
}
}))
diff --git a/mobile/src/i18n/en-runtime-required.json b/mobile/src/i18n/en-runtime-required.json
index 7957760ec78..82edfb8b3c1 100644
--- a/mobile/src/i18n/en-runtime-required.json
+++ b/mobile/src/i18n/en-runtime-required.json
@@ -307,11 +307,11 @@
"pushCommits_one": "Push {{count}} commit.",
"pushCommits_other": "Push {{count}} commits."
},
- "branchCompare": {
+ "changesHeader": {
"commits_one": "{{count}} commit",
"commits_other": "{{count}} commits",
- "files_one": "{{count}} file",
- "files_other": "{{count}} files"
+ "filesNeedDecision_one": "{{count}} file needs a decision before you can finish.",
+ "filesNeedDecision_other": "{{count}} files need a decision before you can finish."
},
"commitDetail": {
"files_one": "{{count}} file",
@@ -355,10 +355,6 @@
"checksRunning_one": "{{count}} check still running",
"checksRunning_other": "{{count}} checks still running"
},
- "prChip": {
- "openWithComments_one": "Pull request #{{number}}, {{state}}, {{checks}}, {{count}} unresolved comments. Open pull request.",
- "openWithComments_other": "Pull request #{{number}}, {{state}}, {{checks}}, {{count}} unresolved comments. Open pull request."
- },
"primaryAction": {
"commitFiles_one": "Commit {{count}} file",
"commitFiles_other": "Commit {{count}} files",
diff --git a/mobile/src/i18n/locales/de.json b/mobile/src/i18n/locales/de.json
index 5886d85bd67..c4438e3b46d 100644
--- a/mobile/src/i18n/locales/de.json
+++ b/mobile/src/i18n/locales/de.json
@@ -2670,7 +2670,6 @@
"checkingReviewStatus": "Review-Status wird geprüft.",
"checkoutBeforePublish": "Vor dem Veröffentlichen von Commits einen Branch auschecken.",
"commitStagedChanges": "Vorgemerkte Änderungen committen.",
- "commits": "Commits",
"committing": "Commit läuft.",
"createPr": "PR erstellen",
"enterCommitMessage": "Zum Committen eine Commit-Nachricht eingeben.",
@@ -2698,23 +2697,11 @@
"resolveFailed": "Branch-Basis konnte nicht ermittelt werden."
},
"branchCard": {
- "branchCount": "{{count}} auf Branch",
- "changedCount": "{{count}} geändert",
- "cherryPickState": "Cherry-pick",
- "mergeState": "Merge",
- "rebaseState": "Rebase",
- "stagedCount": "{{count}} vorgemerkt",
"completingMerge": "Wird abgeschlossen…",
"completeMerge": "Merge abschließen"
},
"branchCompare": {
- "commits_one": "{{count}} Commit",
- "commits_other": "{{count}} Commits",
- "files_one": "{{count}} Datei",
- "files_other": "{{count}} Dateien",
- "sectionTitle": "Auf Branch committet",
- "summary": "{{files}} – gegenüber {{baseRef}}",
- "summaryWithCommits": "{{files}} – {{commits}} – gegenüber {{baseRef}}"
+ "sectionTitle": "Auf Branch committet"
},
"branchCompareOutcome": {
"baseBranchUnavailable": "Basis-Branch für den Vergleich konnte nicht ermittelt werden.",
@@ -2773,11 +2760,8 @@
"emptyMessage": "Der Worktree ist sauber.",
"emptyTitle": "Keine lokalen Änderungen",
"generateMessage": "Commit-Nachricht mit KI generieren",
- "openActions": "Aktionen der Quellcodeverwaltung öffnen",
"reconnecting": "Verbindung zum Desktop wird wiederhergestellt…",
- "regenerateHint": "Nachricht leeren, um sie neu zu generieren.",
- "stageAll": "Alle vormerken",
- "unstageAll": "Alle Vormerkungen aufheben"
+ "regenerateHint": "Nachricht leeren, um sie neu zu generieren."
},
"createPrAction": {
"checkingStatus": "Review-Status wird geprüft…",
@@ -2806,20 +2790,15 @@
"titleUnpublishedForcePushCount_one": "Push von {{count}} Branch-Commit mit Lease erzwingen und bei Bedarf einen Upstream festlegen.",
"titleUnpublishedForcePushCount_other": "Push von {{count}} Branch-Commits mit Lease erzwingen und bei Bedarf einen Upstream festlegen."
},
- "fileList": {
- "committedSection": "Auf Branch committet"
- },
"fileRows": {
"addedByThem": "Von ihnen hinzugefügt",
"addedByUs": "Von uns hinzugefügt",
"bothAdded": "Beidseitig hinzugefügt",
"bothDeleted": "Beidseitig gelöscht",
"bothModified": "Beidseitig geändert",
- "committedSection": "Auf Branch committet",
"committedUnavailable": "Committete Änderungen nicht verfügbar.",
"deletedByThem": "Von ihnen gelöscht",
"deletedByUs": "Von uns gelöscht",
- "discardFile": "Änderungen an {{path}} verwerfen",
"loadingCommitted": "Committete Änderungen werden geladen…",
"openChangedFile": "Geänderte Datei {{path}} öffnen",
"openCommittedChange": "Committete Änderung an {{path}} öffnen",
@@ -2852,9 +2831,7 @@
"waitingForDesktop": "Warten auf Desktop…"
},
"gitStatus": {
- "changes": "Änderungen",
- "conflicts": "Konflikte",
- "stagedChanges": "Vorgemerkte Änderungen"
+ "conflicts": "Konflikte"
},
"header": {
"backToSession": "Zurück zur Sitzung",
@@ -2922,7 +2899,6 @@
"waitingForDesktop": "Warten auf den Desktop…"
},
"modals": {
- "actionsTitle": "Quellcodeverwaltung",
"currentBranch": "aktuell",
"discardButton": "Verwerfen",
"discardConfirmMessage": "Änderungen an „{{path}}“ verwerfen? Dies kann nicht rückgängig gemacht werden.",
@@ -2935,15 +2911,6 @@
"panel": {
"unavailableTitle": "Quellcodeverwaltung nicht verfügbar"
},
- "prChip": {
- "createPr": "Pull Request erstellen",
- "loading": "Pull Request wird geladen…",
- "loadingLabel": "Pull Request wird geladen",
- "openLabel": "Pull Request #{{number}}, {{state}}, {{checks}}. Pull Request öffnen.",
- "openWithComments_one": "Pull Request #{{number}}, {{state}}, {{checks}}, {{count}} ungelöster Kommentar. Pull Request öffnen.",
- "openWithComments_other": "Pull Request #{{number}}, {{state}}, {{checks}}, {{count}} ungelöste Kommentare. Pull Request öffnen.",
- "unavailableLabel": "Pull Request nicht verfügbar: {{message}}"
- },
"prCreate": {
"authRequired": "Vor dem Erstellen eines {{reviewLabel}}s ist eine Authentifizierung erforderlich.",
"baseNotOnRemote": "Den Basis-Branch pushen, bevor ein {{reviewLabel}} erstellt wird.",
@@ -3031,7 +2998,6 @@
"noBranch": "Kein Branch"
},
"state": {
- "noUpstream": "Kein Upstream",
"syncStatus": "{{ahead}} voraus, {{behind}} zurück"
},
"textGenerationFailure": {
diff --git a/mobile/src/i18n/locales/en.json b/mobile/src/i18n/locales/en.json
index f6b6ac5c333..3ecd1df2797 100644
--- a/mobile/src/i18n/locales/en.json
+++ b/mobile/src/i18n/locales/en.json
@@ -3219,7 +3219,6 @@
"createPr": "Create PR",
"pushAndCreatePr": "Push & Create PR",
"switchBranch": "Switch branch",
- "commits": "Commits",
"committing": "Commit in progress.",
"forcePushing": "Force push in progress.",
"remoteOperationInProgress": "Remote operation in progress.",
@@ -3241,7 +3240,8 @@
"pullCommits_other": "Pull {{count}} commits.",
"pushCommits_one": "Push {{count}} commit.",
"pushCommits_other": "Push {{count}} commits.",
- "upToDate": "Nothing to commit. Branch is up to date."
+ "upToDate": "Nothing to commit. Branch is up to date.",
+ "openCommits": "Open commits"
},
"dropdownCounts": {
"pushCount_one": "Push {{count}} commit",
@@ -3299,20 +3299,11 @@
"loading": "Loading commits…"
},
"branchCard": {
- "mergeState": "merge",
- "rebaseState": "rebase",
- "cherryPickState": "cherry-pick",
- "changedCount": "{{count}} changed",
- "stagedCount": "{{count}} staged",
- "branchCount": "{{count}} on branch",
"completingMerge": "Completing…",
"completeMerge": "Complete merge"
},
"content": {
"reconnecting": "Reconnecting to desktop...",
- "stageAll": "Stage All",
- "unstageAll": "Unstage All",
- "openActions": "Open source control actions",
"emptyTitle": "No local changes",
"emptyMessage": "Working tree is clean.",
"commitMessageDisabled": "Commit message disabled. No staged files.",
@@ -3321,9 +3312,6 @@
"cancelGeneration": "Cancel commit message generation",
"generateMessage": "Generate commit message with AI"
},
- "fileList": {
- "committedSection": "Committed on Branch"
- },
"fileRows": {
"bothModified": "Both modified",
"bothAdded": "Both added",
@@ -3338,10 +3326,10 @@
"unstageFile": "Unstage {{path}}",
"stageFile": "Stage {{path}}",
"discardFile": "Discard {{path}}",
- "committedSection": "Committed on Branch",
"loadingCommitted": "Loading committed changes...",
"committedUnavailable": "Committed changes unavailable.",
- "openCommittedChange": "Open committed change {{path}}"
+ "openCommittedChange": "Open committed change {{path}}",
+ "discard": "Discard"
},
"header": {
"close": "Close source control",
@@ -3353,7 +3341,6 @@
"moreActions": "More source control actions"
},
"modals": {
- "actionsTitle": "Source Control",
"discardTitle": "Discard Change",
"discardConfirmMessage": "Discard changes to \"{{path}}\"? This cannot be undone.",
"discardButton": "Discard",
@@ -3367,15 +3354,6 @@
"unavailableTitle": "Source Control Unavailable",
"loading": "Loading changes…"
},
- "prChip": {
- "loading": "Loading pull request…",
- "createPr": "Create pull request",
- "loadingLabel": "Loading pull request",
- "unavailableLabel": "Pull request unavailable: {{message}}",
- "openWithComments_one": "Pull request #{{number}}, {{state}}, {{checks}}, {{count}} unresolved comments. Open pull request.",
- "openWithComments_other": "Pull request #{{number}}, {{state}}, {{checks}}, {{count}} unresolved comments. Open pull request.",
- "openLabel": "Pull request #{{number}}, {{state}}, {{checks}}. Open pull request."
- },
"hostedReviewCopy": {
"updatingBranch": "Updating branch...",
"stagingChanges": "Staging changes...",
@@ -3394,13 +3372,7 @@
"loadFailed": "Unable to load committed changes"
},
"branchCompare": {
- "sectionTitle": "Committed on Branch",
- "summary": "{{files}} - vs {{baseRef}}",
- "summaryWithCommits": "{{files}} - {{commits}} - vs {{baseRef}}",
- "files_one": "{{count}} file",
- "files_other": "{{count}} files",
- "commits_one": "{{count}} commit",
- "commits_other": "{{count}} commits"
+ "sectionTitle": "Committed on Branch"
},
"commitMessageAi": {
"generateFailed": "Failed to generate commit message"
@@ -3422,8 +3394,8 @@
},
"gitStatus": {
"conflicts": "Conflicts",
- "changes": "Changes",
- "stagedChanges": "Staged Changes"
+ "uncommitted": "Uncommitted",
+ "staged": "Staged"
},
"hostedReviewCreateIntent": {
"resolveConflicts": "Resolve conflicts before creating a pull request.",
@@ -3498,8 +3470,7 @@
"actionFailed": "Source control action failed"
},
"state": {
- "syncStatus": "{{ahead}} ahead, {{behind}} behind",
- "noUpstream": "No upstream"
+ "syncStatus": "{{ahead}} ahead, {{behind}} behind"
},
"textGenerationFailure": {
"openCodeInvalidEvents": "OpenCode returned invalid JSON events.",
@@ -3641,6 +3612,33 @@
},
"prCreateDraft": {
"generateFailed": "Failed to write a description"
+ },
+ "changesHeader": {
+ "merging": "Merging",
+ "rebasing": "Rebasing",
+ "cherryPicking": "Cherry-picking",
+ "notPublished": "Not published",
+ "notPushed": "{{count}} not pushed",
+ "behind": "{{count}} behind",
+ "upToDate": "Up to date",
+ "switchBranch": "Switch branch from {{branch}}",
+ "mergeInProgress": "Merge in progress",
+ "rebaseInProgress": "Rebase in progress",
+ "cherryPickInProgress": "Cherry-pick in progress",
+ "filesNeedDecision_one": "{{count}} file needs a decision before you can finish.",
+ "filesNeedDecision_other": "{{count}} files need a decision before you can finish.",
+ "conflictsResolved": "Every conflict is resolved. Finish from the bar below.",
+ "continueOnComputer": "Every conflict is resolved. Continue on your computer to finish.",
+ "commits_one": "{{count}} commit",
+ "commits_other": "{{count}} commits",
+ "conflictsMayRemain": "Not every file is listed, so conflicts may remain. Check on your computer."
+ },
+ "fileList": {
+ "stageAll": "Stage all",
+ "mergedCleanly": "Merged cleanly",
+ "unstageAll": "Unstage all",
+ "onThisBranch": "On this branch {{count}}",
+ "versusBase": "vs {{base}}"
}
},
"newWorkspace": {
diff --git a/mobile/src/i18n/locales/es.json b/mobile/src/i18n/locales/es.json
index a1741bf8b36..e06f495a16b 100644
--- a/mobile/src/i18n/locales/es.json
+++ b/mobile/src/i18n/locales/es.json
@@ -2742,7 +2742,6 @@
"checkingReviewStatus": "Comprobando estado de revisión.",
"checkoutBeforePublish": "Haz checkout de una rama antes de publicar commits.",
"commitStagedChanges": "Haz commit de los cambios preparados.",
- "commits": "Commits",
"committing": "Commit en curso.",
"createPr": "Crear PR",
"enterCommitMessage": "Introduce un mensaje de commit para hacer commit.",
@@ -2772,25 +2771,11 @@
"resolveFailed": "No se pudo determinar la base de la rama"
},
"branchCard": {
- "branchCount": "{{count}} en la rama",
- "changedCount": "{{count}} con cambios",
- "cherryPickState": "cherry-pick",
- "mergeState": "merge",
- "rebaseState": "rebase",
- "stagedCount": "{{count}} en stage",
"completingMerge": "Finalizando…",
"completeMerge": "Completar fusión"
},
"branchCompare": {
- "commits_many": "{{count}} commits",
- "commits_one": "{{count}} commit",
- "commits_other": "{{count}} commits",
- "files_many": "{{count}} archivos",
- "files_one": "{{count}} archivo",
- "files_other": "{{count}} archivos",
- "sectionTitle": "Commits en la rama",
- "summary": "{{files}} - frente a {{baseRef}}",
- "summaryWithCommits": "{{files}} - {{commits}} - frente a {{baseRef}}"
+ "sectionTitle": "Commits en la rama"
},
"branchCompareOutcome": {
"baseBranchUnavailable": "No se pudo determinar la rama base para la comparación.",
@@ -2849,11 +2834,8 @@
"emptyMessage": "El árbol de trabajo está limpio.",
"emptyTitle": "No hay cambios locales",
"generateMessage": "Generar mensaje de commit con IA",
- "openActions": "Abrir acciones de control de código fuente",
"reconnecting": "Reconectando con el escritorio…",
- "regenerateHint": "Borre el mensaje para regenerar.",
- "stageAll": "Hacer stage de todo",
- "unstageAll": "Quitar todo del stage"
+ "regenerateHint": "Borre el mensaje para regenerar."
},
"createPrAction": {
"checkingStatus": "Comprobando estado de revisión…",
@@ -2889,20 +2871,15 @@
"titleUnpublishedForcePushCount_one": "Haz force push con lease de {{count}} commit de la rama y configura un upstream si es necesario.",
"titleUnpublishedForcePushCount_other": "Haz force push con lease de {{count}} commits de la rama y configura un upstream si es necesario."
},
- "fileList": {
- "committedSection": "Commits en la rama"
- },
"fileRows": {
"addedByThem": "Agregado por ellos",
"addedByUs": "Agregado por nosotros",
"bothAdded": "Agregado por ambos",
"bothDeleted": "Eliminado por ambos",
"bothModified": "Modificado por ambos",
- "committedSection": "Commits en la rama",
"committedUnavailable": "Los cambios confirmados no están disponibles.",
"deletedByThem": "Eliminado por ellos",
"deletedByUs": "Eliminado por nosotros",
- "discardFile": "Descartar {{path}}",
"loadingCommitted": "Cargando cambios confirmados…",
"openChangedFile": "Abrir archivo modificado {{path}}",
"openCommittedChange": "Abrir cambio confirmado en {{path}}",
@@ -2940,9 +2917,7 @@
"waitingForDesktop": "Esperando al escritorio…"
},
"gitStatus": {
- "changes": "Cambios",
- "conflicts": "Conflictos",
- "stagedChanges": "Cambios preparados"
+ "conflicts": "Conflictos"
},
"header": {
"backToSession": "Volver a la sesión",
@@ -3010,7 +2985,6 @@
"waitingForDesktop": "Esperando al escritorio…"
},
"modals": {
- "actionsTitle": "Control de código fuente",
"currentBranch": "actual",
"discardButton": "Descartar",
"discardConfirmMessage": "¿Descartar los cambios de \"{{path}}\"? Esta acción no se puede deshacer.",
@@ -3023,16 +2997,6 @@
"panel": {
"unavailableTitle": "Control de código fuente no disponible"
},
- "prChip": {
- "createPr": "Crear pull request",
- "loading": "Cargando pull request…",
- "loadingLabel": "Cargando pull request",
- "openLabel": "Pull request #{{number}}, {{state}}, {{checks}}. Abrir pull request.",
- "openWithComments_many": "Pull request #{{number}}, {{state}}, {{checks}}, {{count}} comentarios sin resolver. Abrir pull request.",
- "openWithComments_one": "Pull request #{{number}}, {{state}}, {{checks}}, {{count}} comentario sin resolver. Abrir pull request.",
- "openWithComments_other": "Pull request #{{number}}, {{state}}, {{checks}}, {{count}} comentarios sin resolver. Abrir pull request.",
- "unavailableLabel": "Pull request no disponible: {{message}}"
- },
"prCreate": {
"authRequired": "Autentícate antes de crear una {{reviewLabel}}.",
"baseNotOnRemote": "Haz push de la rama base antes de crear una {{reviewLabel}}.",
@@ -3120,7 +3084,6 @@
"noBranch": "Sin rama"
},
"state": {
- "noUpstream": "Sin upstream",
"syncStatus": "{{ahead}} por delante, {{behind}} por detrás"
},
"textGenerationFailure": {
diff --git a/mobile/src/i18n/locales/fr.json b/mobile/src/i18n/locales/fr.json
index 8a2fea5270f..40cb6184fef 100644
--- a/mobile/src/i18n/locales/fr.json
+++ b/mobile/src/i18n/locales/fr.json
@@ -2742,7 +2742,6 @@
"checkingReviewStatus": "Vérification du statut de revue.",
"checkoutBeforePublish": "Effectuez un checkout d'une branche avant de publier des commits.",
"commitStagedChanges": "Committer les modifications indexées.",
- "commits": "Commits",
"committing": "Commit en cours.",
"createPr": "Créer une PR",
"enterCommitMessage": "Saisissez un message de commit pour committer.",
@@ -2772,25 +2771,11 @@
"resolveFailed": "Impossible de déterminer la base de la branche"
},
"branchCard": {
- "branchCount": "{{count}} sur la branche",
- "changedCount": "Modifiés : {{count}}",
- "cherryPickState": "cherry-pick",
- "mergeState": "merge",
- "rebaseState": "rebase",
- "stagedCount": "Indexés : {{count}}",
"completingMerge": "Finalisation…",
"completeMerge": "Terminer la fusion"
},
"branchCompare": {
- "commits_many": "{{count}} commits",
- "commits_one": "{{count}} commit",
- "commits_other": "{{count}} commits",
- "files_many": "{{count}} fichiers",
- "files_one": "{{count}} fichier",
- "files_other": "{{count}} fichiers",
- "sectionTitle": "Committé sur la branche",
- "summary": "{{files}} - par rapport à {{baseRef}}",
- "summaryWithCommits": "{{files}} - {{commits}} - par rapport à {{baseRef}}"
+ "sectionTitle": "Committé sur la branche"
},
"branchCompareOutcome": {
"baseBranchUnavailable": "Impossible de déterminer la branche de base pour la comparaison.",
@@ -2849,11 +2834,8 @@
"emptyMessage": "Le worktree est propre.",
"emptyTitle": "Aucune modification locale",
"generateMessage": "Générer le message de commit avec l'IA",
- "openActions": "Ouvrir les actions de contrôle de code source",
"reconnecting": "Reconnexion à l'ordinateur…",
- "regenerateHint": "Effacez le message pour le régénérer.",
- "stageAll": "Tout indexer",
- "unstageAll": "Tout désindexer"
+ "regenerateHint": "Effacez le message pour le régénérer."
},
"createPrAction": {
"checkingStatus": "Vérification du statut de revue…",
@@ -2889,20 +2871,15 @@
"titleUnpublishedForcePushCount_one": "Faites un force push avec lease de {{count}} commit de la branche et définissez un upstream si nécessaire.",
"titleUnpublishedForcePushCount_other": "Faites un force push avec lease de {{count}} commits de la branche et définissez un upstream si nécessaire."
},
- "fileList": {
- "committedSection": "Committé sur la branche"
- },
"fileRows": {
"addedByThem": "Ajouté chez eux",
"addedByUs": "Ajouté chez nous",
"bothAdded": "Ajouté des deux côtés",
"bothDeleted": "Supprimé des deux côtés",
"bothModified": "Modifié des deux côtés",
- "committedSection": "Committé sur la branche",
"committedUnavailable": "Modifications committées indisponibles.",
"deletedByThem": "Supprimé chez eux",
"deletedByUs": "Supprimé chez nous",
- "discardFile": "Abandonner les modifications de {{path}}",
"loadingCommitted": "Chargement des modifications committées…",
"openChangedFile": "Ouvrir le fichier modifié {{path}}",
"openCommittedChange": "Ouvrir la modification committée de {{path}}",
@@ -2940,9 +2917,7 @@
"waitingForDesktop": "En attente de l'ordinateur…"
},
"gitStatus": {
- "changes": "Modifications",
- "conflicts": "Conflits",
- "stagedChanges": "Modifications stagées"
+ "conflicts": "Conflits"
},
"header": {
"backToSession": "Retour à la session",
@@ -3010,7 +2985,6 @@
"waitingForDesktop": "En attente de l'ordinateur…"
},
"modals": {
- "actionsTitle": "Contrôle de code source",
"currentBranch": "actuelle",
"discardButton": "Abandonner",
"discardConfirmMessage": "Abandonner les modifications de « {{path}} » ? Cette action est irréversible.",
@@ -3023,16 +2997,6 @@
"panel": {
"unavailableTitle": "Contrôle de code source indisponible"
},
- "prChip": {
- "createPr": "Créer la pull request",
- "loading": "Chargement de la pull request…",
- "loadingLabel": "Chargement de la pull request",
- "openLabel": "Pull request #{{number}}, {{state}}, {{checks}}. Ouvrir la pull request.",
- "openWithComments_many": "Pull request #{{number}}, {{state}}, {{checks}}, {{count}} commentaires non résolus. Ouvrir la pull request.",
- "openWithComments_one": "Pull request #{{number}}, {{state}}, {{checks}}, {{count}} commentaire non résolu. Ouvrir la pull request.",
- "openWithComments_other": "Pull request #{{number}}, {{state}}, {{checks}}, {{count}} commentaires non résolus. Ouvrir la pull request.",
- "unavailableLabel": "Pull request indisponible : {{message}}"
- },
"prCreate": {
"authRequired": "Authentifiez-vous avant de créer une {{reviewLabel}}.",
"baseNotOnRemote": "Poussez la branche de base avant de créer une {{reviewLabel}}.",
@@ -3120,7 +3084,6 @@
"noBranch": "Aucune branche"
},
"state": {
- "noUpstream": "Aucune branche de suivi",
"syncStatus": "{{ahead}} en avance, {{behind}} en retard"
},
"textGenerationFailure": {
diff --git a/mobile/src/i18n/locales/ja.json b/mobile/src/i18n/locales/ja.json
index 65262ea77e0..32b9b5941a2 100644
--- a/mobile/src/i18n/locales/ja.json
+++ b/mobile/src/i18n/locales/ja.json
@@ -2666,7 +2666,6 @@
"checkingReviewStatus": "レビュー状態を確認中です。",
"checkoutBeforePublish": "コミットを公開する前にブランチをチェックアウトしてください。",
"commitStagedChanges": "ステージ済みの変更をコミットします。",
- "commits": "コミット",
"committing": "コミット中です。",
"createPr": "PR を作成",
"enterCommitMessage": "コミットするには、コミットメッセージを入力してください。",
@@ -2694,23 +2693,11 @@
"resolveFailed": "ベースブランチを特定できませんでした。"
},
"branchCard": {
- "branchCount": "ブランチ内 {{count}} 件",
- "changedCount": "変更 {{count}} 件",
- "cherryPickState": "チェリーピック",
- "mergeState": "マージ",
- "rebaseState": "リベース",
- "stagedCount": "ステージ済み {{count}} 件",
"completingMerge": "完了しています…",
"completeMerge": "マージを完了"
},
"branchCompare": {
- "commits_one": "{{count}} 件のコミット",
- "commits_other": "{{count}} 件のコミット",
- "files_one": "{{count}} 個のファイル",
- "files_other": "{{count}} 個のファイル",
- "sectionTitle": "ブランチのコミット済み変更",
- "summary": "{{files}} - {{baseRef}} との比較",
- "summaryWithCommits": "{{files}} - {{commits}} - {{baseRef}} との比較"
+ "sectionTitle": "ブランチのコミット済み変更"
},
"branchCompareOutcome": {
"baseBranchUnavailable": "比較用のベースブランチを特定できませんでした。",
@@ -2769,11 +2756,8 @@
"emptyMessage": "ワークツリーに変更はありません。",
"emptyTitle": "ローカルの変更なし",
"generateMessage": "AIでコミットメッセージを生成",
- "openActions": "ソース管理の操作を開く",
"reconnecting": "デスクトップに再接続中…",
- "regenerateHint": "再生成するには、メッセージをクリアしてください。",
- "stageAll": "ステージオール",
- "unstageAll": "すべてステージ解除"
+ "regenerateHint": "再生成するには、メッセージをクリアしてください。"
},
"createPrAction": {
"checkingStatus": "レビュー状態を確認中…",
@@ -2802,20 +2786,15 @@
"titleUnpublishedForcePushCount_one": "{{count}} 件のブランチコミットを lease 付きで強制プッシュし、必要に応じてアップストリームを設定します。",
"titleUnpublishedForcePushCount_other": "{{count}} 件のブランチコミットを lease 付きで強制プッシュし、必要に応じてアップストリームを設定します。"
},
- "fileList": {
- "committedSection": "ブランチのコミット済み変更"
- },
"fileRows": {
"addedByThem": "相手側で追加",
"addedByUs": "こちら側で追加",
"bothAdded": "双方で追加",
"bothDeleted": "双方で削除",
"bothModified": "双方で変更",
- "committedSection": "ブランチのコミット済み変更",
"committedUnavailable": "コミット済みの変更を表示できません。",
"deletedByThem": "相手側で削除",
"deletedByUs": "こちら側で削除",
- "discardFile": "{{path}} の変更を破棄",
"loadingCommitted": "コミット済みの変更を読み込み中…",
"openChangedFile": "変更されたファイル {{path}} を開く",
"openCommittedChange": "{{path}} のコミット済みの変更を開く",
@@ -2848,9 +2827,7 @@
"waitingForDesktop": "デスクトップからの応答を待機中…"
},
"gitStatus": {
- "changes": "変更点",
- "conflicts": "競合",
- "stagedChanges": "ステージ済みの変更"
+ "conflicts": "競合"
},
"header": {
"backToSession": "セッションに戻る",
@@ -2918,7 +2895,6 @@
"waitingForDesktop": "デスクトップへの接続を待機中…"
},
"modals": {
- "actionsTitle": "ソース管理",
"currentBranch": "現在",
"discardButton": "破棄",
"discardConfirmMessage": "「{{path}}」への変更を破棄しますか?この操作は取り消せません。",
@@ -2931,15 +2907,6 @@
"panel": {
"unavailableTitle": "ソース管理を利用できません"
},
- "prChip": {
- "createPr": "PR を作成",
- "loading": "PR を読み込み中…",
- "loadingLabel": "PR を読み込み中",
- "openLabel": "PR #{{number}}、{{state}}、{{checks}}。PR を開く。",
- "openWithComments_one": "PR #{{number}}、{{state}}、{{checks}}、未解決のコメント {{count}} 件。PR を開く。",
- "openWithComments_other": "PR #{{number}}、{{state}}、{{checks}}、未解決のコメント {{count}} 件。PR を開く。",
- "unavailableLabel": "PR を表示できません:{{message}}"
- },
"prCreate": {
"authRequired": "{{reviewLabel}} を作成する前に認証してください。",
"baseNotOnRemote": "{{reviewLabel}} を作成する前にベースブランチをプッシュしてください。",
@@ -3027,7 +2994,6 @@
"noBranch": "ブランチなし"
},
"state": {
- "noUpstream": "アップストリームなし",
"syncStatus": "{{ahead}} コミット先行、{{behind}} コミット遅れ"
},
"textGenerationFailure": {
diff --git a/mobile/src/i18n/locales/ko.json b/mobile/src/i18n/locales/ko.json
index 428d61d66d3..bcca2fca555 100644
--- a/mobile/src/i18n/locales/ko.json
+++ b/mobile/src/i18n/locales/ko.json
@@ -2666,7 +2666,6 @@
"checkingReviewStatus": "리뷰 상태를 확인 중입니다.",
"checkoutBeforePublish": "커밋을 게시하기 전에 브랜치를 체크아웃하세요.",
"commitStagedChanges": "스테이징된 변경 사항을 커밋하세요.",
- "commits": "커밋",
"committing": "커밋이 진행 중입니다.",
"createPr": "PR 작성",
"enterCommitMessage": "커밋하려면 커밋 메시지를 입력하세요.",
@@ -2694,23 +2693,11 @@
"resolveFailed": "브랜치의 기준점을 확인할 수 없습니다."
},
"branchCard": {
- "branchCount": "브랜치 변경 {{count}}개",
- "changedCount": "변경 {{count}}개",
- "cherryPickState": "체리픽",
- "mergeState": "병합",
- "rebaseState": "리베이스",
- "stagedCount": "{{count}}개 스테이징됨",
"completingMerge": "완료 중…",
"completeMerge": "병합 완료"
},
"branchCompare": {
- "commits_one": "커밋 {{count}}개",
- "commits_other": "커밋 {{count}}개",
- "files_one": "파일 {{count}}개",
- "files_other": "파일 {{count}}개",
- "sectionTitle": "브랜치에 커밋됨",
- "summary": "{{files}} - {{baseRef}} 대비",
- "summaryWithCommits": "{{files}} - {{commits}} - {{baseRef}} 대비"
+ "sectionTitle": "브랜치에 커밋됨"
},
"branchCompareOutcome": {
"baseBranchUnavailable": "비교할 기본 브랜치를 확인할 수 없습니다.",
@@ -2769,11 +2756,8 @@
"emptyMessage": "워크트리에 변경 사항이 없습니다.",
"emptyTitle": "로컬 변경 사항 없음",
"generateMessage": "AI로 commit 메시지 생성",
- "openActions": "소스 제어 작업 열기",
"reconnecting": "데스크톱에 다시 연결 중…",
- "regenerateHint": "다시 생성하려면 메시지를 지웁니다.",
- "stageAll": "모두 스테이지",
- "unstageAll": "모두 언스테이징"
+ "regenerateHint": "다시 생성하려면 메시지를 지웁니다."
},
"createPrAction": {
"checkingStatus": "리뷰 상태 확인 중…",
@@ -2802,20 +2786,15 @@
"titleUnpublishedForcePushCount_one": "브랜치 커밋 {{count}}개를 lease로 강제 푸시하고 필요하면 업스트림을 설정합니다.",
"titleUnpublishedForcePushCount_other": "브랜치 커밋 {{count}}개를 lease로 강제 푸시하고 필요하면 업스트림을 설정합니다."
},
- "fileList": {
- "committedSection": "브랜치에 커밋됨"
- },
"fileRows": {
"addedByThem": "상대 쪽에서 추가됨",
"addedByUs": "우리 쪽에서 추가됨",
"bothAdded": "양쪽에서 추가됨",
"bothDeleted": "양쪽에서 삭제됨",
"bothModified": "양쪽에서 수정됨",
- "committedSection": "브랜치에 커밋됨",
"committedUnavailable": "커밋된 변경 사항을 확인할 수 없습니다.",
"deletedByThem": "상대 쪽에서 삭제됨",
"deletedByUs": "우리 쪽에서 삭제됨",
- "discardFile": "{{path}} 변경사항 취소",
"loadingCommitted": "커밋된 변경 사항 로드 중…",
"openChangedFile": "변경된 파일 {{path}} 열기",
"openCommittedChange": "{{path}}의 커밋된 변경 사항 열기",
@@ -2848,9 +2827,7 @@
"waitingForDesktop": "데스크톱 응답 대기 중…"
},
"gitStatus": {
- "changes": "변경 사항",
- "conflicts": "충돌",
- "stagedChanges": "스테이징된 변경 사항"
+ "conflicts": "충돌"
},
"header": {
"backToSession": "세션으로 돌아가기",
@@ -2918,7 +2895,6 @@
"waitingForDesktop": "데스크톱 연결 대기 중…"
},
"modals": {
- "actionsTitle": "소스 제어",
"currentBranch": "현재",
"discardButton": "버리기",
"discardConfirmMessage": "\"{{path}}\"의 변경 사항을 버리시겠습니까? 이 작업은 되돌릴 수 없습니다.",
@@ -2931,15 +2907,6 @@
"panel": {
"unavailableTitle": "소스 제어 사용 불가"
},
- "prChip": {
- "createPr": "PR 생성",
- "loading": "PR 로드 중…",
- "loadingLabel": "PR 로드 중",
- "openLabel": "PR #{{number}}, {{state}}, {{checks}}. PR 열기.",
- "openWithComments_one": "PR #{{number}}, {{state}}, {{checks}}, 해결되지 않은 댓글 {{count}}개. PR 열기.",
- "openWithComments_other": "PR #{{number}}, {{state}}, {{checks}}, 해결되지 않은 댓글 {{count}}개. PR 열기.",
- "unavailableLabel": "PR 확인 불가: {{message}}"
- },
"prCreate": {
"authRequired": "{{reviewLabel}} 생성 전에 인증하세요.",
"baseNotOnRemote": "{{reviewLabel}}을(를) 생성하기 전에 기본 브랜치를 푸시하세요.",
@@ -3027,7 +2994,6 @@
"noBranch": "브랜치 없음"
},
"state": {
- "noUpstream": "업스트림 없음",
"syncStatus": "{{ahead}}개 앞섬, {{behind}}개 뒤처짐"
},
"textGenerationFailure": {
diff --git a/mobile/src/i18n/locales/pt.json b/mobile/src/i18n/locales/pt.json
index 5343c605d4c..93ed011a2b8 100644
--- a/mobile/src/i18n/locales/pt.json
+++ b/mobile/src/i18n/locales/pt.json
@@ -2742,7 +2742,6 @@
"checkingReviewStatus": "Verificando status da revisão.",
"checkoutBeforePublish": "Faça checkout de uma branch antes de publicar commits.",
"commitStagedChanges": "Faça commit das alterações staged.",
- "commits": "Commits",
"committing": "Commit em andamento.",
"createPr": "Criar PR",
"enterCommitMessage": "Digite uma mensagem de commit para fazer commit.",
@@ -2772,25 +2771,11 @@
"resolveFailed": "Não foi possível determinar a branch base"
},
"branchCard": {
- "branchCount": "{{count}} na branch",
- "changedCount": "Alterações: {{count}}",
- "cherryPickState": "cherry-pick",
- "mergeState": "merge",
- "rebaseState": "rebase",
- "stagedCount": "No stage: {{count}}",
"completingMerge": "Concluindo…",
"completeMerge": "Concluir mesclagem"
},
"branchCompare": {
- "commits_many": "{{count}} commits",
- "commits_one": "{{count}} commit",
- "commits_other": "{{count}} commits",
- "files_many": "{{count}} arquivos",
- "files_one": "{{count}} arquivo",
- "files_other": "{{count}} arquivos",
- "sectionTitle": "Commitado na Branch",
- "summary": "{{files}} - em relação a {{baseRef}}",
- "summaryWithCommits": "{{files}} - {{commits}} - em relação a {{baseRef}}"
+ "sectionTitle": "Commitado na Branch"
},
"branchCompareOutcome": {
"baseBranchUnavailable": "Não foi possível determinar a branch base para comparação.",
@@ -2849,11 +2834,8 @@
"emptyMessage": "A árvore de trabalho está limpa.",
"emptyTitle": "Nenhuma alteração local",
"generateMessage": "Gerar mensagem de commit com IA",
- "openActions": "Abrir ações de controle de versão",
"reconnecting": "Reconectando ao desktop…",
- "regenerateHint": "Limpe a mensagem para gerar novamente.",
- "stageAll": "Colocar Tudo em Stage",
- "unstageAll": "Remover Tudo do Stage"
+ "regenerateHint": "Limpe a mensagem para gerar novamente."
},
"createPrAction": {
"checkingStatus": "Verificando status de review…",
@@ -2889,20 +2871,15 @@
"titleUnpublishedForcePushCount_one": "Faça force push com lease de {{count}} commit da branch e defina um upstream se necessário.",
"titleUnpublishedForcePushCount_other": "Faça force push com lease de {{count}} commits da branch e defina um upstream se necessário."
},
- "fileList": {
- "committedSection": "Commitado na Branch"
- },
"fileRows": {
"addedByThem": "Adicionado por eles",
"addedByUs": "Adicionado por nós",
"bothAdded": "Adicionado por ambos",
"bothDeleted": "Excluído por ambos",
"bothModified": "Modificado por ambos",
- "committedSection": "Commitado na Branch",
"committedUnavailable": "Alterações commitadas indisponíveis.",
"deletedByThem": "Excluído por eles",
"deletedByUs": "Excluído por nós",
- "discardFile": "Descartar {{path}}",
"loadingCommitted": "Carregando alterações commitadas…",
"openChangedFile": "Abrir arquivo alterado {{path}}",
"openCommittedChange": "Abrir alteração commitada em {{path}}",
@@ -2940,9 +2917,7 @@
"waitingForDesktop": "Aguardando o desktop…"
},
"gitStatus": {
- "changes": "Alterações",
- "conflicts": "Conflitos",
- "stagedChanges": "Alterações no Stage"
+ "conflicts": "Conflitos"
},
"header": {
"backToSession": "Voltar à sessão",
@@ -3010,7 +2985,6 @@
"waitingForDesktop": "Aguardando o desktop…"
},
"modals": {
- "actionsTitle": "Controle de Código-Fonte",
"currentBranch": "atual",
"discardButton": "Descartar",
"discardConfirmMessage": "Descartar alterações em \"{{path}}\"? Esta ação não pode ser desfeita.",
@@ -3023,16 +2997,6 @@
"panel": {
"unavailableTitle": "Controle de Código-Fonte Indisponível"
},
- "prChip": {
- "createPr": "Criar pull request",
- "loading": "Carregando pull request…",
- "loadingLabel": "Carregando pull request",
- "openLabel": "Pull request #{{number}}, {{state}}, {{checks}}. Abrir pull request.",
- "openWithComments_many": "Pull request #{{number}}, {{state}}, {{checks}}, {{count}} comentários não resolvidos. Abrir pull request.",
- "openWithComments_one": "Pull request #{{number}}, {{state}}, {{checks}}, {{count}} comentário não resolvido. Abrir pull request.",
- "openWithComments_other": "Pull request #{{number}}, {{state}}, {{checks}}, {{count}} comentários não resolvidos. Abrir pull request.",
- "unavailableLabel": "Pull request indisponível: {{message}}"
- },
"prCreate": {
"authRequired": "Autentique-se antes de criar um {{reviewLabel}}.",
"baseNotOnRemote": "Faça push da branch base antes de criar um {{reviewLabel}}.",
@@ -3120,7 +3084,6 @@
"noBranch": "Sem branch"
},
"state": {
- "noUpstream": "Sem upstream",
"syncStatus": "{{ahead}} à frente, {{behind}} atrás"
},
"textGenerationFailure": {
diff --git a/mobile/src/i18n/locales/ru.json b/mobile/src/i18n/locales/ru.json
index eeda9c28825..0ca130d1b95 100644
--- a/mobile/src/i18n/locales/ru.json
+++ b/mobile/src/i18n/locales/ru.json
@@ -2814,7 +2814,6 @@
"checkingReviewStatus": "Получение статуса PR/MR.",
"checkoutBeforePublish": "Переключитесь на ветку перед публикацией коммитов.",
"commitStagedChanges": "Создать коммит с изменениями в индексе.",
- "commits": "Коммиты",
"committing": "Создаётся коммит.",
"createPr": "Создать PR",
"enterCommitMessage": "Введите сообщение, чтобы создать коммит.",
@@ -2846,27 +2845,11 @@
"resolveFailed": "Не удалось определить базовую ветку"
},
"branchCard": {
- "branchCount": "В ветке: {{count}}",
- "changedCount": "Изменено: {{count}}",
- "cherryPickState": "cherry-pick",
- "mergeState": "слияние",
- "rebaseState": "перебазирование",
- "stagedCount": "В индексе: {{count}}",
"completingMerge": "Завершение…",
"completeMerge": "Завершить слияние"
},
"branchCompare": {
- "commits_few": "{{count}} коммита",
- "commits_many": "{{count}} коммитов",
- "commits_one": "{{count}} коммит",
- "commits_other": "{{count}} коммита",
- "files_few": "{{count}} файла",
- "files_many": "{{count}} файлов",
- "files_one": "{{count}} файл",
- "files_other": "{{count}} файла",
- "sectionTitle": "Закоммичено в ветке",
- "summary": "{{files}} — по сравнению с {{baseRef}}",
- "summaryWithCommits": "{{files}} — {{commits}} — по сравнению с {{baseRef}}"
+ "sectionTitle": "Закоммичено в ветке"
},
"branchCompareOutcome": {
"baseBranchUnavailable": "Не удалось определить базовую ветку для сравнения.",
@@ -2925,11 +2908,8 @@
"emptyMessage": "В рабочем дереве нет изменений.",
"emptyTitle": "Нет локальных изменений",
"generateMessage": "Создать сообщение коммита с помощью ИИ",
- "openActions": "Открыть меню управления версиями",
"reconnecting": "Переподключение к компьютеру…",
- "regenerateHint": "Очистите сообщение для повторного создания.",
- "stageAll": "Добавить всё в индекс",
- "unstageAll": "Убрать всё из индекса"
+ "regenerateHint": "Очистите сообщение для повторного создания."
},
"createPrAction": {
"checkingStatus": "Получение статуса PR/MR…",
@@ -2972,20 +2952,15 @@
"titleUnpublishedForcePushCount_one": "Принудительно отправьте {{count}} коммит этой ветки с проверкой состояния удалённой ветки и при необходимости настройте отслеживаемую удалённую ветку.",
"titleUnpublishedForcePushCount_other": "Принудительно отправьте {{count}} коммита этой ветки с проверкой состояния удалённой ветки и при необходимости настройте отслеживаемую удалённую ветку."
},
- "fileList": {
- "committedSection": "Закоммичено в ветке"
- },
"fileRows": {
"addedByThem": "Добавлен ими",
"addedByUs": "Добавлен нами",
"bothAdded": "Добавлен с обеих сторон",
"bothDeleted": "Удалён с обеих сторон",
"bothModified": "Изменён с обеих сторон",
- "committedSection": "Закоммичено в ветке",
"committedUnavailable": "Закоммиченные изменения недоступны.",
"deletedByThem": "Удалён ими",
"deletedByUs": "Удалён нами",
- "discardFile": "Отменить изменения в «{{path}}»",
"loadingCommitted": "Загрузка закоммиченных изменений…",
"openChangedFile": "Открыть изменённый файл {{path}}",
"openCommittedChange": "Открыть закоммиченные изменения в «{{path}}»",
@@ -3028,9 +3003,7 @@
"waitingForDesktop": "Ожидание ответа от компьютера…"
},
"gitStatus": {
- "changes": "Изменения",
- "conflicts": "Конфликты",
- "stagedChanges": "Изменения в индексе"
+ "conflicts": "Конфликты"
},
"header": {
"backToSession": "Вернуться к сессии",
@@ -3098,7 +3071,6 @@
"waitingForDesktop": "Ожидание подключения к компьютеру…"
},
"modals": {
- "actionsTitle": "Управление версиями",
"currentBranch": "текущая",
"discardButton": "Отменить изменения",
"discardConfirmMessage": "Отменить изменения в «{{path}}»? Это действие необратимо.",
@@ -3111,17 +3083,6 @@
"panel": {
"unavailableTitle": "Управление версиями недоступно"
},
- "prChip": {
- "createPr": "Создать пул-реквест",
- "loading": "Загрузка пул-реквеста…",
- "loadingLabel": "Загрузка пул-реквеста",
- "openLabel": "Пул-реквест #{{number}}, {{state}}, {{checks}}. Открыть пул-реквест.",
- "openWithComments_few": "Пул-реквест #{{number}}, {{state}}, {{checks}}, {{count}} неразрешённых комментария. Открыть пул-реквест.",
- "openWithComments_many": "Пул-реквест #{{number}}, {{state}}, {{checks}}, {{count}} неразрешённых комментариев. Открыть пул-реквест.",
- "openWithComments_one": "Пул-реквест #{{number}}, {{state}}, {{checks}}, {{count}} неразрешённый комментарий. Открыть пул-реквест.",
- "openWithComments_other": "Пул-реквест #{{number}}, {{state}}, {{checks}}, {{count}} неразрешённого комментария. Открыть пул-реквест.",
- "unavailableLabel": "Пул-реквест недоступен: {{message}}"
- },
"prCreate": {
"authRequired": "Прежде чем создать {{reviewLabel}}, пройдите аутентификацию.",
"baseNotOnRemote": "Прежде чем создать {{reviewLabel}}, отправьте базовую ветку.",
@@ -3209,7 +3170,6 @@
"noBranch": "Нет ветки"
},
"state": {
- "noUpstream": "Нет отслеживаемой ветки",
"syncStatus": "Опережает на {{ahead}}, отстаёт на {{behind}}"
},
"textGenerationFailure": {
diff --git a/mobile/src/i18n/locales/tr.json b/mobile/src/i18n/locales/tr.json
index b7acbef6c26..4706bed8d0f 100644
--- a/mobile/src/i18n/locales/tr.json
+++ b/mobile/src/i18n/locales/tr.json
@@ -2670,7 +2670,6 @@
"checkingReviewStatus": "İnceleme durumu kontrol ediliyor.",
"checkoutBeforePublish": "Commit'leri yayınlamadan önce bir branch'i checkout edin.",
"commitStagedChanges": "Stage edilmiş değişiklikleri commit'leyin.",
- "commits": "Commit'ler",
"committing": "Commit işlemi sürüyor.",
"createPr": "PR Oluştur",
"enterCommitMessage": "Commit'lemek için bir commit mesajı girin.",
@@ -2698,23 +2697,11 @@
"resolveFailed": "Temel branch belirlenemedi"
},
"branchCard": {
- "branchCount": "branch'te {{count}}",
- "changedCount": "{{count}} değişiklik",
- "cherryPickState": "cherry-pick",
- "mergeState": "merge",
- "rebaseState": "rebase",
- "stagedCount": "{{count}} stage edildi",
"completingMerge": "Tamamlanıyor…",
"completeMerge": "Birleştirmeyi tamamla"
},
"branchCompare": {
- "commits_one": "{{count}} commit",
- "commits_other": "{{count}} commit",
- "files_one": "{{count}} dosya",
- "files_other": "{{count}} dosya",
- "sectionTitle": "Branch'e Commit Edildi",
- "summary": "{{files}} - {{baseRef}} ile karşılaştırma",
- "summaryWithCommits": "{{files}} - {{commits}} - {{baseRef}} ile karşılaştırma"
+ "sectionTitle": "Branch'e Commit Edildi"
},
"branchCompareOutcome": {
"baseBranchUnavailable": "Karşılaştırma için temel branch belirlenemedi.",
@@ -2773,11 +2760,8 @@
"emptyMessage": "Çalışma ağacı temiz.",
"emptyTitle": "Yerel değişiklik yok",
"generateMessage": "Commit mesajını AI ile oluştur",
- "openActions": "Kaynak kontrolü eylemlerini aç",
"reconnecting": "Masaüstüne yeniden bağlanılıyor…",
- "regenerateHint": "Yeniden oluşturmak için mesajı temizleyin.",
- "stageAll": "Tümünü Stage Et",
- "unstageAll": "Tümünü Stage'den Çıkar"
+ "regenerateHint": "Yeniden oluşturmak için mesajı temizleyin."
},
"createPrAction": {
"checkingStatus": "İnceleme durumu kontrol ediliyor…",
@@ -2806,20 +2790,15 @@
"titleUnpublishedForcePushCount_one": "Branch'teki {{count}} commit'i lease korumasıyla force push edin ve gerekirse bir upstream ayarlayın.",
"titleUnpublishedForcePushCount_other": "Branch'teki {{count}} commit'i lease korumasıyla force push edin ve gerekirse bir upstream ayarlayın."
},
- "fileList": {
- "committedSection": "Branch'e Commit'lendi"
- },
"fileRows": {
"addedByThem": "Karşı tarafça eklendi",
"addedByUs": "Tarafımızdan eklendi",
"bothAdded": "İki tarafta da eklendi",
"bothDeleted": "İki tarafta da silindi",
"bothModified": "İki tarafta da değiştirildi",
- "committedSection": "Branch'e Commit'lendi",
"committedUnavailable": "Commit'lenen değişiklikler kullanılamıyor.",
"deletedByThem": "Karşı tarafça silindi",
"deletedByUs": "Tarafımızdan silindi",
- "discardFile": "{{path}} değişikliklerini at",
"loadingCommitted": "Commit'lenen değişiklikler yükleniyor…",
"openChangedFile": "Değiştirilen {{path}} dosyasını aç",
"openCommittedChange": "{{path}} için commit'lenen değişikliği aç",
@@ -2852,9 +2831,7 @@
"waitingForDesktop": "Masaüstü bekleniyor…"
},
"gitStatus": {
- "changes": "Değişiklikler",
- "conflicts": "Conflict'ler",
- "stagedChanges": "Stage Edilen Değişiklikler"
+ "conflicts": "Conflict'ler"
},
"header": {
"backToSession": "Oturuma dön",
@@ -2922,7 +2899,6 @@
"waitingForDesktop": "Masaüstü bekleniyor…"
},
"modals": {
- "actionsTitle": "Kaynak Kontrolü",
"currentBranch": "geçerli",
"discardButton": "At",
"discardConfirmMessage": "“{{path}}” dosyasındaki değişiklikler atılsın mı? Bu işlem geri alınamaz.",
@@ -2935,15 +2911,6 @@
"panel": {
"unavailableTitle": "Kaynak Kontrolü Kullanılamıyor"
},
- "prChip": {
- "createPr": "Pull request oluştur",
- "loading": "Pull request yükleniyor…",
- "loadingLabel": "Pull request yükleniyor",
- "openLabel": "#{{number}} numaralı pull request, {{state}}, {{checks}}. Pull request'i aç.",
- "openWithComments_one": "#{{number}} numaralı pull request, {{state}}, {{checks}}, {{count}} çözülmemiş yorum. Pull request'i aç.",
- "openWithComments_other": "#{{number}} numaralı pull request, {{state}}, {{checks}}, {{count}} çözülmemiş yorum. Pull request'i aç.",
- "unavailableLabel": "Pull request kullanılamıyor: {{message}}"
- },
"prCreate": {
"authRequired": "Bir {{reviewLabel}} oluşturmadan önce kimliğinizi doğrulayın.",
"baseNotOnRemote": "Bir {{reviewLabel}} oluşturmadan önce temel branch'i push edin.",
@@ -3031,7 +2998,6 @@
"noBranch": "Branch yok"
},
"state": {
- "noUpstream": "Upstream yok",
"syncStatus": "{{ahead}} ileride, {{behind}} geride"
},
"textGenerationFailure": {
diff --git a/mobile/src/i18n/locales/zh-Hant.json b/mobile/src/i18n/locales/zh-Hant.json
index 4f420e895c8..2377832a35d 100644
--- a/mobile/src/i18n/locales/zh-Hant.json
+++ b/mobile/src/i18n/locales/zh-Hant.json
@@ -2666,7 +2666,6 @@
"checkingReviewStatus": "正在檢查審查狀態。",
"checkoutBeforePublish": "請先檢出分支再發布提交。",
"commitStagedChanges": "提交已暫存的變更。",
- "commits": "提交",
"committing": "正在提交。",
"createPr": "建立 PR",
"enterCommitMessage": "請輸入提交訊息以進行提交。",
@@ -2694,23 +2693,11 @@
"resolveFailed": "無法判定分支基準"
},
"branchCard": {
- "branchCount": "已提交 {{count}} 項",
- "changedCount": "已變更 {{count}} 項",
- "cherryPickState": "挑選",
- "mergeState": "合併",
- "rebaseState": "變基",
- "stagedCount": "已暫存 {{count}} 項",
"completingMerge": "正在完成…",
"completeMerge": "完成合併"
},
"branchCompare": {
- "commits_one": "{{count}} 筆提交",
- "commits_other": "{{count}} 筆提交",
- "files_one": "{{count}} 個檔案",
- "files_other": "{{count}} 個檔案",
- "sectionTitle": "已在分支上提交",
- "summary": "{{files}} - 與 {{baseRef}} 比較",
- "summaryWithCommits": "{{files}} - {{commits}} - 與 {{baseRef}} 比較"
+ "sectionTitle": "已在分支上提交"
},
"branchCompareOutcome": {
"baseBranchUnavailable": "無法判定用於比較的基礎分支。",
@@ -2769,11 +2756,8 @@
"emptyMessage": "工作樹沒有變更。",
"emptyTitle": "沒有本機變更",
"generateMessage": "使用 AI 產生提交訊息",
- "openActions": "開啟原始碼控制操作",
"reconnecting": "正在重新連線至電腦…",
- "regenerateHint": "清除訊息以重新產生。",
- "stageAll": "暫存全部",
- "unstageAll": "全部取消暫存"
+ "regenerateHint": "清除訊息以重新產生。"
},
"createPrAction": {
"checkingStatus": "正在檢查審查狀態…",
@@ -2802,20 +2786,15 @@
"titleUnpublishedForcePushCount_one": "使用租約強制推送此分支的 {{count}} 筆提交,並視需要設定上游分支。",
"titleUnpublishedForcePushCount_other": "使用租約強制推送此分支的 {{count}} 筆提交,並視需要設定上游分支。"
},
- "fileList": {
- "committedSection": "已在分支上提交"
- },
"fileRows": {
"addedByThem": "對方已新增",
"addedByUs": "我方已新增",
"bothAdded": "雙方皆已新增",
"bothDeleted": "雙方皆已刪除",
"bothModified": "雙方皆已修改",
- "committedSection": "已在分支上提交",
"committedUnavailable": "無法取得已提交的變更。",
"deletedByThem": "對方已刪除",
"deletedByUs": "我方已刪除",
- "discardFile": "放棄 {{path}} 的變更",
"loadingCommitted": "正在載入已提交的變更…",
"openChangedFile": "開啟已變更的檔案 {{path}}",
"openCommittedChange": "開啟 {{path}} 的已提交變更",
@@ -2848,9 +2827,7 @@
"waitingForDesktop": "正在等待電腦…"
},
"gitStatus": {
- "changes": "變更",
- "conflicts": "衝突",
- "stagedChanges": "已暫存的變更"
+ "conflicts": "衝突"
},
"header": {
"backToSession": "返回工作階段",
@@ -2918,7 +2895,6 @@
"waitingForDesktop": "等待電腦…"
},
"modals": {
- "actionsTitle": "原始碼控制",
"currentBranch": "目前分支",
"discardButton": "放棄",
"discardConfirmMessage": "要放棄「{{path}}」的變更嗎?此操作無法復原。",
@@ -2931,15 +2907,6 @@
"panel": {
"unavailableTitle": "無法使用原始碼控制"
},
- "prChip": {
- "createPr": "建立 PR",
- "loading": "正在載入 PR…",
- "loadingLabel": "正在載入 PR",
- "openLabel": "PR #{{number}},{{state}},{{checks}}。開啟 PR。",
- "openWithComments_one": "PR #{{number}},{{state}},{{checks}},有 {{count}} 則未解決的留言。開啟 PR。",
- "openWithComments_other": "PR #{{number}},{{state}},{{checks}},有 {{count}} 則未解決的留言。開啟 PR。",
- "unavailableLabel": "無法取得 PR:{{message}}"
- },
"prCreate": {
"authRequired": "請在建立 {{reviewLabel}} 之前完成身分驗證。",
"baseNotOnRemote": "建立 {{reviewLabel}} 前,請先推送基準分支。",
@@ -3027,7 +2994,6 @@
"noBranch": "無分支"
},
"state": {
- "noUpstream": "無上游分支",
"syncStatus": "領先 {{ahead}} 個提交,落後 {{behind}} 個提交"
},
"textGenerationFailure": {
diff --git a/mobile/src/i18n/locales/zh.json b/mobile/src/i18n/locales/zh.json
index 335d1c310fb..4c83f3760a4 100644
--- a/mobile/src/i18n/locales/zh.json
+++ b/mobile/src/i18n/locales/zh.json
@@ -2666,7 +2666,6 @@
"checkingReviewStatus": "正在检查评审状态。",
"checkoutBeforePublish": "请先检出分支再发布提交。",
"commitStagedChanges": "提交已暂存的更改。",
- "commits": "提交",
"committing": "正在提交。",
"createPr": "创建PR",
"enterCommitMessage": "请输入提交信息后再提交。",
@@ -2694,23 +2693,11 @@
"resolveFailed": "无法解析基础分支"
},
"branchCard": {
- "branchCount": "已提交 {{count}} 个文件",
- "changedCount": "已更改 {{count}} 个文件",
- "cherryPickState": "cherry-pick",
- "mergeState": "合并",
- "rebaseState": "变基",
- "stagedCount": "已暂存 {{count}} 个文件",
"completingMerge": "正在完成…",
"completeMerge": "完成合并"
},
"branchCompare": {
- "commits_one": "{{count}} 次提交",
- "commits_other": "{{count}} 次提交",
- "files_one": "{{count}} 个文件",
- "files_other": "{{count}} 个文件",
- "sectionTitle": "已提交的更改",
- "summary": "{{files}} - 与 {{baseRef}} 比较",
- "summaryWithCommits": "{{files}} - {{commits}} - 与 {{baseRef}} 比较"
+ "sectionTitle": "已提交的更改"
},
"branchCompareOutcome": {
"baseBranchUnavailable": "无法解析用于比较的基础分支。",
@@ -2769,11 +2756,8 @@
"emptyMessage": "工作树无更改。",
"emptyTitle": "无本地更改",
"generateMessage": "使用 AI 生成提交信息",
- "openActions": "打开源代码控制操作菜单",
"reconnecting": "正在重新连接桌面…",
- "regenerateHint": "清除消息以重新生成。",
- "stageAll": "暂存全部",
- "unstageAll": "取消全部暂存"
+ "regenerateHint": "清除消息以重新生成。"
},
"createPrAction": {
"checkingStatus": "正在检查评审状态…",
@@ -2802,20 +2786,15 @@
"titleUnpublishedForcePushCount_one": "使用 lease 强制推送 {{count}} 个分支提交,并在需要时设置上游。",
"titleUnpublishedForcePushCount_other": "使用 lease 强制推送 {{count}} 个分支提交,并在需要时设置上游。"
},
- "fileList": {
- "committedSection": "已提交的更改"
- },
"fileRows": {
"addedByThem": "对方已添加",
"addedByUs": "我方已添加",
"bothAdded": "双方均已添加",
"bothDeleted": "双方均已删除",
"bothModified": "双方均已修改",
- "committedSection": "已提交的更改",
"committedUnavailable": "已提交的更改不可用。",
"deletedByThem": "对方已删除",
"deletedByUs": "我方已删除",
- "discardFile": "放弃 {{path}} 的更改",
"loadingCommitted": "正在加载已提交的更改…",
"openChangedFile": "打开已更改的文件 {{path}}",
"openCommittedChange": "打开 {{path}} 中已提交的更改",
@@ -2848,9 +2827,7 @@
"waitingForDesktop": "正在等待桌面…"
},
"gitStatus": {
- "changes": "更改",
- "conflicts": "冲突",
- "stagedChanges": "已暂存的更改"
+ "conflicts": "冲突"
},
"header": {
"backToSession": "返回会话",
@@ -2918,7 +2895,6 @@
"waitingForDesktop": "正在等待桌面…"
},
"modals": {
- "actionsTitle": "源代码控制",
"currentBranch": "当前",
"discardButton": "放弃",
"discardConfirmMessage": "放弃对“{{path}}”的更改?此操作无法撤销。",
@@ -2931,15 +2907,6 @@
"panel": {
"unavailableTitle": "源代码控制不可用"
},
- "prChip": {
- "createPr": "创建 PR",
- "loading": "正在加载 PR…",
- "loadingLabel": "正在加载 PR",
- "openLabel": "PR #{{number}},{{state}},{{checks}}。打开 PR。",
- "openWithComments_one": "PR #{{number}},{{state}},{{checks}},{{count}} 条未解决的评论。打开 PR。",
- "openWithComments_other": "PR #{{number}},{{state}},{{checks}},{{count}} 条未解决的评论。打开 PR。",
- "unavailableLabel": "PR 不可用:{{message}}"
- },
"prCreate": {
"authRequired": "请在创建 {{reviewLabel}} 之前完成身份验证。",
"baseNotOnRemote": "请先推送基础分支,再创建 {{reviewLabel}}。",
@@ -3027,7 +2994,6 @@
"noBranch": "无分支"
},
"state": {
- "noUpstream": "无上游分支",
"syncStatus": "领先 {{ahead}},落后 {{behind}}"
},
"textGenerationFailure": {
diff --git a/mobile/src/source-control/MobileSourceControlBranchCard.tsx b/mobile/src/source-control/MobileSourceControlBranchCard.tsx
deleted file mode 100644
index 44862bcddb9..00000000000
--- a/mobile/src/source-control/MobileSourceControlBranchCard.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import { Pressable, Text, View } from 'react-native'
-import { GitBranch } from 'lucide-react-native'
-import { colors } from '../theme/mobile-theme'
-import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
-import { useMobileTranslation } from '../i18n/use-mobile-translation'
-import { styles } from './mobile-source-control-styles'
-import { MobileSourceControlPrChip } from './MobileSourceControlPrChip'
-import type { MobilePrChipSummary } from './mobile-pr-chip-summary'
-import { mobileConflictAbortLabel } from './mobile-source-control-conflict-abort'
-
-type Props = {
- branchLabel: string
- syncLabel: string | null
- unstagedCount: number
- stagedCount: number
- branchCount: number
- conflictOperation: string | null
- conflictBusy: boolean
- conflictAborting: boolean
- onAbortConflict: (operation: string) => void
- prChip: MobilePrChipSummary | null
- onOpenPr: () => void
-}
-
-export function MobileSourceControlBranchCard({
- branchLabel,
- syncLabel,
- unstagedCount,
- stagedCount,
- branchCount,
- conflictOperation,
- conflictBusy,
- conflictAborting,
- onAbortConflict,
- prChip,
- onOpenPr
-}: Props) {
- useMobileColorScheme()
- const t = useMobileTranslation()
- const showConflict = conflictOperation !== null && conflictOperation !== 'unknown'
- const conflictLabel =
- conflictOperation === 'merge'
- ? t('sourceControl.branchCard.mergeState', 'merge')
- : conflictOperation === 'rebase'
- ? t('sourceControl.branchCard.rebaseState', 'rebase')
- : conflictOperation === 'cherry-pick'
- ? t('sourceControl.branchCard.cherryPickState', 'cherry-pick')
- : conflictOperation
- return (
-
-
-
-
-
- {branchLabel}
-
-
- {syncLabel ? {syncLabel} : null}
-
-
-
- {t('sourceControl.branchCard.changedCount', '{{count}} changed', {
- count: unstagedCount
- })}
-
-
- {t('sourceControl.branchCard.stagedCount', '{{count}} staged', { count: stagedCount })}
-
- {branchCount > 0 ? (
-
- {t('sourceControl.branchCard.branchCount', '{{count}} on branch', {
- count: branchCount
- })}
-
- ) : null}
-
- {/* Keep Abort on its own row so long counts cannot overflow the card. */}
- {showConflict ? (
-
- {conflictLabel}
- {conflictOperation === 'merge' || conflictOperation === 'rebase' ? (
- [
- styles.abortButton,
- conflictBusy && styles.abortButtonDisabled,
- pressed && !conflictBusy && styles.abortPressed
- ]}
- disabled={conflictBusy}
- onPress={() => onAbortConflict(conflictOperation)}
- >
-
- {mobileConflictAbortLabel(conflictOperation, conflictAborting)}
-
-
- ) : null}
-
- ) : null}
- {prChip ? : null}
-
- )
-}
diff --git a/mobile/src/source-control/MobileSourceControlChangesHeader.tsx b/mobile/src/source-control/MobileSourceControlChangesHeader.tsx
new file mode 100644
index 00000000000..c7e8e4ccf8f
--- /dev/null
+++ b/mobile/src/source-control/MobileSourceControlChangesHeader.tsx
@@ -0,0 +1,192 @@
+import { Pressable, Text, View } from 'react-native'
+import {
+ ChevronDown,
+ ChevronRight,
+ GitBranch,
+ GitCommitHorizontal,
+ TriangleAlert
+} from 'lucide-react-native'
+import { colors } from '../theme/mobile-theme'
+import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
+import type { Translate } from '../i18n/mobile-i18n'
+import { useMobileTranslation } from '../i18n/use-mobile-translation'
+import type { GitConflictOperation } from '../../../src/shared/git-status-types'
+import {
+ isMobileConflictAborting,
+ mobileConflictAbortLabel
+} from './mobile-source-control-conflict-abort'
+import type { MobileSourceControlState } from './use-mobile-source-control-state'
+import { styles } from './mobile-source-control-styles'
+
+type HeaderState = Pick<
+ MobileSourceControlState,
+ | 'status'
+ | 'branchLabel'
+ | 'branchCompareResult'
+ | 'busyAction'
+ | 'openBranchPicker'
+ | 'abortConflictOperation'
+>
+
+function syncMeta(
+ status: MobileSourceControlState['status'],
+ operation: GitConflictOperation | null,
+ t: Translate
+): string | null {
+ switch (operation) {
+ case 'merge':
+ return t('sourceControl.changesHeader.merging', 'Merging')
+ case 'rebase':
+ return t('sourceControl.changesHeader.rebasing', 'Rebasing')
+ case 'cherry-pick':
+ return t('sourceControl.changesHeader.cherryPicking', 'Cherry-picking')
+ case 'unknown':
+ case null:
+ break
+ }
+ const upstream = status?.upstreamStatus
+ if (!upstream) {
+ return null
+ }
+ if (!upstream.hasUpstream) {
+ return t('sourceControl.changesHeader.notPublished', 'Not published')
+ }
+ if (upstream.ahead > 0 && upstream.behind > 0) {
+ return t('sourceControl.state.syncStatus', '{{ahead}} ahead, {{behind}} behind', upstream)
+ }
+ if (upstream.ahead > 0) {
+ return t('sourceControl.changesHeader.notPushed', '{{count}} not pushed', {
+ count: upstream.ahead
+ })
+ }
+ if (upstream.behind > 0) {
+ return t('sourceControl.changesHeader.behind', '{{count}} behind', { count: upstream.behind })
+ }
+ return t('sourceControl.changesHeader.upToDate', 'Up to date')
+}
+
+/** Above the Changes list: the branch and its sync state, the Commits row, and a paused merge. */
+export function MobileSourceControlChangesHeader({
+ state,
+ onOpenCommits
+}: {
+ state: HeaderState
+ onOpenCommits: () => void
+}) {
+ useMobileColorScheme()
+ const t = useMobileTranslation()
+ const { status, busyAction } = state
+ const rawOperation = status?.conflictOperation ?? null
+ const operation = rawOperation === 'unknown' ? null : rawOperation
+ const meta = syncMeta(status, operation, t)
+ const commits = state.branchCompareResult?.summary.commitsAhead ?? 0
+ const unresolved =
+ status?.entries.filter((entry) => entry.conflictStatus === 'unresolved').length ?? 0
+ const aborting = isMobileConflictAborting(busyAction, operation)
+ return (
+
+
+ [styles.branchButton, pressed && styles.sectionActionPressed]}
+ onPress={state.openBranchPicker}
+ disabled={busyAction !== null}
+ accessibilityRole="button"
+ accessibilityLabel={t(
+ 'sourceControl.changesHeader.switchBranch',
+ 'Switch branch from {{branch}}',
+ {
+ branch: state.branchLabel
+ }
+ )}
+ >
+
+
+ {state.branchLabel}
+
+
+
+ {meta ? {meta} : null}
+
+ {operation ? (
+
+
+
+
+ {operation === 'merge'
+ ? t('sourceControl.changesHeader.mergeInProgress', 'Merge in progress')
+ : operation === 'rebase'
+ ? t('sourceControl.changesHeader.rebaseInProgress', 'Rebase in progress')
+ : t(
+ 'sourceControl.changesHeader.cherryPickInProgress',
+ 'Cherry-pick in progress'
+ )}
+
+ {operation === 'merge' || operation === 'rebase' ? (
+ [
+ styles.abortButton,
+ busyAction !== null && styles.abortButtonDisabled,
+ pressed && busyAction === null && styles.abortPressed
+ ]}
+ disabled={busyAction !== null}
+ onPress={() => void state.abortConflictOperation(operation)}
+ accessibilityLabel={mobileConflictAbortLabel(operation, aborting)}
+ >
+
+ {mobileConflictAbortLabel(operation, aborting)}
+
+
+ ) : null}
+
+
+ {unresolved > 0
+ ? unresolved === 1
+ ? t(
+ 'sourceControl.changesHeader.filesNeedDecision_one',
+ '{{count}} file needs a decision before you can finish.',
+ { count: unresolved }
+ )
+ : t(
+ 'sourceControl.changesHeader.filesNeedDecision_other',
+ '{{count}} files need a decision before you can finish.',
+ { count: unresolved }
+ )
+ : status?.didHitLimit === true
+ ? // A capped listing can hide the remaining conflicts; never claim they're done.
+ t(
+ 'sourceControl.changesHeader.conflictsMayRemain',
+ 'Not every file is listed, so conflicts may remain. Check on your computer.'
+ )
+ : operation === 'merge'
+ ? t(
+ 'sourceControl.changesHeader.conflictsResolved',
+ 'Every conflict is resolved. Finish from the bar below.'
+ )
+ : // Mobile has no continue for a rebase or cherry-pick.
+ t(
+ 'sourceControl.changesHeader.continueOnComputer',
+ 'Every conflict is resolved. Continue on your computer to finish.'
+ )}
+
+
+ ) : null}
+ {commits > 0 ? (
+ [styles.commitsRow, pressed && styles.fileRowPressed]}
+ onPress={onOpenCommits}
+ accessibilityRole="button"
+ >
+
+
+ {commits === 1
+ ? t('sourceControl.changesHeader.commits_one', '{{count}} commit', { count: commits })
+ : t('sourceControl.changesHeader.commits_other', '{{count}} commits', {
+ count: commits
+ })}
+
+
+
+ ) : null}
+
+ )
+}
diff --git a/mobile/src/source-control/MobileSourceControlContent.test.tsx b/mobile/src/source-control/MobileSourceControlContent.test.tsx
index 9716e64b8b3..c40b3d2f57e 100644
--- a/mobile/src/source-control/MobileSourceControlContent.test.tsx
+++ b/mobile/src/source-control/MobileSourceControlContent.test.tsx
@@ -9,7 +9,6 @@ import type { MobileSourceControlState } from './use-mobile-source-control-state
type MobileSourceControlContentState = Omit & {
primaryAction: MobileSourceControlPrimaryAction
}
-import * as branchFormat from './mobile-branch-entry-format'
import type { SectionListProps } from 'react-native'
import type {
MobileBranchEntryView,
@@ -23,7 +22,8 @@ vi.mock('react-native', () => ({
SectionList: (props: SectionListProps) =>
createElement(
'SectionList',
- props,
+ { ...props, ListHeaderComponent: undefined },
+ typeof props.ListHeaderComponent === 'function' ? null : props.ListHeaderComponent,
typeof props.ListFooterComponent === 'function'
? createElement(props.ListFooterComponent)
: props.ListFooterComponent,
@@ -53,16 +53,42 @@ vi.mock('react-native', () => ({
View: 'View'
}))
vi.mock('expo-clipboard', () => ({ setStringAsync: async () => {} }))
+// Counts row renders: every file row draws its line stats once.
+const rowRenders = vi.hoisted(() => ({ count: 0 }))
+vi.mock('../components/MobileDiffReviewFileSummary', () => ({
+ MobileDiffLineStats: () => {
+ rowRenders.count += 1
+ return null
+ }
+}))
+vi.mock('react-native-gesture-handler', async () => {
+ const chain = new Proxy({}, { get: () => () => chain })
+ return {
+ Gesture: { Pan: () => chain },
+ GestureDetector: ({ children }: { children: unknown }) => children
+ }
+})
+vi.mock('react-native-reanimated', () => ({
+ default: { View: ({ children }: { children: unknown }) => children },
+ Easing: { bezier: () => () => 0 },
+ ReduceMotion: { Never: 'never' },
+ useAnimatedStyle: () => ({}),
+ useReducedMotion: () => false,
+ useSharedValue: (value: number) => ({ value }),
+ withTiming: (value: number) => value
+}))
vi.mock('../components/ConfirmModal', () => ({ ConfirmModal: () => null }))
vi.mock('lucide-react-native', () => ({
ArrowDown: 'ArrowDown',
ArrowDownUp: 'ArrowDownUp',
ArrowUp: 'ArrowUp',
Check: 'Check',
+ ChevronDown: 'ChevronDown',
ChevronRight: 'ChevronRight',
CloudUpload: 'CloudUpload',
FileText: 'FileText',
GitBranch: 'GitBranch',
+ GitCommitHorizontal: 'GitCommitHorizontal',
GitMerge: 'GitMerge',
GitPullRequest: 'GitPullRequest',
GitPullRequestArrow: 'GitPullRequestArrow',
@@ -72,7 +98,9 @@ vi.mock('lucide-react-native', () => ({
Plus: 'Plus',
RefreshCw: 'RefreshCw',
Sparkles: 'Sparkles',
- Trash2: 'Trash2'
+ Trash2: 'Trash2',
+ TriangleAlert: 'TriangleAlert',
+ Undo2: 'Undo2'
}))
const noop = () => {}
@@ -154,7 +182,6 @@ function initialState(): MobileSourceControlContentState {
status: null,
sections: [],
branchCompareResult,
- branchCompareSummaryText: '20 files vs main',
branchEntries,
shouldShowBranchCompareSection: true,
hasVisibleChanges: true,
@@ -164,7 +191,6 @@ function initialState(): MobileSourceControlContentState {
unstagedCount: 0,
branchLabel: 'fixture',
upstreamKnown: false,
- syncLabel: null,
primaryAction: {
kind: 'commit',
label: 'Commit',
@@ -227,7 +253,11 @@ function Fixture({ initial }: { initial: MobileSourceControlContentState }) {
return createElement(
'View',
null,
- createElement(MobileSourceControlContent, { state: liveState, bottomInset: 0 }),
+ createElement(MobileSourceControlContent, {
+ state: liveState,
+ bottomInset: 0,
+ onOpenCommits: noop
+ }),
createElement(MobileSourceControlNextStepBar, {
view: { kind: 'composer' },
notice: null,
@@ -263,7 +293,10 @@ function rows() {
)
}
function text() {
- return JSON.stringify(renderer?.toJSON())
+ // Section headers carry React elements in props; serialize only what renders.
+ return JSON.stringify(renderer?.toJSON(), (key, value) =>
+ key === 'sections' ? undefined : value
+ )
}
afterEach(() => {
@@ -279,10 +312,10 @@ describe('committed branch files during commit-message typing', () => {
if (layout === 'dirty') {
initial.sections = [{ id: 'staged', area: 'staged', title: 'Staged Changes', data: [] }]
}
- const format = vi.spyOn(branchFormat, 'formatMobileBranchEntryMeta')
+ rowRenders.count = 0
render(initial)
- expect(format).toHaveBeenCalledTimes(20)
- format.mockClear()
+ expect(rowRenders.count).toBe(20)
+ rowRenders.count = 0
for (let index = 0; index < 50; index++) {
act(() => {
const input = renderer?.root.find((node) => String(node.type) === 'TextInput')
@@ -296,7 +329,7 @@ describe('committed branch files during commit-message typing', () => {
'message 49'
)
expect(rows()).toHaveLength(20)
- expect(format).not.toHaveBeenCalled()
+ expect(rowRenders.count).toBe(0)
})
it.each([false, true])(
@@ -324,10 +357,7 @@ describe('committed branch files during commit-message typing', () => {
it('updates rows and section messages when their inputs change', () => {
const initial = initialState()
- const format = vi.spyOn(branchFormat, 'formatMobileBranchEntryMeta')
render(initial)
- patch({ branchCompareSummaryText: 'changed summary' })
- expect(text()).toContain('changed summary')
const first = initial.branchEntries[0]
if (!first) {
throw new Error('missing fixture entry')
@@ -370,14 +400,12 @@ describe('committed branch files during commit-message typing', () => {
patch({ branchCompareResult: initial.branchCompareResult })
expect(rows()).toHaveLength(1)
patch({ shouldShowBranchCompareSection: false })
- expect(text()).not.toContain('Committed on Branch')
+ expect(text()).not.toContain('On this branch')
patch({ shouldShowBranchCompareSection: true })
expect(rows()).toHaveLength(1)
- format.mockClear()
- patch({ connState: 'disconnected', actionError: 'offline', keyboardLift: 100 })
+ patch({ connState: 'disconnected', actionError: 'offline' })
expect(text()).toContain('Reconnecting to desktop')
expect(text()).toContain('offline')
- expect(format).not.toHaveBeenCalled()
})
})
diff --git a/mobile/src/source-control/MobileSourceControlContent.tsx b/mobile/src/source-control/MobileSourceControlContent.tsx
index 8742c66e25d..aace32ca2d9 100644
--- a/mobile/src/source-control/MobileSourceControlContent.tsx
+++ b/mobile/src/source-control/MobileSourceControlContent.tsx
@@ -1,11 +1,11 @@
import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
-import { ActivityIndicator, Animated, Easing, Pressable, Text, View } from 'react-native'
-import { Minus, MoreHorizontal, Plus } from 'lucide-react-native'
+import { ActivityIndicator, Animated, Easing, Text, View } from 'react-native'
import { colors, spacing } from '../theme/mobile-theme'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { useReducedMotionEnabled } from '../platform/use-reduced-motion'
import { SHEET_CURVE } from '../components/sheet-motion-values'
+import { MobileSourceControlChangesHeader } from './MobileSourceControlChangesHeader'
import { MobileCommitFailurePanel } from './MobileCommitFailurePanel'
import { MobileSourceControlFileList } from './MobileSourceControlFileList'
import type { MobileSourceControlState } from './use-mobile-source-control-state'
@@ -44,31 +44,13 @@ type Props = {
state: Omit
/** Clears the next-step bar the panel floats over the list. */
bottomInset: number
+ onOpenCommits: () => void
}
-export function MobileSourceControlContent({ state, bottomInset }: Props) {
+export function MobileSourceControlContent({ state, bottomInset, onOpenCommits }: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
- const {
- connState,
- busyAction,
- setShowActionSheet,
- setDiscardTarget,
- actionError,
- commitFailureRecovery,
- commitFailureRecoveryAction,
- sections,
- hasVisibleChanges,
- status,
- stageablePaths,
- unstageablePaths,
- stageAll,
- unstageAll,
- openDiffForFile,
- resolveConflict,
- runGitAction
- } = state
- const ioBusy = busyAction !== null
+ const { busyAction, hasVisibleChanges } = state
// Commit runners keep busyAction set ('commit…') until the refreshed status has landed.
const [listShown, setListShown] = useState(hasVisibleChanges)
const [cleanedByCommit, setCleanedByCommit] = useState(false)
@@ -76,56 +58,111 @@ export function MobileSourceControlContent({ state, bottomInset }: Props) {
setListShown(hasVisibleChanges)
setCleanedByCommit(!hasVisibleChanges && busyAction?.startsWith('commit') === true)
}
- // A capped all-staged prefix can hide unstaged rows.
- const canStageAll = stageablePaths.length > 0 || status?.didHitLimit === true
- const listBottomInset = bottomInset + spacing.lg
- const fileList = useMemo(
+ // Pick only what the list reads, so commit-message keystrokes never re-render its rows.
+ const {
+ connState,
+ status,
+ branchLabel,
+ branchCompareResult,
+ actionError,
+ commitFailureRecovery,
+ commitFailureRecoveryAction,
+ openBranchPicker,
+ abortConflictOperation,
+ sections,
+ shouldShowBranchCompareSection,
+ branchEntries,
+ branchCompareState,
+ stageablePaths,
+ unstageablePaths,
+ stageAll,
+ unstageAll,
+ openDiffForFile,
+ resolveConflict,
+ runGitAction,
+ setDiscardTarget
+ } = state
+ const header = useMemo(
() => (
-
),
[
- listBottomInset,
- sections,
+ abortConflictOperation,
+ branchCompareResult,
+ branchLabel,
+ busyAction,
+ onOpenCommits,
+ openBranchPicker,
+ status
+ ]
+ )
+ const list = useMemo(
+ () => (
+
+ ),
+ [
+ bottomInset,
+ branchCompareResult,
+ branchCompareState,
+ branchEntries,
+ busyAction,
+ header,
openDiffForFile,
resolveConflict,
runGitAction,
+ sections,
setDiscardTarget,
- state.shouldShowBranchCompareSection,
- state.branchCompareSummaryText,
- state.branchEntries,
- state.branchCompareState,
- state.branchCompareResult,
- busyAction
+ shouldShowBranchCompareSection,
+ stageAll,
+ stageablePaths,
+ status,
+ unstageAll,
+ unstageablePaths
]
)
-
- return (
- <>
- {connState !== 'connected' ? (
- // Retained rows look actionable offline; show reconnect state beside them.
-
-
-
- {t('sourceControl.content.reconnecting', 'Reconnecting to desktop...')}
-
-
- ) : null}
+ // Outside the list, so a failure stays in view however far the rows are scrolled.
+ const feedback =
+ connState !== 'connected' || commitFailureRecovery || actionError ? (
+ {connState !== 'connected' ? (
+ // Retained rows look actionable offline; show reconnect state beside them.
+
+
+
+ {t('sourceControl.content.reconnecting', 'Reconnecting to desktop...')}
+
+
+ ) : null}
{commitFailureRecovery ? (
) : null}
-
- [
- styles.bulkButton,
- (!canStageAll || ioBusy) && styles.bulkButtonDisabled,
- pressed && styles.bulkButtonPressed
- ]}
- onPress={() => void stageAll()}
- disabled={ioBusy || !canStageAll}
- >
- {busyAction === 'stage-all' ? (
-
- ) : (
-
- )}
-
- {t('sourceControl.content.stageAll', 'Stage All')}
-
-
- [
- styles.bulkButton,
- (unstageablePaths.length === 0 || ioBusy) && styles.bulkButtonDisabled,
- pressed && styles.bulkButtonPressed
- ]}
- onPress={() => void unstageAll()}
- disabled={ioBusy || unstageablePaths.length === 0}
- >
- {busyAction === 'unstage-all' ? (
-
- ) : (
-
- )}
-
- {t('sourceControl.content.unstageAll', 'Unstage All')}
-
-
- [
- styles.bulkMenuButton,
- pressed && styles.bulkButtonPressed,
- ioBusy && styles.bulkButtonDisabled
- ]}
- onPress={() => setShowActionSheet(true)}
- disabled={ioBusy}
- hitSlop={8}
- accessibilityLabel={t(
- 'sourceControl.content.openActions',
- 'Open source control actions'
- )}
- >
-
-
-
-
- {!hasVisibleChanges ? (
+ ) : null
+ if (!hasVisibleChanges) {
+ return (
+
+ {feedback}
+ {header}
{t('sourceControl.content.emptyTitle', 'No local changes')}
@@ -203,9 +190,13 @@ export function MobileSourceControlContent({ state, bottomInset }: Props) {
{t('sourceControl.content.emptyMessage', 'Working tree is clean.')}
- ) : (
- fileList
- )}
- >
+
+ )
+ }
+ return (
+
+ {feedback}
+ {list}
+
)
}
diff --git a/mobile/src/source-control/MobileSourceControlFileList.tsx b/mobile/src/source-control/MobileSourceControlFileList.tsx
index ed9807375f1..c416c93094d 100644
--- a/mobile/src/source-control/MobileSourceControlFileList.tsx
+++ b/mobile/src/source-control/MobileSourceControlFileList.tsx
@@ -1,10 +1,8 @@
-import { SectionList, View } from 'react-native'
+import type { ReactNode } from 'react'
+import { ActivityIndicator, Pressable, SectionList, Text, View } from 'react-native'
import { SectionHeader } from '../components/list/ListGroup'
-import {
- BranchCompareHeader,
- BranchFileRow,
- makeRenderFileRow
-} from './MobileSourceControlFileRows'
+import { colors } from '../theme/mobile-theme'
+import { BranchFileRow, ChangeFileRow, type FileRowState } from './MobileSourceControlFileRows'
import type {
MobileBranchEntryView,
MobileGitStatusEntryView
@@ -20,57 +18,124 @@ function isWorkingTreeEntry(entry: FileEntry): entry is MobileGitStatusEntryView
return typeof entry.stageActionId === 'string'
}
-type FileSection = { key: string; title: string; data: readonly FileEntry[] }
+type FileSection = {
+ key: string
+ title: string
+ trailing?: ReactNode
+ data: readonly FileEntry[]
+}
-export type FileListState = Pick<
- MobileSourceControlState,
- | 'sections'
- | 'shouldShowBranchCompareSection'
- | 'branchCompareSummaryText'
- | 'branchEntries'
- | 'branchCompareState'
- | 'branchCompareResult'
- | 'busyAction'
- | 'openDiffForFile'
- | 'resolveConflict'
- | 'runGitAction'
- | 'setDiscardTarget'
->
+export type FileListState = FileRowState &
+ Pick<
+ MobileSourceControlState,
+ | 'sections'
+ | 'status'
+ | 'shouldShowBranchCompareSection'
+ | 'branchEntries'
+ | 'branchCompareState'
+ | 'branchCompareResult'
+ | 'stageablePaths'
+ | 'unstageablePaths'
+ | 'stageAll'
+ | 'unstageAll'
+ >
export function MobileSourceControlFileList({
state,
+ header,
bottomInset
}: {
state: FileListState
- /** Clears the floating commit bar, which grows with its message. */
+ /** The status line, Commits row and conflict banner above the sections. */
+ header: ReactNode
+ /** Clears the next-step bar the panel floats over the list. */
bottomInset: number
}) {
const colorScheme = useMobileColorScheme()
const t = useMobileTranslation()
- const sections: FileSection[] = state.sections.map((section) => ({
- ...section,
- key: section.id
- }))
+ const busy = state.busyAction !== null
+ const merging = state.status?.conflictOperation === 'merge'
+ // A capped all-staged prefix can hide unstaged rows.
+ const canStageAll = state.stageablePaths.length > 0 || state.status?.didHitLimit === true
+ const sectionAction = (label: string, onPress: () => void, enabled: boolean) => (
+ [
+ (busy || !enabled) && styles.iconButtonDisabled,
+ pressed && styles.sectionActionPressed
+ ]}
+ >
+ {label}
+
+ )
+ // A capped listing can hide every unstaged file behind a staged prefix; keep Stage all reachable.
+ const cappedWithoutUnstaged =
+ state.status?.didHitLimit === true &&
+ !state.sections.some((section) => section.id === 'unstaged')
+ const stageAllOnStaged =
+ cappedWithoutUnstaged && state.sections.some((section) => section.id === 'staged')
+ const stageAllOnConflicts = cappedWithoutUnstaged && !stageAllOnStaged
+ const stageAllAction = sectionAction(
+ t('sourceControl.fileList.stageAll', 'Stage all'),
+ () => void state.stageAll(),
+ canStageAll
+ )
+ const sections: FileSection[] = state.sections.map((section) => {
+ const title =
+ merging && section.id === 'staged'
+ ? t('sourceControl.fileList.mergedCleanly', 'Merged cleanly')
+ : section.title
+ return {
+ key: section.id,
+ title: `${title} ${section.data.length}`,
+ trailing:
+ section.id === 'unstaged' || (section.id === 'conflicts' && stageAllOnConflicts) ? (
+ stageAllAction
+ ) : section.id === 'staged' ? (
+
+ {stageAllOnStaged ? stageAllAction : null}
+ {merging
+ ? null
+ : sectionAction(
+ t('sourceControl.fileList.unstageAll', 'Unstage all'),
+ () => void state.unstageAll(),
+ state.unstageablePaths.length > 0
+ )}
+
+ ) : undefined,
+ data: section.data
+ }
+ })
+ const compare = state.branchCompareResult
if (state.shouldShowBranchCompareSection) {
+ const ready = state.branchCompareState.kind === 'ready' && compare?.summary.status === 'ready'
sections.push({
key: 'committed',
- title: t('sourceControl.fileList.committedSection', 'Committed on Branch'),
- data:
- state.branchCompareState.kind === 'ready' &&
- state.branchCompareResult?.summary.status === 'ready'
- ? state.branchEntries
- : []
+ title: t('sourceControl.fileList.onThisBranch', 'On this branch {{count}}', {
+ count: state.branchEntries.length
+ }),
+ trailing: compare?.summary.baseRef ? (
+
+ {t('sourceControl.fileList.versusBase', 'vs {{base}}', {
+ base: compare.summary.baseRef.replace(/^origin\//, '')
+ })}
+
+ ) : undefined,
+ data: ready ? state.branchEntries : []
})
}
- const renderFileRow = makeRenderFileRow(state, t)
return (
style={hubStyles.tabBody}
sections={sections}
extraData={colorScheme}
+ ListHeaderComponent={<>{header}>}
renderItem={({ item }) =>
isWorkingTreeEntry(item) ? (
- renderFileRow({ item })
+
) : (
)
@@ -78,17 +143,43 @@ export function MobileSourceControlFileList({
keyExtractor={(item) =>
`${isWorkingTreeEntry(item) ? item.area : 'committed'}:${item.path}:${item.oldPath ?? ''}`
}
- renderSectionHeader={({ section }) =>
- section.key === 'committed' ? (
-
- ) : (
+ renderSectionHeader={({ section }) => (
+
-
+
- )
- }
+ {section.key === 'committed' ? : null}
+
+ )}
stickySectionHeadersEnabled={false}
contentContainerStyle={[styles.listContent, { paddingBottom: bottomInset }]}
/>
)
}
+
+// Loading or failure under "On this branch", where its rows would be.
+function BranchCompareState({ state }: { state: FileListState }) {
+ useMobileColorScheme()
+ const t = useMobileTranslation()
+ const { branchCompareState, branchCompareResult } = state
+ const message =
+ branchCompareState.kind === 'error'
+ ? branchCompareState.message
+ : branchCompareResult && branchCompareResult.summary.status !== 'ready'
+ ? (branchCompareResult.summary.errorMessage ??
+ t('sourceControl.fileRows.committedUnavailable', 'Committed changes unavailable.'))
+ : null
+ if (branchCompareState.kind !== 'loading' && message === null) {
+ return null
+ }
+ return (
+
+ {branchCompareState.kind === 'loading' ? (
+
+ ) : null}
+
+ {message ?? t('sourceControl.fileRows.loadingCommitted', 'Loading committed changes...')}
+
+
+ )
+}
diff --git a/mobile/src/source-control/MobileSourceControlFileList.web.test.tsx b/mobile/src/source-control/MobileSourceControlFileList.web.test.tsx
index 04a409dc2c7..b84e3071a72 100644
--- a/mobile/src/source-control/MobileSourceControlFileList.web.test.tsx
+++ b/mobile/src/source-control/MobileSourceControlFileList.web.test.tsx
@@ -5,7 +5,24 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { MobileSourceControlFileList, type FileListState } from './MobileSourceControlFileList'
vi.mock('react-native', () => vi.importActual('react-native-web'))
+vi.mock('react-native-gesture-handler', async () => {
+ const chain = new Proxy({}, { get: () => () => chain })
+ return {
+ Gesture: { Pan: () => chain },
+ GestureDetector: ({ children }: { children: unknown }) => children
+ }
+})
+vi.mock('react-native-reanimated', () => ({
+ default: { View: ({ children }: { children: unknown }) => children },
+ Easing: { bezier: () => () => 0 },
+ ReduceMotion: { Never: 'never' },
+ useAnimatedStyle: () => ({}),
+ useReducedMotion: () => false,
+ useSharedValue: (value: number) => ({ value }),
+ withTiming: (value: number) => value
+}))
vi.mock('lucide-react-native', () => ({
+ Undo2: () => null,
ArrowDown: () => null,
ArrowDownUp: () => null,
ArrowUp: () => null,
@@ -66,8 +83,12 @@ function fileListState(count: number, dirty: boolean): FileListState {
}
]
: [],
+ status: null,
+ stageablePaths: dirty ? ['src/uncommitted.ts'] : [],
+ unstageablePaths: [],
+ stageAll: vi.fn(async () => {}),
+ unstageAll: vi.fn(async () => {}),
shouldShowBranchCompareSection: true,
- branchCompareSummaryText: `${count} files vs main`,
branchEntries,
branchCompareState: { kind: 'ready', result: branchCompareResult },
branchCompareResult,
@@ -102,14 +123,19 @@ describe('source-control file virtualization in the mobile web page', () => {
const state = fileListState(count, dirty)
await act(async () => {
root.render(
- createElement(MobileSourceControlFileList, { key: count, state, bottomInset: 0 })
+ createElement(MobileSourceControlFileList, {
+ key: count,
+ state,
+ header: null,
+ bottomInset: 0
+ })
)
})
const rows = container.querySelectorAll('[aria-label^="Open committed change "]')
mountedCounts.push(rows.length)
expect(rows.length).toBeGreaterThan(0)
expect(rows.length).toBeLessThan(50)
- expect(container.textContent).toContain(`${count} files vs main`)
+ expect(container.textContent).toContain(`On this branch ${count}`)
expect(
container.querySelector('[aria-label="Open committed change src/committed-999.ts"]')
).toBeNull()
diff --git a/mobile/src/source-control/MobileSourceControlFileRows.tsx b/mobile/src/source-control/MobileSourceControlFileRows.tsx
index 01b4bb26f1c..85cb7dbad3c 100644
--- a/mobile/src/source-control/MobileSourceControlFileRows.tsx
+++ b/mobile/src/source-control/MobileSourceControlFileRows.tsx
@@ -1,13 +1,23 @@
+import type { ReactNode } from 'react'
import { ActivityIndicator, Pressable, Text, View } from 'react-native'
-import { ChevronRight, FileText, Minus, Plus, Trash2 } from 'lucide-react-native'
+import { Gesture, GestureDetector } from 'react-native-gesture-handler'
+import Animated, {
+ Easing,
+ ReduceMotion,
+ useAnimatedStyle,
+ useSharedValue,
+ withTiming
+} from 'react-native-reanimated'
+import { SHEET_CURVE } from '../components/sheet-motion-values'
+import { useDrawerReducedMotion } from '../components/bottom-drawer-constants'
+import { Minus, Plus, Undo2 } from 'lucide-react-native'
import { colors } from '../theme/mobile-theme'
-import { SectionHeader } from '../components/list/ListGroup'
import { STATUS_LABELS } from '../../../src/shared/git-status-labels'
-import type { GitConflictKind } from '../../../src/shared/git-status-types'
+import type { GitConflictKind, GitFileStatus } from '../../../src/shared/git-status-types'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import type { Translate } from '../i18n/mobile-i18n'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
-import { formatMobileBranchEntryMeta } from './mobile-branch-entry-format'
+import { MobileDiffLineStats } from '../components/MobileDiffReviewFileSummary'
import type {
MobileBranchEntryView,
MobileGitStatusEntryView
@@ -17,7 +27,7 @@ import { styles } from './mobile-source-control-styles'
import { mobileConflictResolutionLabel } from './mobile-source-control-conflict-actions'
import { getUnstageablePaths, statusColor } from './mobile-git-status'
-type RowState = Pick<
+export type FileRowState = Pick<
MobileSourceControlState,
'busyAction' | 'openDiffForFile' | 'resolveConflict' | 'runGitAction' | 'setDiscardTarget'
>
@@ -43,227 +53,288 @@ function conflictKindLabel(kind: GitConflictKind | undefined, t: Translate): str
}
}
-export function makeRenderFileRow(state: RowState, t: Translate) {
- const { busyAction, openDiffForFile, resolveConflict, runGitAction, setDiscardTarget } = state
- return function renderFileRow({ item }: { item: MobileGitStatusEntryView }) {
- const rowBusy =
- busyAction === item.stageActionId ||
- busyAction === item.unstageActionId ||
- busyAction === item.discardActionId ||
- busyAction === item.resolveActionId
- const rowDisabled = !item.canOpen || busyAction !== null
- const ioBusy = busyAction !== null
- const hasConflictActions = item.conflictResolutions.length > 0
- return (
+// Git paths always use '/'; the name leads so it never truncates behind its folder.
+function splitGitPath(path: string): { name: string; folder: string } {
+ const slash = path.lastIndexOf('/')
+ return slash === -1
+ ? { name: path, folder: '' }
+ : { name: path.slice(slash + 1), folder: path.slice(0, slash) }
+}
+
+/** Status letter, file name over its folder, then the row's stats and action. */
+function FileRowBody({
+ status,
+ path,
+ detail,
+ added,
+ removed,
+ trailing,
+ actions
+}: {
+ status: GitFileStatus
+ path: string
+ /** Leads the folder line, e.g. a conflict kind or a rename source. */
+ detail?: string
+ added?: number
+ removed?: number
+ trailing?: ReactNode
+ /** Wrapping buttons under the name, e.g. a conflict's resolutions, so the name keeps its width. */
+ actions?: ReactNode
+}) {
+ useMobileColorScheme()
+ const { name, folder } = splitGitPath(path)
+ const subtitle = detail ? (folder ? `${detail} · ${folder}` : detail) : folder
+ return (
+ <>
+
+
+ {STATUS_LABELS[status]}
+
+
+
+
+ {name}
+
+ {subtitle ? (
+
+ {subtitle}
+
+ ) : null}
+ {actions ? {actions} : null}
+
+
+ {trailing}
+ >
+ )
+}
+
+// The width the Discard action uncovers; a swipe past half of it settles open.
+const DISCARD_WIDTH = 84
+const SWIPE_SETTLE_MS = 250
+const DRAG_CLICK_WINDOW_MS = 400
+
+/** Discard sits behind a left swipe and still asks first; it is also on the diff screen. */
+function SwipeToDiscard({
+ available,
+ enabled,
+ label,
+ accessibilityLabel,
+ onDiscard,
+ children
+}: {
+ /** Only discardable rows get the swipe; toggling it would remount the row. */
+ available: boolean
+ enabled: boolean
+ label: string
+ accessibilityLabel: string
+ onDiscard: () => void
+ /** Gets a wrapper for the row's press: a tap on a swiped row closes it instead. */
+ children: (guardPress: (onPress: () => void) => () => void) => ReactNode
+}) {
+ useMobileColorScheme()
+ const reducedMotion = useDrawerReducedMotion()
+ const offset = useSharedValue(0)
+ const start = useSharedValue(0)
+ // When the last drag ended: on the web, releasing a mouse drag also clicks the row.
+ const dragEndedAt = useSharedValue(0)
+ // Built per render: a module-level easing would run before a test's Reanimated mock can serve it.
+ const settle = (to: number) => {
+ 'worklet'
+ return withTiming(
+ to,
+ reducedMotion
+ ? { duration: 0, reduceMotion: ReduceMotion.Never }
+ : { duration: SWIPE_SETTLE_MS, easing: Easing.bezier(...SHEET_CURVE) }
+ )
+ }
+ // Horizontal only, so the list keeps scrolling under a vertical drag.
+ const pan = Gesture.Pan()
+ .enabled(enabled)
+ .activeOffsetX([-12, 12])
+ .failOffsetY([-10, 10])
+ .onBegin(() => {
+ start.value = offset.value
+ })
+ .onUpdate((event) => {
+ offset.value = Math.min(0, Math.max(-DISCARD_WIDTH, start.value + event.translationX))
+ })
+ .onEnd(() => {
+ // oxlint-disable-next-line react/purity -- runs when the drag ends, never during render.
+ dragEndedAt.value = Date.now()
+ offset.value = settle(offset.value < -DISCARD_WIDTH / 2 ? -DISCARD_WIDTH : 0)
+ })
+ // Web has no worklet plugin; mapper hooks need this explicit dependency list.
+ const rowStyle = useAnimatedStyle(() => ({ transform: [{ translateX: offset.value }] }), [offset])
+ // A tap on an open row closes it rather than opening the diff.
+ const guardPress = (onPress: () => void) => () => {
+ // The click a drag release produces belongs to the drag, not to the row.
+ if (Date.now() - dragEndedAt.value < DRAG_CLICK_WINDOW_MS) {
+ return
+ }
+ if (offset.value !== 0) {
+ offset.value = settle(0)
+ return
+ }
+ onPress()
+ }
+ if (!available) {
+ return children((onPress) => onPress)
+ }
+ return (
+
[
- styles.fileRow,
- pressed && item.canOpen && styles.fileRowPressed,
- // Conflict rows cannot open but carry live actions; their buttons dim themselves while busy.
- rowDisabled && !hasConflictActions && styles.fileRowDisabled,
- !item.canOpen && !hasConflictActions && styles.fileRowUnavailable
- ]}
+ style={({ pressed }) => [styles.discardAction, pressed && styles.iconButtonPressed]}
+ disabled={!enabled}
onPress={() => {
- if (item.area !== undefined) {
- openDiffForFile({ kind: 'changes', filePath: item.path, area: item.area })
- }
+ offset.value = settle(0)
+ onDiscard()
}}
- disabled={rowDisabled}
- // An accessible row hides nested buttons from iOS VoiceOver; conflict rows cannot open anyway.
- accessible={!hasConflictActions}
- accessibilityLabel={t(
- 'sourceControl.fileRows.openChangedFile',
- 'Open changed file {{path}}',
- {
- path: item.path
- }
- )}
+ accessibilityRole="button"
+ accessibilityLabel={accessibilityLabel}
>
-
-
- {STATUS_LABELS[item.status]}
-
-
-
-
-
- {item.path}
-
- {item.oldPath ? (
-
- {t('sourceControl.fileRows.renamedFrom', 'from {{path}}', { path: item.oldPath })}
-
- ) : item.conflictStatus === 'unresolved' ? (
-
- {conflictKindLabel(item.conflictKind, t)}
-
- ) : null}
- {!rowBusy && hasConflictActions ? (
-
- {item.conflictResolutions.map((resolution) => (
- [
- styles.conflictActionButton,
- ioBusy && styles.iconButtonDisabled,
- pressed && styles.iconButtonPressed
- ]}
- disabled={ioBusy}
- onPress={() => void resolveConflict(item, resolution)}
- hitSlop={4}
- accessibilityLabel={t(
- 'sourceControl.conflictActions.resolveFile',
- '{{action}}: {{path}}',
- {
- action: mobileConflictResolutionLabel(resolution, t),
- path: item.path
- }
- )}
- >
-
- {mobileConflictResolutionLabel(resolution, t)}
-
-
- ))}
-
- ) : null}
-
- {rowBusy ? (
-
- ) : item.canUnstage ? (
+
+ {label}
+
+ {/* pan-y keeps the page scrolling vertically over the row on the web. */}
+
+ {children(guardPress)}
+
+
+ )
+}
+
+export function ChangeFileRow({
+ item,
+ state
+}: {
+ item: MobileGitStatusEntryView
+ state: FileRowState
+}) {
+ useMobileColorScheme()
+ const t = useMobileTranslation()
+ const { busyAction, openDiffForFile, resolveConflict, runGitAction, setDiscardTarget } = state
+ const rowBusy =
+ busyAction === item.stageActionId ||
+ busyAction === item.unstageActionId ||
+ busyAction === item.discardActionId ||
+ busyAction === item.resolveActionId
+ const ioBusy = busyAction !== null
+ const hasConflictActions = item.conflictResolutions.length > 0
+ const detail = item.oldPath
+ ? t('sourceControl.fileRows.renamedFrom', 'from {{path}}', { path: item.oldPath })
+ : item.conflictStatus === 'unresolved'
+ ? conflictKindLabel(item.conflictKind, t)
+ : undefined
+ const stage = () => void runGitAction(item.stageActionId, 'git.stage', { filePath: item.path })
+ const unstage = () => {
+ const filePaths = getUnstageablePaths([item])
+ void (filePaths.length > 1
+ ? runGitAction(item.unstageActionId, 'git.bulkUnstage', { filePaths })
+ : runGitAction(item.unstageActionId, 'git.unstage', { filePath: item.path }))
+ }
+ // Every press in the row goes through the swipe's guard, so a drag never also presses a button.
+ const conflictActions = (guard: (onPress: () => void) => () => void) =>
+ !rowBusy && hasConflictActions
+ ? item.conflictResolutions.map((resolution) => (
[
- styles.iconButton,
+ styles.conflictActionButton,
ioBusy && styles.iconButtonDisabled,
pressed && styles.iconButtonPressed
]}
disabled={ioBusy}
- onPress={() => {
- const filePaths = getUnstageablePaths([item])
- void (filePaths.length > 1
- ? runGitAction(item.unstageActionId, 'git.bulkUnstage', { filePaths })
- : runGitAction(item.unstageActionId, 'git.unstage', { filePath: item.path }))
- }}
- hitSlop={8}
- accessibilityLabel={t('sourceControl.fileRows.unstageFile', 'Unstage {{path}}', {
- path: item.path
- })}
+ onPress={guard(() => void resolveConflict(item, resolution))}
+ hitSlop={4}
+ accessibilityLabel={t(
+ 'sourceControl.conflictActions.resolveFile',
+ '{{action}}: {{path}}',
+ {
+ action: mobileConflictResolutionLabel(resolution, t),
+ path: item.path
+ }
+ )}
>
-
+
+ {mobileConflictResolutionLabel(resolution, t)}
+
- ) : item.canStage || item.canDiscard ? (
-
- {item.canStage ? (
- [
- styles.iconButton,
- ioBusy && styles.iconButtonDisabled,
- pressed && styles.iconButtonPressed
- ]}
- disabled={ioBusy}
- onPress={() =>
- void runGitAction(item.stageActionId, 'git.stage', { filePath: item.path })
- }
- hitSlop={8}
- accessibilityLabel={t('sourceControl.fileRows.stageFile', 'Stage {{path}}', {
- path: item.path
- })}
- >
-
-
- ) : null}
- {item.canDiscard ? (
- [
- styles.iconButton,
- ioBusy && styles.iconButtonDisabled,
- pressed && styles.iconButtonPressed
- ]}
- disabled={ioBusy}
- onPress={() => setDiscardTarget(item)}
- hitSlop={8}
- accessibilityLabel={t('sourceControl.fileRows.discardFile', 'Discard {{path}}', {
- path: item.path
- })}
- >
-
-
- ) : null}
-
- ) : null}
- {!rowBusy && item.canOpen ? (
-
- ) : null}
+ ))
+ : null
+ const trailing = (guard: (onPress: () => void) => () => void) =>
+ rowBusy ? (
+
+ ) : !hasConflictActions && (item.canUnstage || item.canStage) ? (
+ [
+ styles.iconButton,
+ ioBusy && styles.iconButtonDisabled,
+ pressed && styles.iconButtonPressed
+ ]}
+ disabled={ioBusy}
+ onPress={guard(item.canUnstage ? unstage : stage)}
+ hitSlop={8}
+ accessibilityLabel={
+ item.canUnstage
+ ? t('sourceControl.fileRows.unstageFile', 'Unstage {{path}}', { path: item.path })
+ : t('sourceControl.fileRows.stageFile', 'Stage {{path}}', { path: item.path })
+ }
+ >
+ {item.canUnstage ? (
+
+ ) : (
+
+ )}
- )
- }
-}
-
-type BranchState = Pick<
- MobileSourceControlState,
- | 'shouldShowBranchCompareSection'
- | 'branchCompareSummaryText'
- | 'branchEntries'
- | 'branchCompareState'
- | 'branchCompareResult'
- | 'busyAction'
- | 'openDiffForFile'
->
-
-export function BranchCompareHeader({ state }: { state: BranchState }) {
- useMobileColorScheme()
- const t = useMobileTranslation()
- const {
- shouldShowBranchCompareSection,
- branchCompareSummaryText,
- branchEntries,
- branchCompareState,
- branchCompareResult
- } = state
- if (!shouldShowBranchCompareSection) {
- return null
- }
-
+ ) : null
return (
-
-
-
-
- {branchCompareSummaryText ? (
-
- {branchCompareSummaryText}
-
- ) : null}
- {branchCompareState.kind === 'loading' ? (
-
-
-
- {t('sourceControl.fileRows.loadingCommitted', 'Loading committed changes...')}
-
-
- ) : branchCompareState.kind === 'error' ? (
-
- {branchCompareState.message}
-
- ) : branchCompareResult && branchCompareResult.summary.status !== 'ready' ? (
-
-
- {branchCompareResult.summary.errorMessage ??
- t('sourceControl.fileRows.committedUnavailable', 'Committed changes unavailable.')}
-
-
- ) : null}
-
+ setDiscardTarget(item)}
+ >
+ {(guardPress) => (
+ [
+ styles.fileRow,
+ pressed && item.canOpen && styles.fileRowPressed,
+ !item.canOpen && !hasConflictActions && styles.fileRowUnavailable
+ ]}
+ onPress={guardPress(() => {
+ if (item.area !== undefined) {
+ openDiffForFile({ kind: 'changes', filePath: item.path, area: item.area })
+ }
+ })}
+ disabled={!item.canOpen || ioBusy}
+ // An accessible row hides nested buttons from iOS VoiceOver; conflict rows cannot open anyway.
+ accessible={!hasConflictActions}
+ accessibilityLabel={t(
+ 'sourceControl.fileRows.openChangedFile',
+ 'Open changed file {{path}}',
+ {
+ path: item.path
+ }
+ )}
+ >
+
+
+ )}
+
)
}
@@ -272,55 +343,38 @@ export function BranchFileRow({
state
}: {
entry: MobileBranchEntryView
- state: BranchState
+ state: Pick
}) {
useMobileColorScheme()
const t = useMobileTranslation()
- const { busyAction, openDiffForFile } = state
- const rowDisabled = !entry.canOpen || busyAction !== null
- const meta = formatMobileBranchEntryMeta(entry)
return (
[
styles.fileRow,
pressed && entry.canOpen && styles.fileRowPressed,
- rowDisabled && styles.fileRowDisabled,
!entry.canOpen && styles.fileRowUnavailable
]}
- onPress={() => openDiffForFile({ kind: 'changes', filePath: entry.path, area: 'branch' })}
- disabled={rowDisabled}
+ onPress={() =>
+ state.openDiffForFile({ kind: 'changes', filePath: entry.path, area: 'branch' })
+ }
+ disabled={!entry.canOpen || state.busyAction !== null}
accessibilityLabel={t(
'sourceControl.fileRows.openCommittedChange',
'Open committed change {{path}}',
- {
- path: entry.path
- }
+ { path: entry.path }
)}
>
-
-
- {STATUS_LABELS[entry.status]}
-
-
-
-
-
- {entry.path}
-
- {meta ? (
-
- {meta}
-
- ) : null}
-
- {entry.canOpen ? : null}
)
}
diff --git a/mobile/src/source-control/MobileSourceControlModals.tsx b/mobile/src/source-control/MobileSourceControlModals.tsx
index 1d1c4686561..85dd3de09de 100644
--- a/mobile/src/source-control/MobileSourceControlModals.tsx
+++ b/mobile/src/source-control/MobileSourceControlModals.tsx
@@ -1,4 +1,3 @@
-import { ActionSheetModal, type ActionSheetAction } from '../components/ActionSheetModal'
import { ConfirmModal } from '../components/ConfirmModal'
import { PickerModal } from '../components/PickerModal'
import { openMobilePrUrl } from '../components/mobile-pr-url'
@@ -7,14 +6,11 @@ import type { MobileSourceControlState } from './use-mobile-source-control-state
type Props = {
state: MobileSourceControlState
- actionSheetActions: ActionSheetAction[]
}
-export function MobileSourceControlModals({ state, actionSheetActions }: Props) {
+export function MobileSourceControlModals({ state }: Props) {
const t = useMobileTranslation()
const {
- showActionSheet,
- setShowActionSheet,
discardTarget,
setDiscardTarget,
showBranchPicker,
@@ -24,21 +20,12 @@ export function MobileSourceControlModals({ state, actionSheetActions }: Props)
setCreatedPrUrl,
createdPrWarning,
setCreatedPrWarning,
- branchLabel,
checkoutBranch,
runGitAction
} = state
return (
<>
- setShowActionSheet(false)}
- />
-
openHubScreen('commits'), [openHubScreen])
- const openPrTab = useCallback(() => selectTab('pr'), [selectTab])
// The PR tab owns the Create PR and Merge sheets; the bar and Changes reach them through this.
const prOpeners = useRef({ createPr: () => {}, merge: () => {} })
const openCreatePr = useCallback(() => prOpeners.current.createPr(), [])
@@ -99,13 +96,7 @@ export function MobileSourceControlPanel({
busyAction,
loadStatus,
status,
- branchCompareResult,
- branchLabel,
- syncLabel,
- unstagedCount,
- stagedCount,
- branchEntries,
- abortConflictOperation
+ branchCompareResult
} = state
const ioBusy = busyAction !== null
const ready = screenState.kind === 'ready'
@@ -144,6 +135,18 @@ export function MobileSourceControlPanel({
)
const primaryActionArgs = state.primaryActionArgs
const actionSheetActions = useMobileSourceControlActionSheet(state, review)
+ // The menu closes on a pick; the bar and the rows show the action's progress.
+ const gitMenu = useMemo(
+ () =>
+ groupMobileSourceControlMenu(
+ actionSheetActions.map((action) => ({
+ ...action,
+ id: action.id ?? '',
+ skipAutoClose: false
+ }))
+ ),
+ [actionSheetActions]
+ )
const primaryAction = useMemo(
() => buildMobileSourceControlPrimaryAction({ ...primaryActionArgs, review }),
[primaryActionArgs, review, t]
@@ -197,18 +200,6 @@ export function MobileSourceControlPanel({
}
}, [activeTab, isHostedRepo, prBranch, prSidebarKind, prDetailsMissingFor])
- const prChip = useMemo(() => {
- // Detached HEAD never loads PR state; hide the chip instead of leaving it spinning.
- if (!isHostedRepo || !prBranch) {
- return null
- }
- const commentCount =
- prController.prSidebarState.kind === 'ready'
- ? countUnresolvedReviewThreads(prController.prSidebarState.data.details?.comments)
- : null
- return buildMobilePrChipSummary(prController.prSidebarState, commentCount)
- }, [isHostedRepo, prBranch, prController.prSidebarState, t])
-
const nextStep = useMobileSourceControlNextStep({
worktreeId,
state,
@@ -238,6 +229,23 @@ export function MobileSourceControlPanel({
}
void refetchPr({ includeDetails: false })
}, [activeTab, isHostedRepo, loadStatus, refetchPr])
+ // Changes: the rarer git actions, then Refresh, then Force push last and red.
+ const changesMenuGroups = useMemo(
+ () => [
+ ...gitMenu.groups,
+ [
+ {
+ id: 'refresh',
+ label: t('sourceControl.header.refresh', 'Refresh source control'),
+ icon: RefreshCw,
+ disabled: ioBusy,
+ onPress: onRefresh
+ }
+ ],
+ ...(gitMenu.forcePush ? [[{ ...gitMenu.forcePush, destructive: true }]] : [])
+ ],
+ [gitMenu, ioBusy, onRefresh, t]
+ )
const { prTab, prMenuGroups, branchCompare } = useMobileSourceControlPrTab({
state,
activeTab,
@@ -273,7 +281,13 @@ export function MobileSourceControlPanel({
onBack={onBack}
onClose={onClose}
onRefresh={onRefresh}
- menuGroups={activeTab === 'pr' ? prMenuGroups : null}
+ menuGroups={
+ activeTab === 'pr'
+ ? prMenuGroups
+ : activeTab === 'changes' && ready
+ ? changesMenuGroups
+ : null
+ }
onOpenPrWeb={prWebUrl ? () => openMobilePrUrl(prWebUrl) : undefined}
prNumber={prWebNumber}
accessory={
@@ -317,9 +331,6 @@ export function MobileSourceControlPanel({
const showChanges = ready && (activeTab === 'changes' || visitedTabs.has('changes'))
// Unmount inactive PR content to release Mermaid WebViews and avoid comment rerenders per keystroke.
const showPrBody = ready && activeTab === 'pr'
- const conflictOperation = status?.conflictOperation ?? null
- const hasActiveConflict = conflictOperation != null && conflictOperation !== 'unknown'
- const conflictAborting = isMobileConflictAborting(busyAction, conflictOperation)
const hub = (
@@ -331,26 +342,13 @@ export function MobileSourceControlPanel({
)}
- {/* Ready PRs duplicate the branch card; retain it only when needed to abort a conflict. */}
- {ready && (activeTab !== 'pr' || hasActiveConflict) ? (
- void abortConflictOperation(operation)}
- prChip={prChip}
- onOpenPr={openPrTab}
- />
- ) : null}
-
{showChanges ? (
-
+
) : activeTab === 'changes' ? (
statusGate
@@ -418,7 +416,7 @@ export function MobileSourceControlPanel({
) : null
}
/>
-
+
)
diff --git a/mobile/src/source-control/MobileSourceControlPrChip.tsx b/mobile/src/source-control/MobileSourceControlPrChip.tsx
deleted file mode 100644
index 32318b52eb3..00000000000
--- a/mobile/src/source-control/MobileSourceControlPrChip.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import { ActivityIndicator, Pressable, Text, View } from 'react-native'
-import {
- AlertTriangle,
- Check,
- ChevronRight,
- CircleDot,
- GitPullRequest,
- MessageSquare,
- X
-} from 'lucide-react-native'
-import { colors } from '../theme/mobile-theme'
-import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
-import type { Translate } from '../i18n/mobile-i18n'
-import { useMobileTranslation } from '../i18n/use-mobile-translation'
-import { statusColor } from '../components/pr-sidebar/pr-sidebar-status-color'
-import { hubStyles } from './mobile-source-control-hub-styles'
-import type { MobilePrChipRollup, MobilePrChipSummary } from './mobile-pr-chip-summary'
-
-type Props = {
- summary: MobilePrChipSummary
- onPress: () => void
-}
-
-export function MobileSourceControlPrChip({ summary, onPress }: Props) {
- useMobileColorScheme()
- const t = useMobileTranslation()
- return (
- [hubStyles.chip, pressed && hubStyles.chipPressed]}
- onPress={onPress}
- accessibilityRole="button"
- accessibilityLabel={chipAccessibilityLabel(summary, t)}
- >
-
-
-
- {summary.kind === 'loading' ? (
- <>
-
-
- {t('sourceControl.prChip.loading', 'Loading pull request…')}
-
- >
- ) : summary.kind === 'none' ? (
- <>
-
- {t('sourceControl.prChip.createPr', 'Create pull request')}
-
-
-
- >
- ) : summary.kind === 'unavailable' ? (
- <>
-
- {summary.message}
-
-
- >
- ) : (
- <>
- #{summary.number}
-
-
- {summary.stateLabel}
-
-
-
- {summary.commentCount != null && summary.commentCount > 0 ? (
-
-
- {summary.commentCount}
-
- ) : null}
-
-
- >
- )}
-
- )
-}
-
-function ChipRollup({ rollup }: { rollup: MobilePrChipRollup }) {
- useMobileColorScheme()
- const color = statusColor(rollup.token)
- return (
-
-
- {rollup.text}
-
- )
-}
-
-function RollupIcon({ kind, color }: { kind: MobilePrChipRollup['kind']; color: string }) {
- const size = 13
- const strokeWidth = 2.3
- switch (kind) {
- case 'conflict':
- return
- case 'failing':
- return
- case 'running':
- return
- case 'passed':
- return
- case 'none':
- return null
- }
-}
-
-function chipAccessibilityLabel(summary: MobilePrChipSummary, t: Translate): string {
- switch (summary.kind) {
- case 'loading':
- return t('sourceControl.prChip.loadingLabel', 'Loading pull request')
- case 'none':
- return t('sourceControl.prChip.createPr', 'Create pull request')
- case 'unavailable':
- return t('sourceControl.prChip.unavailableLabel', 'Pull request unavailable: {{message}}', {
- message: summary.message
- })
- case 'ready': {
- const count = summary.commentCount
- const details = {
- number: summary.number,
- state: summary.stateLabel,
- checks: summary.rollup.text
- }
- if (count != null && count > 0) {
- return count === 1
- ? t(
- 'sourceControl.prChip.openWithComments_one',
- 'Pull request #{{number}}, {{state}}, {{checks}}, {{count}} unresolved comments. Open pull request.',
- { ...details, count }
- )
- : t(
- 'sourceControl.prChip.openWithComments_other',
- 'Pull request #{{number}}, {{state}}, {{checks}}, {{count}} unresolved comments. Open pull request.',
- { ...details, count }
- )
- }
- return t(
- 'sourceControl.prChip.openLabel',
- 'Pull request #{{number}}, {{state}}, {{checks}}. Open pull request.',
- details
- )
- }
- }
-}
diff --git a/mobile/src/source-control/mobile-branch-compare.test.ts b/mobile/src/source-control/mobile-branch-compare.test.ts
index 9e85f3d3938..e1a9095560b 100644
--- a/mobile/src/source-control/mobile-branch-compare.test.ts
+++ b/mobile/src/source-control/mobile-branch-compare.test.ts
@@ -1,8 +1,7 @@
import { describe, expect, it } from 'vitest'
import {
buildMobileBranchCompareSection,
- canOpenMobileBranchCompareDiff,
- formatMobileBranchCompareSummary
+ canOpenMobileBranchCompareDiff
} from './mobile-branch-compare'
describe('mobile branch compare helpers', () => {
@@ -16,22 +15,6 @@ describe('mobile branch compare helpers', () => {
expect(section?.data.map((entry) => entry.path)).toEqual(['alpha.ts', 'zeta.ts'])
})
- it('summarizes ready branch compares', () => {
- expect(
- formatMobileBranchCompareSummary({
- baseRef: 'origin/main',
- baseOid: 'a'.repeat(40),
- compareRef: 'HEAD',
- headOid: 'b'.repeat(40),
- mergeBase: 'c'.repeat(40),
- changedFiles: 2,
- commitsAhead: 1,
- errorMessage: undefined,
- status: 'ready'
- })
- ).toBe('2 files - 1 commit - vs origin/main')
- })
-
it('only opens branch diffs when compare object ids are available', () => {
expect(
canOpenMobileBranchCompareDiff({
diff --git a/mobile/src/source-control/mobile-branch-compare.ts b/mobile/src/source-control/mobile-branch-compare.ts
index 298a41f3e81..428ff6720d1 100644
--- a/mobile/src/source-control/mobile-branch-compare.ts
+++ b/mobile/src/source-control/mobile-branch-compare.ts
@@ -30,38 +30,6 @@ export function buildMobileBranchCompareSection {
it('builds sections in the mobile source control order', () => {
const sections = buildMobileSourceControlSections(entries, 'changes-first')
- expect(sections.map((section) => section.title)).toEqual(['Changes', 'Staged Changes'])
+ expect(sections.map((section) => section.title)).toEqual(['Uncommitted', 'Staged'])
})
it('computes actionable path sets', () => {
@@ -106,7 +106,7 @@ describe('mobile source control status helpers', () => {
sections.map((section) => [section.title, section.data.map((entry) => entry.path)])
).toEqual([
['Conflicts', ['alpha.ts', 'beta.ts']],
- ['Changes', ['aardvark.ts', 'zeta.ts']]
+ ['Uncommitted', ['aardvark.ts', 'zeta.ts']]
])
})
diff --git a/mobile/src/source-control/mobile-git-status.ts b/mobile/src/source-control/mobile-git-status.ts
index 45239451ac4..7f3fea4c54d 100644
--- a/mobile/src/source-control/mobile-git-status.ts
+++ b/mobile/src/source-control/mobile-git-status.ts
@@ -50,8 +50,8 @@ export type MobileSourceControlSection {
return {
conflicts: translate('sourceControl.gitStatus.conflicts', 'Conflicts'),
- unstaged: translate('sourceControl.gitStatus.changes', 'Changes'),
- staged: translate('sourceControl.gitStatus.stagedChanges', 'Staged Changes')
+ unstaged: translate('sourceControl.gitStatus.uncommitted', 'Uncommitted'),
+ staged: translate('sourceControl.gitStatus.staged', 'Staged')
}
}
diff --git a/mobile/src/source-control/mobile-pr-chip-summary.test.ts b/mobile/src/source-control/mobile-pr-chip-summary.test.ts
deleted file mode 100644
index d8b97d85e91..00000000000
--- a/mobile/src/source-control/mobile-pr-chip-summary.test.ts
+++ /dev/null
@@ -1,166 +0,0 @@
-import { describe, it, expect } from 'vitest'
-import type { PRCheckDetail } from '../../../src/shared/github/check-types'
-import type { PRComment } from '../../../src/shared/github/comment-types'
-import type { PRInfo } from '../../../src/shared/github/pull-request-types'
-import type { PrSidebarState } from '../session/mobile-pr-sidebar-state'
-import { buildMobilePrChipSummary, countUnresolvedReviewThreads } from './mobile-pr-chip-summary'
-
-function pr(overrides: Partial = {}): PRInfo {
- return {
- number: 7701,
- title: 'Make center split more visible',
- state: 'open',
- url: 'https://example.test/pr/7701',
- checksStatus: 'success',
- updatedAt: '2026-07-07T00:00:00Z',
- mergeable: 'MERGEABLE',
- ...overrides
- }
-}
-
-function check(
- conclusion: PRCheckDetail['conclusion'],
- status: PRCheckDetail['status'] = 'completed'
-): PRCheckDetail {
- return { name: `check-${conclusion}-${status}`, status, conclusion, url: null }
-}
-
-function ready(prInfo: PRInfo, checks: PRCheckDetail[]): PrSidebarState {
- return {
- kind: 'ready',
- data: { pr: prInfo, checks, details: null, checksError: null, pushTarget: null }
- }
-}
-
-describe('buildMobilePrChipSummary', () => {
- it('maps non-ready states', () => {
- expect(buildMobilePrChipSummary({ kind: 'hidden' })).toEqual({ kind: 'loading' })
- expect(buildMobilePrChipSummary({ kind: 'loading' })).toEqual({ kind: 'loading' })
- expect(buildMobilePrChipSummary({ kind: 'none' })).toEqual({ kind: 'none' })
- expect(buildMobilePrChipSummary({ kind: 'error', message: 'net' })).toEqual({
- kind: 'unavailable',
- message: 'net'
- })
- expect(buildMobilePrChipSummary({ kind: 'blocked', message: 'auth' })).toEqual({
- kind: 'unavailable',
- message: 'auth'
- })
- })
-
- it('surfaces the PR number and state badge', () => {
- const summary = buildMobilePrChipSummary(ready(pr({ state: 'draft' }), [check('success')]))
- expect(summary.kind).toBe('ready')
- if (summary.kind !== 'ready') {
- return
- }
- expect(summary.number).toBe(7701)
- expect(summary.stateLabel).toBe('Draft')
- })
-
- // Skipped checks count as satisfied, matching desktop and the tasks grid.
- it('rolls up passed checks as passed/total, counting skipped as passed', () => {
- const summary = buildMobilePrChipSummary(
- ready(pr(), [check('success'), check('success'), check('skipped')])
- )
- if (summary.kind !== 'ready') {
- throw new Error('expected ready')
- }
- expect(summary.rollup).toEqual({ kind: 'passed', text: '3/3', token: 'statusGreen' })
- })
-
- it('treats a merge-blocking action_required gate as failing, not passing', () => {
- const summary = buildMobilePrChipSummary(
- ready(pr(), [check('success'), check('action_required')])
- )
- if (summary.kind !== 'ready') {
- throw new Error('expected ready')
- }
- expect(summary.rollup).toEqual({ kind: 'failing', text: '1 failing', token: 'statusRed' })
- })
-
- it('distinguishes checks that resolved to nothing actionable from having no checks', () => {
- const summary = buildMobilePrChipSummary(ready(pr(), [check('neutral')]))
- if (summary.kind !== 'ready') {
- throw new Error('expected ready')
- }
- expect(summary.rollup).toEqual({
- kind: 'none',
- text: 'Unresolved checks',
- token: 'textSecondary'
- })
- })
-
- it('prefers failing over running and passing', () => {
- const summary = buildMobilePrChipSummary(
- ready(pr(), [check('success'), check('failure'), check(null, 'in_progress')])
- )
- if (summary.kind !== 'ready') {
- throw new Error('expected ready')
- }
- expect(summary.rollup).toEqual({ kind: 'failing', text: '1 failing', token: 'statusRed' })
- })
-
- it('shows running when nothing has failed yet', () => {
- const summary = buildMobilePrChipSummary(ready(pr(), [check('success'), check(null, 'queued')]))
- if (summary.kind !== 'ready') {
- throw new Error('expected ready')
- }
- expect(summary.rollup).toEqual({ kind: 'running', text: '1 running', token: 'statusAmber' })
- })
-
- it('lets a merge conflict win over green checks', () => {
- const summary = buildMobilePrChipSummary(
- ready(pr({ mergeable: 'CONFLICTING' }), [check('success')])
- )
- if (summary.kind !== 'ready') {
- throw new Error('expected ready')
- }
- expect(summary.rollup.kind).toBe('conflict')
- })
-
- it('reports no checks when the list is empty', () => {
- const summary = buildMobilePrChipSummary(ready(pr(), []))
- if (summary.kind !== 'ready') {
- throw new Error('expected ready')
- }
- expect(summary.rollup).toEqual({ kind: 'none', text: 'No checks', token: 'textSecondary' })
- })
-
- it('passes through the unresolved comment count', () => {
- const summary = buildMobilePrChipSummary(ready(pr(), [check('success')]), 3)
- if (summary.kind !== 'ready') {
- throw new Error('expected ready')
- }
- expect(summary.commentCount).toBe(3)
- })
-})
-
-describe('countUnresolvedReviewThreads', () => {
- function comment(overrides: Partial): PRComment {
- return {
- id: 1,
- author: 'octocat',
- authorAvatarUrl: '',
- body: 'x',
- createdAt: '2026-07-07T00:00:00Z',
- url: '',
- ...overrides
- }
- }
-
- it('returns null when details have not loaded', () => {
- expect(countUnresolvedReviewThreads(null)).toBeNull()
- expect(countUnresolvedReviewThreads(undefined)).toBeNull()
- })
-
- it('counts each unresolved thread once and ignores resolved threads', () => {
- const comments = [
- comment({ id: 1, threadId: 't1', isResolved: false }),
- comment({ id: 2, threadId: 't1', isResolved: false }),
- comment({ id: 3, threadId: 't2', isResolved: true }),
- comment({ id: 4, threadId: 't3' }),
- comment({ id: 5 })
- ]
- expect(countUnresolvedReviewThreads(comments)).toBe(2)
- })
-})
diff --git a/mobile/src/source-control/mobile-pr-chip-summary.ts b/mobile/src/source-control/mobile-pr-chip-summary.ts
deleted file mode 100644
index 9d62b270f22..00000000000
--- a/mobile/src/source-control/mobile-pr-chip-summary.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-import type { PRComment } from '../../../src/shared/github/comment-types'
-import type { PrSidebarState } from '../session/mobile-pr-sidebar-state'
-import { summarizeProviderChecks } from '../../../src/shared/provider-check-summary'
-import {
- prStateBadge,
- type MobileStatusToken
-} from '../components/pr-sidebar/pr-checks-presentation'
-
-// Use the sidebar’s same state so the chip and linked checks list cannot disagree.
-
-export type MobilePrChipRollup =
- | { kind: 'conflict'; text: string; token: MobileStatusToken }
- | { kind: 'failing'; text: string; token: MobileStatusToken }
- | { kind: 'running'; text: string; token: MobileStatusToken }
- | { kind: 'passed'; text: string; token: MobileStatusToken }
- | { kind: 'none'; text: string; token: MobileStatusToken }
-
-export type MobilePrChipSummary =
- | { kind: 'loading' }
- | { kind: 'none' }
- | { kind: 'unavailable'; message: string }
- | {
- kind: 'ready'
- number: number
- stateLabel: string
- stateToken: MobileStatusToken
- rollup: MobilePrChipRollup
- commentCount: number | null
- }
-
-// Count unresolved inline threads once per threadId; conversation comments are not review threads.
-export function countUnresolvedReviewThreads(
- comments: PRComment[] | null | undefined
-): number | null {
- if (!comments) {
- return null
- }
- const unresolved = new Set()
- for (const comment of comments) {
- if (comment.threadId && comment.isResolved !== true) {
- unresolved.add(comment.threadId)
- }
- }
- return unresolved.size
-}
-
-export function buildMobilePrChipSummary(
- state: PrSidebarState,
- commentCount: number | null = null
-): MobilePrChipSummary {
- switch (state.kind) {
- case 'hidden':
- case 'loading':
- return { kind: 'loading' }
- case 'none':
- return { kind: 'none' }
- case 'error':
- case 'blocked':
- return { kind: 'unavailable', message: state.message }
- case 'ready': {
- const badge = prStateBadge(state.data.pr.state)
- return {
- kind: 'ready',
- number: state.data.pr.number,
- stateLabel: badge.label,
- stateToken: badge.token,
- rollup: buildChipRollup(state),
- commentCount
- }
- }
- }
-}
-
-function buildChipRollup(state: Extract): MobilePrChipRollup {
- if (state.data.pr.mergeable === 'CONFLICTING') {
- return { kind: 'conflict', text: 'Conflicts', token: 'statusAmber' }
- }
- // Share classification with the checks list and tasks grid.
- const checks = summarizeProviderChecks(state.data.checks)
- if (checks.failed > 0) {
- return { kind: 'failing', text: `${checks.failed} failing`, token: 'statusRed' }
- }
- if (checks.pending > 0) {
- return { kind: 'running', text: `${checks.pending} running`, token: 'statusAmber' }
- }
- if (checks.passed > 0) {
- return { kind: 'passed', text: `${checks.passed}/${checks.total}`, token: 'statusGreen' }
- }
- // Existing checks with no actionable outcome are still distinct from no checks.
- return {
- kind: 'none',
- text: checks.total === 0 ? 'No checks' : 'Unresolved checks',
- token: 'textSecondary'
- }
-}
diff --git a/mobile/src/source-control/mobile-source-control-actions.ts b/mobile/src/source-control/mobile-source-control-actions.ts
index 8540fe62d3b..936eff41189 100644
--- a/mobile/src/source-control/mobile-source-control-actions.ts
+++ b/mobile/src/source-control/mobile-source-control-actions.ts
@@ -145,12 +145,12 @@ export function buildMobileSourceControlActions(
label: t('sourceControl.actions.switchBranch', 'Switch branch'),
iconKey: 'branch',
disabled: busy,
- skipAutoClose: true,
+ closeBeforePress: true,
onPress: handlers.checkout
},
{
id: 'history',
- label: t('sourceControl.actions.commits', 'Commits'),
+ label: t('sourceControl.actions.openCommits', 'Open commits'),
iconKey: 'history',
disabled: busy,
onPress: handlers.history
@@ -158,6 +158,27 @@ export function buildMobileSourceControlActions(
]
}
+// The ⋯ menu's git actions, grouped; Create PR lives in the next-step bar and the PR tab.
+const MENU_GROUPS: readonly (readonly string[])[] = [
+ ['commit-push', 'commit-sync'],
+ // Push and Publish stay here too: the bar shows the commit box while anything is uncommitted.
+ ['push', 'publish', 'fetch', 'pull', 'fast-forward', 'sync', 'rebase'],
+ ['checkout', 'history']
+]
+
+export function groupMobileSourceControlMenu(
+ actions: readonly T[]
+): { groups: T[][]; forcePush: T | null } {
+ const byId = new Map(actions.map((action) => [action.id, action]))
+ const groups = MENU_GROUPS.map((ids) =>
+ ids.flatMap((id) => {
+ const action = byId.get(id)
+ return action ? [action] : []
+ })
+ ).filter((group) => group.length > 0)
+ return { groups, forcePush: byId.get('force-push') ?? null }
+}
+
export function getMobilePrimaryActionHint(
decision: SourceControlCommitAreaPrimaryActionDecision
): string {
diff --git a/mobile/src/source-control/mobile-source-control-list-styles.ts b/mobile/src/source-control/mobile-source-control-list-styles.ts
index 04fed32b715..6b43b3a73e7 100644
--- a/mobile/src/source-control/mobile-source-control-list-styles.ts
+++ b/mobile/src/source-control/mobile-source-control-list-styles.ts
@@ -11,13 +11,21 @@ export const listStyles = themedStyleSheet(() => ({
sectionHeaderBleed: {
marginHorizontal: -spacing.lg
},
- branchCompareBlock: {
- paddingBottom: spacing.sm
+ sectionAction: {
+ color: colors.textPrimary,
+ fontSize: 13,
+ fontWeight: '600'
},
- branchSectionSubtitle: {
- color: colors.textMuted,
- fontSize: typography.metaSize,
- paddingBottom: spacing.xs
+ sectionActions: {
+ flexDirection: 'row',
+ gap: spacing.lg
+ },
+ sectionActionPressed: {
+ opacity: 0.6
+ },
+ sectionMeta: {
+ color: colors.textSecondary,
+ fontSize: 13
},
branchStateRow: {
minHeight: 44,
@@ -34,21 +42,20 @@ export const listStyles = themedStyleSheet(() => ({
fontSize: typography.metaSize,
lineHeight: 18
},
+ // Opaque so a left swipe uncovers Discard only as the row slides.
fileRow: {
minHeight: 50,
flexDirection: 'row',
alignItems: 'center',
gap: spacing.sm,
paddingVertical: spacing.sm,
+ backgroundColor: colors.bgBase,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: colors.borderSubtle
},
fileRowPressed: {
backgroundColor: colors.bgPanel
},
- fileRowDisabled: {
- opacity: 0.78
- },
fileRowUnavailable: {
opacity: 0.72
},
@@ -66,23 +73,19 @@ export const listStyles = themedStyleSheet(() => ({
flex: 1,
minWidth: 0
},
- filePath: {
+ fileName: {
color: colors.textPrimary,
fontSize: typography.bodySize
},
- filePathDisabled: {
- color: colors.textSecondary
+ fileNameGone: {
+ color: colors.textSecondary,
+ textDecorationLine: 'line-through'
},
fileMeta: {
color: colors.textMuted,
fontSize: typography.metaSize,
marginTop: 2
},
- rowActions: {
- flexDirection: 'row',
- alignItems: 'center',
- gap: spacing.xs
- },
iconButton: {
width: 32,
height: 32,
@@ -90,6 +93,23 @@ export const listStyles = themedStyleSheet(() => ({
alignItems: 'center',
justifyContent: 'center'
},
+ // Behind the row's right end; the row slides left to uncover it.
+ discardAction: {
+ position: 'absolute',
+ top: 0,
+ right: 0,
+ bottom: 0,
+ width: 84,
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: 2,
+ backgroundColor: colors.statusRed
+ },
+ discardActionText: {
+ color: colors.onStatusRed,
+ fontSize: typography.metaSize,
+ fontWeight: '600'
+ },
conflictActions: {
flexDirection: 'row',
flexWrap: 'wrap',
diff --git a/mobile/src/source-control/mobile-source-control-styles.ts b/mobile/src/source-control/mobile-source-control-styles.ts
index 4068ae43a78..5c84eaf35f2 100644
--- a/mobile/src/source-control/mobile-source-control-styles.ts
+++ b/mobile/src/source-control/mobile-source-control-styles.ts
@@ -13,62 +13,67 @@ const baseStyles = themedStyleSheet(() => ({
header: {
backgroundColor: colors.bgBase
},
- summaryCard: {
- margin: spacing.lg,
- marginBottom: spacing.sm,
+ statusLine: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: spacing.sm,
+ paddingTop: spacing.md,
+ paddingBottom: spacing.xs
+ },
+ branchButton: {
+ flexShrink: 1,
+ minHeight: 32,
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 6
+ },
+ branchText: {
+ flexShrink: 1,
+ color: colors.textPrimary,
+ fontSize: typography.bodySize,
+ fontWeight: '600'
+ },
+ conflictBanner: {
+ marginTop: spacing.sm,
padding: spacing.md,
+ gap: spacing.sm,
borderRadius: radii.surface,
backgroundColor: colors.bgPanel,
borderWidth: StyleSheet.hairlineWidth,
- borderColor: colors.borderSubtle
+ borderColor: colors.statusAmber
},
- summaryHeader: {
+ conflictBannerTitleRow: {
flexDirection: 'row',
alignItems: 'center',
- justifyContent: 'space-between',
- gap: spacing.md
+ gap: spacing.sm
},
- branchLine: {
- flex: 1,
- minWidth: 0,
- flexDirection: 'row',
- alignItems: 'center',
- gap: spacing.xs
- },
- branchText: {
+ conflictBannerTitle: {
flex: 1,
color: colors.textPrimary,
fontSize: typography.bodySize,
fontWeight: '600'
},
- syncText: {
+ conflictBannerText: {
color: colors.textSecondary,
fontSize: typography.metaSize
},
- countRow: {
+ commitsRow: {
+ minHeight: 48,
flexDirection: 'row',
- flexWrap: 'wrap',
- gap: spacing.md,
- marginTop: spacing.sm
- },
- countText: {
- color: colors.textSecondary,
- fontSize: typography.metaSize
- },
- // Keep Abort below counts so narrow cards cannot overflow.
- conflictRow: {
- flexDirection: 'row',
- flexWrap: 'wrap',
alignItems: 'center',
gap: spacing.sm,
- marginTop: spacing.sm,
- alignSelf: 'flex-start',
- maxWidth: '100%'
+ borderBottomWidth: StyleSheet.hairlineWidth,
+ borderBottomColor: colors.borderSubtle
},
- conflictText: {
- color: colors.statusAmber,
- fontSize: typography.metaSize,
- textTransform: 'capitalize'
+ commitsRowText: {
+ flex: 1,
+ color: colors.textPrimary,
+ fontSize: typography.bodySize
+ },
+ syncText: {
+ marginLeft: 'auto',
+ color: colors.textSecondary,
+ fontSize: typography.metaSize
},
abortButton: {
minHeight: 32,
@@ -125,40 +130,6 @@ const baseStyles = themedStyleSheet(() => ({
color: colors.textPrimary,
fontSize: typography.metaSize,
lineHeight: 16
- },
- bulkRow: {
- flexDirection: 'row',
- gap: spacing.sm,
- marginTop: spacing.md
- },
- bulkButton: {
- flex: 1,
- minHeight: 36,
- borderRadius: radii.control,
- backgroundColor: colors.bgRaised,
- alignItems: 'center',
- justifyContent: 'center',
- flexDirection: 'row',
- gap: spacing.xs
- },
- bulkMenuButton: {
- width: 42,
- minHeight: 36,
- borderRadius: radii.control,
- backgroundColor: colors.bgRaised,
- alignItems: 'center',
- justifyContent: 'center'
- },
- bulkButtonDisabled: {
- opacity: 0.45
- },
- bulkButtonPressed: {
- opacity: 0.75
- },
- bulkButtonText: {
- color: colors.textPrimary,
- fontSize: typography.bodySize,
- fontWeight: '600'
}
}))
diff --git a/mobile/src/source-control/source-control-status-retry.test.tsx b/mobile/src/source-control/source-control-status-retry.test.tsx
index 0ae212e0ef9..a212a66c54b 100644
--- a/mobile/src/source-control/source-control-status-retry.test.tsx
+++ b/mobile/src/source-control/source-control-status-retry.test.tsx
@@ -20,6 +20,7 @@ vi.mock('react-native-safe-area-context', () => ({ SafeAreaView: 'SafeAreaView'
vi.mock('lucide-react-native', () => ({
CircleAlert: 'Icon',
GitBranch: 'Icon',
+ RefreshCw: 'Icon',
WifiOff: 'Icon'
}))
vi.mock('./MobileSourceControlHeader', () => ({ MobileSourceControlHeader: () => null }))
@@ -55,7 +56,7 @@ vi.mock('../components/pr-sidebar/use-mobile-pr-tab', () => ({
vi.mock('../components/pr-sidebar/use-pr-tab-menu', () => ({ usePrTabMenuGroups: () => null }))
vi.mock('../navigation/use-back-claim', () => ({ useBackClaim: () => {} }))
vi.mock('./use-mobile-source-control-action-sheet', () => ({
- useMobileSourceControlActionSheet: () => ({})
+ useMobileSourceControlActionSheet: () => []
}))
vi.mock('../session/use-mobile-pr-sidebar-controller', () => ({
useMobilePrSidebarController: () => ({
@@ -82,7 +83,6 @@ vi.mock('./use-mobile-source-control-state', () => ({
status: null,
branchCompareResult: null,
branchLabel: '',
- syncLabel: '',
unstagedCount: 0,
stagedCount: 0,
branchEntries: [],
diff --git a/mobile/src/source-control/use-mobile-source-control-loaders.ts b/mobile/src/source-control/use-mobile-source-control-loaders.ts
index 4a2b821dce9..97913c55776 100644
--- a/mobile/src/source-control/use-mobile-source-control-loaders.ts
+++ b/mobile/src/source-control/use-mobile-source-control-loaders.ts
@@ -185,10 +185,9 @@ export function useMobileSourceControlLoaders(params: Params): MobileSourceContr
setScreenState((prev) => (prev.kind === 'ready' ? prev : { kind: 'loading' }))
try {
for (let attempt = 0; attempt <= SELECTOR_RETRY_COUNT; attempt += 1) {
+ // Changes rows show each file's +/- counts, so ask the host for line stats.
const reply = await gitStatusHostPayloadRead.request(client, {
- worktree: `id:${worktreeId}`,
- // Changes rows never show line counts; older hosts strip the unknown key.
- includeLineStats: false
+ worktree: `id:${worktreeId}`
})
if (!isCurrentLoad()) {
return false
diff --git a/mobile/src/source-control/use-mobile-source-control-state.ts b/mobile/src/source-control/use-mobile-source-control-state.ts
index 0caa6fdf156..5206db5c678 100644
--- a/mobile/src/source-control/use-mobile-source-control-state.ts
+++ b/mobile/src/source-control/use-mobile-source-control-state.ts
@@ -15,8 +15,7 @@ import { useMobileSourceControlKeyboardLift } from './use-mobile-source-control-
import type { RuntimeGitLocalBranches } from '../../../src/shared/runtime-types'
import {
buildMobileBranchCompareSection,
- canOpenMobileBranchCompareDiff,
- formatMobileBranchCompareSummary
+ canOpenMobileBranchCompareDiff
} from './mobile-branch-compare'
import {
@@ -89,9 +88,6 @@ export function useMobileSourceControlState(params: MobileSourceControlStatePara
() => buildMobileBranchCompareSection(branchCompareResult?.entries ?? []),
[branchCompareResult, t]
)
- const branchCompareSummaryText = branchCompareResult
- ? formatMobileBranchCompareSummary(branchCompareResult.summary)
- : null
const branchCompareCanOpen = branchCompareResult
? canOpenMobileBranchCompareDiff(branchCompareResult.summary)
: false
@@ -124,12 +120,6 @@ export function useMobileSourceControlState(params: MobileSourceControlStatePara
const branchLabel = formatBranchLabel(status?.branch, status?.head)
const upstream = status?.upstreamStatus
const upstreamKnown = upstream !== undefined
- const syncLabel =
- upstream && upstream.hasUpstream
- ? t('sourceControl.state.syncStatus', '{{ahead}} ahead, {{behind}} behind', upstream)
- : upstream && !upstream.hasUpstream
- ? t('sourceControl.state.noUpstream', 'No upstream')
- : null
const { sendGitRequest, sendCommitRequest, runGitSyncSteps } = useMobileGitRequests({
client,
@@ -239,7 +229,6 @@ export function useMobileSourceControlState(params: MobileSourceControlStatePara
status,
sections,
branchCompareResult,
- branchCompareSummaryText,
branchEntries,
shouldShowBranchCompareSection,
hasVisibleChanges,
@@ -249,7 +238,6 @@ export function useMobileSourceControlState(params: MobileSourceControlStatePara
unstagedCount,
branchLabel,
upstreamKnown,
- syncLabel,
primaryActionArgs,
createPrAction,
loadStatus,