diff --git a/config/scripts/ci-shard-timings.json b/config/scripts/ci-shard-timings.json index 19d51b803bb..bfeb2370e20 100644 --- a/config/scripts/ci-shard-timings.json +++ b/config/scripts/ci-shard-timings.json @@ -10074,7 +10074,7 @@ "src/renderer/src/lib/renderer-agent-status-observation-ingress.test.ts": 137, "src/renderer/src/lib/renderer-app-platform.test.ts": 177, "src/renderer/src/lib/renderer-memory-profile.test.ts": 64, - "src/renderer/src/lib/repo-display-labels.test.ts": 50, + "src/shared/repo-display-labels.test.ts": 50, "src/renderer/src/lib/repo-runtime-owner.test.ts": 61, "src/renderer/src/lib/repo-search.test.ts": 43, "src/renderer/src/lib/repo-slug-cache.test.ts": 37, diff --git a/mobile/rpc-foundation/goldens/matrix-settings.repo-metadata-host.platform-1.json b/mobile/rpc-foundation/goldens/matrix-settings.repo-metadata-host.platform-1.json index 27eda6c704c..d5e14ee56df 100644 --- a/mobile/rpc-foundation/goldens/matrix-settings.repo-metadata-host.platform-1.json +++ b/mobile/rpc-foundation/goldens/matrix-settings.repo-metadata-host.platform-1.json @@ -4,9 +4,9 @@ "family": "settings.repo-metadata", "namedDeltas": [], "values": { - "03c9a2ba60b9": { + "08c11e02c691": { "name": "host.platform#1", - "ordinal": 9, + "ordinal": 7, "args": [ { "name": "method", @@ -33,14 +33,15 @@ "id": "frame-4", "ok": true, "result": { - "platform": "linux" + "error": "inner refused", + "ok": false } } } }, - "0c4296f215a4": { + "18762ff064ed": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 6, "args": [ { "name": "method", @@ -78,9 +79,92 @@ } } }, - "0d7d9425dc1a": { + "19b2979d7fc2": { + "name": "repo.list#1", + "ordinal": 2, + "json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" + }, + "1e027d0127f3": { "name": "host.platform#1", - "ordinal": 9, + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "rejected", + "startedAt": 0, + "settledAt": 0, + "error": { + "category": "Error", + "message": "Connection closed", + "isRpcDeliveryUnknown": true + } + } + }, + "1f909a505b54": { + "name": "hostPlatform", + "ordinal": 13, + "value": { + "$rpc": "null" + } + }, + "299d571abf13": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 5, + "args": [ + { + "name": "method", + "value": "ssh.listTargetSummaries" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-2", + "ok": true, + "result": { + "targets": [ + { + "id": "ssh-1", + "label": "SSH" + } + ] + } + } + } + }, + "371d2b84193c": { + "name": "host.platform#1", + "ordinal": 7, "args": [ { "name": "method", @@ -104,18 +188,14 @@ "startedAt": 0, "settledAt": 0, "value": { - "error": { - "code": "method_not_found", - "message": "Unknown method" - }, "id": "frame-4", - "ok": false + "ok": true } } }, - "0f2a66846ba6": { + "3d730d2842bc": { "name": "host.platform#1", - "ordinal": 9, + "ordinal": 7, "args": [ { "name": "method", @@ -147,79 +227,9 @@ } } }, - "19b2979d7fc2": { - "name": "repo.list#1", - "ordinal": 2, - "json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" - }, - "1a160fc2d137": { + "3e996af9e799": { "name": "host.platform#1", - "ordinal": 9, - "args": [ - { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "pending", - "startedAt": 0 - } - }, - "1ff13ee8621c": { - "name": "repoIconsByName", - "ordinal": 4, - "value": [] - }, - "26d576339b0b": { - "name": "host.platform#1", - "ordinal": 9, - "args": [ - { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-4", - "ok": true, - "result": { - "error": "refused" - } - } - } - }, - "28cff8ea82f0": { - "name": "host.platform#1", - "ordinal": 9, + "ordinal": 7, "args": [ { "name": "method", @@ -245,16 +255,16 @@ "value": { "error": { "code": "refused", - "message": "outer refused" + "message": "" }, "id": "frame-4", "ok": false } } }, - "2c656b54bd5a": { + "443ebd7b9f5e": { "name": "host.platform#1", - "ordinal": 9, + "ordinal": 7, "args": [ { "name": "method", @@ -278,30 +288,15 @@ "startedAt": 0, "settledAt": 0, "value": { + "error": { + "code": "method_not_found", + "message": "Unknown method" + }, "id": "frame-4", - "ok": true, - "result": { - "error": { - "message": "inner refused" - }, - "ok": false - } + "ok": false } } }, - "2f1b95eac8f1": { - "name": "repoColorsByName", - "ordinal": 3, - "value": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ] - }, - "36a96ddbd396": { - "name": "host.platform#1", - "ordinal": 12, - "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" - }, "4526bff18b74": { "name": "repo.list#1", "ordinal": 1, @@ -349,78 +344,9 @@ } } }, - "50027661b715": { + "5248b37ec085": { "name": "host.platform#1", - "ordinal": 9, - "args": [ - { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "rejected", - "startedAt": 0, - "settledAt": 0, - "error": { - "category": "Error", - "message": "", - "isRpcDeliveryUnknown": true - } - } - }, - "53226cb82ec3": { - "name": "hostPlatform", - "ordinal": 15, - "value": "linux" - }, - "53dea128b181": { - "name": "host.platform#1", - "ordinal": 9, - "args": [ - { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "rejected", - "startedAt": 0, - "settledAt": 0, - "error": { - "category": "Error", - "message": "Connection closed", - "isRpcDeliveryUnknown": true - } - } - }, - "5413fbee66a1": { - "name": "host.platform#1", - "ordinal": 9, + "ordinal": 7, "args": [ { "name": "method", @@ -447,40 +373,188 @@ "id": "frame-4", "ok": true, "result": { - "error": "inner refused", + "platform": "linux" + } + } + } + }, + "661b4721aec6": { + "name": "desktopRepos", + "ordinal": 3, + "value": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ] + }, + "68916f125c3c": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ], + "repoHostIdByRepoId": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] + }, + "6d43117882f3": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ], + "hostHealthById": [], + "hostLabelById": [["ssh:ssh-1", "SSH"]], + "hostPlatform": "linux", + "repoHostIdByRepoId": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] + }, + "7c508088602c": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "pending", + "startedAt": 0 + } + }, + "8b618db22116": { + "name": "settings.get#1", + "ordinal": 9, + "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" + }, + "902c0b2ed3ce": { + "name": "settings.get#1", + "ordinal": 6, + "args": [ + { + "name": "method", + "value": "settings.get" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "pending", + "startedAt": 0 + } + }, + "91e7f4ef9a77": { + "name": "hostPlatform", + "ordinal": 13, + "value": "linux" + }, + "9270aeb7d9c6": { + "status": "pending", + "startedAt": 0 + }, + "c1246f5792ee": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 8, + "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" + }, + "c1d6fc758acc": { + "name": "host.platform#1", + "ordinal": 10, + "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" + }, + "c294eaf242bb": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-4", + "ok": true, + "result": { + "error": { + "message": "inner refused" + }, "ok": false } } } }, - "6063e534d9db": { - "name": "hostHealthById", - "ordinal": 13, - "value": [] - }, - "6134b73f18d0": { - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ], - "repoHostIdByRepoId": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "668a7e313975": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 10, - "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" - }, - "77aba92ac03c": { + "c2d611080e31": { "name": "host.platform#1", - "ordinal": 9, + "ordinal": 7, "args": [ { "name": "method", @@ -510,142 +584,9 @@ } } }, - "7e772c1c111d": { - "name": "repoIdsByName", - "ordinal": 5, - "value": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "8ef3b348c179": { - "name": "repoHostIdByRepoId", - "ordinal": 6, - "value": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ] - }, - "9270aeb7d9c6": { - "status": "pending", - "startedAt": 0 - }, - "96e0c54d86b0": { - "name": "settings.get#1", - "ordinal": 8, - "args": [ - { - "name": "method", - "value": "settings.get" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "pending", - "startedAt": 0 - } - }, - "9b82184a62fd": { + "c2d95318e127": { "name": "host.platform#1", - "ordinal": 9, - "args": [ - { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-4", - "ok": true - } - } - }, - "9d20695fdb34": { - "name": "hostPlatform", - "ordinal": 15, - "value": { - "$rpc": "null" - } - }, - "b428bce85ee7": { - "name": "settings.get#1", - "ordinal": 11, - "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" - }, - "bea13a4a2e87": { - "name": "hostLabelById", - "ordinal": 14, - "value": [["ssh:ssh-1", "SSH"]] - }, - "c752e67787f9": { - "name": "ssh.listTargetSummaries#1", "ordinal": 7, - "args": [ - { - "name": "method", - "value": "ssh.listTargetSummaries" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-2", - "ok": true, - "result": { - "targets": [ - { - "id": "ssh-1", - "label": "SSH" - } - ] - } - } - } - }, - "d77076db12f0": { - "name": "host.platform#1", - "ordinal": 9, "args": [ { "name": "method", @@ -671,31 +612,120 @@ "value": { "error": { "code": "refused", - "message": "" + "message": "outer refused" }, "id": "frame-4", "ok": false } } }, - "e841f87c65aa": { + "cc76dd9f9341": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-4", + "ok": true, + "result": { + "error": "refused" + } + } + } + }, + "d88cba4e044f": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ], "hostHealthById": [], "hostLabelById": [["ssh:ssh-1", "SSH"]], "hostPlatform": { "$rpc": "null" }, - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ], "repoHostIdByRepoId": [ ["repo-1", "local"], ["repo-2", "ssh:ssh-1"] + ] + }, + "d9c464d0dc78": { + "name": "hostLabelById", + "ordinal": 12, + "value": [["ssh:ssh-1", "SSH"]] + }, + "df14e89c3d1c": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] + "settlement": { + "status": "rejected", + "startedAt": 0, + "settledAt": 0, + "error": { + "category": "Error", + "message": "", + "isRpcDeliveryUnknown": true + } + } + }, + "e2881f748a46": { + "name": "hostHealthById", + "ordinal": 11, + "value": [] + }, + "e49da3e1abca": { + "name": "repoHostIdByRepoId", + "ordinal": 4, + "value": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] ] }, "eb79a9b3682a": { @@ -705,24 +735,6 @@ "value": { "$rpc": "undefined" } - }, - "f4667dda0187": { - "hostHealthById": [], - "hostLabelById": [["ssh:ssh-1", "SSH"]], - "hostPlatform": "linux", - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ], - "repoHostIdByRepoId": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] } }, "recording": { @@ -731,265 +743,241 @@ { "id": "settings-repo-metadata-fulfilled.prelude:settings-pending", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.prelude:cleanup", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "53dea128b181"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "1e027d0127f3"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "6134b73f18d0", + "state": "68916f125c3c", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } }, { "id": "settings-repo-metadata-fulfilled.normal:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.result-absent:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "9b82184a62fd"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "371d2b84193c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "e841f87c65aa", + "state": "d88cba4e044f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } }, { "id": "settings-repo-metadata-fulfilled.result-null:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "0f2a66846ba6"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "3d730d2842bc"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "e841f87c65aa", + "state": "d88cba4e044f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } }, { "id": "settings-repo-metadata-fulfilled.inner-ok-missing:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "26d576339b0b"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "cc76dd9f9341"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "e841f87c65aa", + "state": "d88cba4e044f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } }, { "id": "settings-repo-metadata-fulfilled.inner-false-string-error:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "5413fbee66a1"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "08c11e02c691"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "e841f87c65aa", + "state": "d88cba4e044f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } }, { "id": "settings-repo-metadata-fulfilled.inner-false-object-error:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "2c656b54bd5a"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "c294eaf242bb"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "e841f87c65aa", + "state": "d88cba4e044f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } }, { "id": "settings-repo-metadata-fulfilled.outer-refused:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "28cff8ea82f0"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "c2d95318e127"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "e841f87c65aa", + "state": "d88cba4e044f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } }, { "id": "settings-repo-metadata-fulfilled.outer-refused-no-message:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "d77076db12f0"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "3e996af9e799"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "e841f87c65aa", + "state": "d88cba4e044f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } }, { "id": "settings-repo-metadata-fulfilled.method-not-found:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "0d7d9425dc1a"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "443ebd7b9f5e"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "e841f87c65aa", + "state": "d88cba4e044f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } }, { "id": "settings-repo-metadata-fulfilled.transport-rejection:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "77aba92ac03c"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "c2d611080e31"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "e841f87c65aa", + "state": "d88cba4e044f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } }, { "id": "settings-repo-metadata-fulfilled.transport-rejection-no-message:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "50027661b715"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "df14e89c3d1c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "e841f87c65aa", + "state": "d88cba4e044f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } } diff --git a/mobile/rpc-foundation/goldens/matrix-settings.repo-metadata-repo.list-1.json b/mobile/rpc-foundation/goldens/matrix-settings.repo-metadata-repo.list-1.json index bd38ed25958..14c5c95ee04 100644 --- a/mobile/rpc-foundation/goldens/matrix-settings.repo-metadata-repo.list-1.json +++ b/mobile/rpc-foundation/goldens/matrix-settings.repo-metadata-repo.list-1.json @@ -4,80 +4,6 @@ "family": "settings.repo-metadata", "namedDeltas": [], "values": { - "03c9a2ba60b9": { - "name": "host.platform#1", - "ordinal": 9, - "args": [ - { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-4", - "ok": true, - "result": { - "platform": "linux" - } - } - } - }, - "0c4296f215a4": { - "name": "settings.get#1", - "ordinal": 8, - "args": [ - { - "name": "method", - "value": "settings.get" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-3", - "ok": true, - "result": { - "settings": { - "defaultTuiAgent": "codex", - "disabledTuiAgents": ["claude"], - "hostSettingOverrides": {}, - "prBotAuthorOverrides": ["bot-user"], - "visibleTaskProviders": ["github", "linear"] - } - } - } - } - }, "12e70439f294": { "name": "repo.list#1", "ordinal": 1, @@ -145,18 +71,13 @@ } } }, - "19b2979d7fc2": { - "name": "repo.list#1", - "ordinal": 2, - "json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" - }, - "1a160fc2d137": { - "name": "host.platform#1", - "ordinal": 9, + "18762ff064ed": { + "name": "settings.get#1", + "ordinal": 6, "args": [ { "name": "method", - "value": "host.platform" + "value": "settings.get" }, { "name": "params", @@ -172,10 +93,29 @@ } ], "settlement": { - "status": "pending", - "startedAt": 0 + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-3", + "ok": true, + "result": { + "settings": { + "defaultTuiAgent": "codex", + "disabledTuiAgents": ["claude"], + "hostSettingOverrides": {}, + "prBotAuthorOverrides": ["bot-user"], + "visibleTaskProviders": ["github", "linear"] + } + } + } } }, + "19b2979d7fc2": { + "name": "repo.list#1", + "ordinal": 2, + "json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" + }, "1b23dab83fcf": { "name": "repo.list#1", "ordinal": 1, @@ -210,23 +150,44 @@ } } }, - "1ff13ee8621c": { - "name": "repoIconsByName", - "ordinal": 4, - "value": [] - }, - "2f1b95eac8f1": { - "name": "repoColorsByName", - "ordinal": 3, - "value": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ] - }, - "36a96ddbd396": { - "name": "host.platform#1", - "ordinal": 12, - "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" + "299d571abf13": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 5, + "args": [ + { + "name": "method", + "value": "ssh.listTargetSummaries" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-2", + "ok": true, + "result": { + "targets": [ + { + "id": "ssh-1", + "label": "SSH" + } + ] + } + } + } }, "44136fa355b3": {}, "4526bff18b74": { @@ -310,30 +271,39 @@ } } }, - "53226cb82ec3": { - "name": "hostPlatform", - "ordinal": 15, - "value": "linux" - }, - "6063e534d9db": { - "name": "hostHealthById", - "ordinal": 13, - "value": [] - }, - "6134b73f18d0": { - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] + "5248b37ec085": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } ], - "repoHostIdByRepoId": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-4", + "ok": true, + "result": { + "platform": "linux" + } + } + } }, "624ec4e5082c": { "name": "repo.list#1", @@ -370,10 +340,66 @@ } } }, - "668a7e313975": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 10, - "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" + "661b4721aec6": { + "name": "desktopRepos", + "ordinal": 3, + "value": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ] + }, + "68916f125c3c": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ], + "repoHostIdByRepoId": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] + }, + "6d43117882f3": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ], + "hostHealthById": [], + "hostLabelById": [["ssh:ssh-1", "SSH"]], + "hostPlatform": "linux", + "repoHostIdByRepoId": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] }, "747c556da67a": { "name": "repo.list#1", @@ -406,13 +432,31 @@ } } }, - "7e772c1c111d": { - "name": "repoIdsByName", - "ordinal": 5, - "value": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] + "7c508088602c": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "pending", + "startedAt": 0 + } }, "8a72d3d14d44": { "name": "repo.list#1", @@ -449,21 +493,14 @@ } } }, - "8ef3b348c179": { - "name": "repoHostIdByRepoId", - "ordinal": 6, - "value": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ] - }, - "9270aeb7d9c6": { - "status": "pending", - "startedAt": 0 - }, - "96e0c54d86b0": { + "8b618db22116": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 9, + "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" + }, + "902c0b2ed3ce": { + "name": "settings.get#1", + "ordinal": 6, "args": [ { "name": "method", @@ -487,54 +524,24 @@ "startedAt": 0 } }, - "b428bce85ee7": { - "name": "settings.get#1", - "ordinal": 11, - "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" + "91e7f4ef9a77": { + "name": "hostPlatform", + "ordinal": 13, + "value": "linux" }, - "bea13a4a2e87": { - "name": "hostLabelById", - "ordinal": 14, - "value": [["ssh:ssh-1", "SSH"]] + "9270aeb7d9c6": { + "status": "pending", + "startedAt": 0 }, - "c752e67787f9": { + "c1246f5792ee": { "name": "ssh.listTargetSummaries#1", - "ordinal": 7, - "args": [ - { - "name": "method", - "value": "ssh.listTargetSummaries" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-2", - "ok": true, - "result": { - "targets": [ - { - "id": "ssh-1", - "label": "SSH" - } - ] - } - } - } + "ordinal": 8, + "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" + }, + "c1d6fc758acc": { + "name": "host.platform#1", + "ordinal": 10, + "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" }, "d68475063b62": { "name": "repo.list#1", @@ -573,6 +580,11 @@ } } }, + "d9c464d0dc78": { + "name": "hostLabelById", + "ordinal": 12, + "value": [["ssh:ssh-1", "SSH"]] + }, "dae756300589": { "name": "repo.list#1", "ordinal": 1, @@ -605,6 +617,19 @@ } } }, + "e2881f748a46": { + "name": "hostHealthById", + "ordinal": 11, + "value": [] + }, + "e49da3e1abca": { + "name": "repoHostIdByRepoId", + "ordinal": 4, + "value": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] + }, "eb79a9b3682a": { "status": "fulfilled", "startedAt": 0, @@ -647,24 +672,6 @@ "ok": false } } - }, - "f4667dda0187": { - "hostHealthById": [], - "hostLabelById": [["ssh:ssh-1", "SSH"]], - "hostPlatform": "linux", - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ], - "repoHostIdByRepoId": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] } }, "recording": { @@ -673,34 +680,32 @@ { "id": "settings-repo-metadata-fulfilled.normal:settings-pending", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.normal:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, diff --git a/mobile/rpc-foundation/goldens/matrix-settings.repo-metadata-settings.get-1.json b/mobile/rpc-foundation/goldens/matrix-settings.repo-metadata-settings.get-1.json index 423f3062147..46d391c5870 100644 --- a/mobile/rpc-foundation/goldens/matrix-settings.repo-metadata-settings.get-1.json +++ b/mobile/rpc-foundation/goldens/matrix-settings.repo-metadata-settings.get-1.json @@ -4,13 +4,45 @@ "family": "settings.repo-metadata", "namedDeltas": [], "values": { - "03c9a2ba60b9": { - "name": "host.platform#1", - "ordinal": 9, + "010dfaf4d076": { + "name": "settings.get#1", + "ordinal": 6, "args": [ { "name": "method", - "value": "host.platform" + "value": "settings.get" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "rejected", + "startedAt": 0, + "settledAt": 0, + "error": { + "category": "Error", + "message": "transport failure", + "isRpcDeliveryUnknown": true + } + } + }, + "156e191c4a8b": { + "name": "settings.get#1", + "ordinal": 6, + "args": [ + { + "name": "method", + "value": "settings.get" }, { "name": "params", @@ -30,17 +62,18 @@ "startedAt": 0, "settledAt": 0, "value": { - "id": "frame-4", + "id": "frame-3", "ok": true, "result": { - "platform": "linux" + "error": "inner refused", + "ok": false } } } }, - "0c4296f215a4": { + "18762ff064ed": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 6, "args": [ { "name": "method", @@ -78,9 +111,124 @@ } } }, - "0e736afe9db0": { + "19b2979d7fc2": { + "name": "repo.list#1", + "ordinal": 2, + "json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" + }, + "1e027d0127f3": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "rejected", + "startedAt": 0, + "settledAt": 0, + "error": { + "category": "Error", + "message": "Connection closed", + "isRpcDeliveryUnknown": true + } + } + }, + "1f909a505b54": { + "name": "hostPlatform", + "ordinal": 13, + "value": { + "$rpc": "null" + } + }, + "299d571abf13": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 5, + "args": [ + { + "name": "method", + "value": "ssh.listTargetSummaries" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-2", + "ok": true, + "result": { + "targets": [ + { + "id": "ssh-1", + "label": "SSH" + } + ] + } + } + } + }, + "302f82cb9a68": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 6, + "args": [ + { + "name": "method", + "value": "settings.get" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "rejected", + "startedAt": 0, + "settledAt": 0, + "error": { + "category": "Error", + "message": "Connection closed", + "isRpcDeliveryUnknown": true + } + } + }, + "33781e3dd8ff": { + "name": "settings.get#1", + "ordinal": 6, "args": [ { "name": "method", @@ -107,95 +255,11 @@ "id": "frame-3", "ok": true, "result": { - "$rpc": "null" + "error": "refused" } } } }, - "191d1c98ca5c": { - "name": "settings.get#1", - "ordinal": 8, - "args": [ - { - "name": "method", - "value": "settings.get" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "error": { - "code": "refused", - "message": "" - }, - "id": "frame-3", - "ok": false - } - } - }, - "19b2979d7fc2": { - "name": "repo.list#1", - "ordinal": 2, - "json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" - }, - "1a160fc2d137": { - "name": "host.platform#1", - "ordinal": 9, - "args": [ - { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "pending", - "startedAt": 0 - } - }, - "1ff13ee8621c": { - "name": "repoIconsByName", - "ordinal": 4, - "value": [] - }, - "2f1b95eac8f1": { - "name": "repoColorsByName", - "ordinal": 3, - "value": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ] - }, - "36a96ddbd396": { - "name": "host.platform#1", - "ordinal": 12, - "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" - }, "4526bff18b74": { "name": "repo.list#1", "ordinal": 1, @@ -243,9 +307,43 @@ } } }, - "4f9cd81446d9": { + "5248b37ec085": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-4", + "ok": true, + "result": { + "platform": "linux" + } + } + } + }, + "597c187eb03e": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 6, "args": [ { "name": "method", @@ -270,22 +368,78 @@ "settledAt": 0, "value": { "error": { - "code": "method_not_found", - "message": "Unknown method" + "code": "refused", + "message": "" }, "id": "frame-3", "ok": false } } }, - "53226cb82ec3": { - "name": "hostPlatform", - "ordinal": 15, - "value": "linux" + "661b4721aec6": { + "name": "desktopRepos", + "ordinal": 3, + "value": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ] }, - "53dea128b181": { + "68916f125c3c": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ], + "repoHostIdByRepoId": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] + }, + "6d43117882f3": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ], + "hostHealthById": [], + "hostLabelById": [["ssh:ssh-1", "SSH"]], + "hostPlatform": "linux", + "repoHostIdByRepoId": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] + }, + "7c508088602c": { "name": "host.platform#1", - "ordinal": 9, + "ordinal": 7, "args": [ { "name": "method", @@ -304,232 +458,14 @@ } } ], - "settlement": { - "status": "rejected", - "startedAt": 0, - "settledAt": 0, - "error": { - "category": "Error", - "message": "Connection closed", - "isRpcDeliveryUnknown": true - } - } - }, - "6063e534d9db": { - "name": "hostHealthById", - "ordinal": 13, - "value": [] - }, - "6134b73f18d0": { - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ], - "repoHostIdByRepoId": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "628a0201bdef": { - "name": "settings.get#1", - "ordinal": 8, - "args": [ - { - "name": "method", - "value": "settings.get" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "rejected", - "startedAt": 0, - "settledAt": 0, - "error": { - "category": "Error", - "message": "transport failure", - "isRpcDeliveryUnknown": true - } - } - }, - "668a7e313975": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 10, - "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" - }, - "7e772c1c111d": { - "name": "repoIdsByName", - "ordinal": 5, - "value": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "80c010782ff2": { - "name": "settings.get#1", - "ordinal": 8, - "args": [ - { - "name": "method", - "value": "settings.get" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-3", - "ok": true, - "result": { - "error": "refused" - } - } - } - }, - "8eda68e753fa": { - "name": "settings.get#1", - "ordinal": 8, - "args": [ - { - "name": "method", - "value": "settings.get" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "rejected", - "startedAt": 0, - "settledAt": 0, - "error": { - "category": "Error", - "message": "", - "isRpcDeliveryUnknown": true - } - } - }, - "8ef3b348c179": { - "name": "repoHostIdByRepoId", - "ordinal": 6, - "value": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ] - }, - "9270aeb7d9c6": { - "status": "pending", - "startedAt": 0 - }, - "96e0c54d86b0": { - "name": "settings.get#1", - "ordinal": 8, - "args": [ - { - "name": "method", - "value": "settings.get" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], "settlement": { "status": "pending", "startedAt": 0 } }, - "9d20695fdb34": { - "name": "hostPlatform", - "ordinal": 15, - "value": { - "$rpc": "null" - } - }, - "ae308dad876a": { + "80c9ec66b537": { "name": "settings.get#1", - "ordinal": 8, - "args": [ - { - "name": "method", - "value": "settings.get" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-3", - "ok": true - } - } - }, - "b428bce85ee7": { - "name": "settings.get#1", - "ordinal": 11, - "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" - }, - "bba1cbe66ba5": { - "name": "settings.get#1", - "ordinal": 8, + "ordinal": 6, "args": [ { "name": "method", @@ -562,18 +498,53 @@ } } }, - "bea13a4a2e87": { - "name": "hostLabelById", - "ordinal": 14, - "value": [["ssh:ssh-1", "SSH"]] + "8b618db22116": { + "name": "settings.get#1", + "ordinal": 9, + "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" }, - "c752e67787f9": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 7, + "902c0b2ed3ce": { + "name": "settings.get#1", + "ordinal": 6, "args": [ { "name": "method", - "value": "ssh.listTargetSummaries" + "value": "settings.get" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "pending", + "startedAt": 0 + } + }, + "91e7f4ef9a77": { + "name": "hostPlatform", + "ordinal": 13, + "value": "linux" + }, + "9270aeb7d9c6": { + "status": "pending", + "startedAt": 0 + }, + "a58fc81113a0": { + "name": "settings.get#1", + "ordinal": 6, + "args": [ + { + "name": "method", + "value": "settings.get" }, { "name": "params", @@ -593,22 +564,62 @@ "startedAt": 0, "settledAt": 0, "value": { - "id": "frame-2", + "id": "frame-3", "ok": true, "result": { - "targets": [ - { - "id": "ssh-1", - "label": "SSH" - } - ] + "$rpc": "null" } } } }, - "da3e3c335005": { + "aec686ccee7e": { "name": "settings.get#1", + "ordinal": 6, + "args": [ + { + "name": "method", + "value": "settings.get" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "error": { + "code": "method_not_found", + "message": "Unknown method" + }, + "id": "frame-3", + "ok": false + } + } + }, + "c1246f5792ee": { + "name": "ssh.listTargetSummaries#1", "ordinal": 8, + "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" + }, + "c1d6fc758acc": { + "name": "host.platform#1", + "ordinal": 10, + "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" + }, + "d82cff5cd531": { + "name": "settings.get#1", + "ordinal": 6, "args": [ { "name": "method", @@ -643,41 +654,14 @@ } } }, - "dd3998116e8e": { - "name": "settings.get#1", - "ordinal": 8, - "args": [ - { - "name": "method", - "value": "settings.get" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "rejected", - "startedAt": 0, - "settledAt": 0, - "error": { - "category": "Error", - "message": "Connection closed", - "isRpcDeliveryUnknown": true - } - } + "d9c464d0dc78": { + "name": "hostLabelById", + "ordinal": 12, + "value": [["ssh:ssh-1", "SSH"]] }, - "e44262c95a88": { + "dd998f128ff4": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 6, "args": [ { "name": "method", @@ -702,14 +686,23 @@ "settledAt": 0, "value": { "id": "frame-3", - "ok": true, - "result": { - "error": "inner refused", - "ok": false - } + "ok": true } } }, + "e2881f748a46": { + "name": "hostHealthById", + "ordinal": 11, + "value": [] + }, + "e49da3e1abca": { + "name": "repoHostIdByRepoId", + "ordinal": 4, + "value": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] + }, "eb79a9b3682a": { "status": "fulfilled", "startedAt": 0, @@ -718,23 +711,37 @@ "$rpc": "undefined" } }, - "f4667dda0187": { - "hostHealthById": [], - "hostLabelById": [["ssh:ssh-1", "SSH"]], - "hostPlatform": "linux", - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] + "f18e81499208": { + "name": "settings.get#1", + "ordinal": 6, + "args": [ + { + "name": "method", + "value": "settings.get" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } ], - "repoHostIdByRepoId": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] + "settlement": { + "status": "rejected", + "startedAt": 0, + "settledAt": 0, + "error": { + "category": "Error", + "message": "", + "isRpcDeliveryUnknown": true + } + } } }, "recording": { @@ -743,265 +750,241 @@ { "id": "settings-repo-metadata-fulfilled.prelude:settings-pending", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.prelude:cleanup", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "dd3998116e8e", "53dea128b181"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "302f82cb9a68", "1e027d0127f3"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "6134b73f18d0", + "state": "68916f125c3c", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } }, { "id": "settings-repo-metadata-fulfilled.normal:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.result-absent:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "ae308dad876a", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "dd998f128ff4", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.result-null:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0e736afe9db0", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "a58fc81113a0", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.inner-ok-missing:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "80c010782ff2", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "33781e3dd8ff", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.inner-false-string-error:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "e44262c95a88", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "156e191c4a8b", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.inner-false-object-error:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "da3e3c335005", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "d82cff5cd531", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.outer-refused:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "bba1cbe66ba5", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "80c9ec66b537", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.outer-refused-no-message:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "191d1c98ca5c", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "597c187eb03e", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.method-not-found:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "4f9cd81446d9", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "aec686ccee7e", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.transport-rejection:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "628a0201bdef", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "010dfaf4d076", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.transport-rejection-no-message:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "8eda68e753fa", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "f18e81499208", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } } diff --git a/mobile/rpc-foundation/goldens/matrix-settings.repo-metadata-ssh.listtargetsummaries-1.json b/mobile/rpc-foundation/goldens/matrix-settings.repo-metadata-ssh.listtargetsummaries-1.json index eaf4e75fb05..b6896a08bd6 100644 --- a/mobile/rpc-foundation/goldens/matrix-settings.repo-metadata-ssh.listtargetsummaries-1.json +++ b/mobile/rpc-foundation/goldens/matrix-settings.repo-metadata-ssh.listtargetsummaries-1.json @@ -4,109 +4,9 @@ "family": "settings.repo-metadata", "namedDeltas": [], "values": { - "03c9a2ba60b9": { - "name": "host.platform#1", - "ordinal": 9, - "args": [ - { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-4", - "ok": true, - "result": { - "platform": "linux" - } - } - } - }, - "069215767aa7": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 7, - "args": [ - { - "name": "method", - "value": "ssh.listTargetSummaries" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-2", - "ok": true, - "result": { - "error": "refused" - } - } - } - }, - "08d05f42e4a2": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 7, - "args": [ - { - "name": "method", - "value": "ssh.listTargetSummaries" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "rejected", - "startedAt": 0, - "settledAt": 0, - "error": { - "category": "Error", - "message": "", - "isRpcDeliveryUnknown": true - } - } - }, - "0c4296f215a4": { + "18762ff064ed": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 6, "args": [ { "name": "method", @@ -149,35 +49,9 @@ "ordinal": 2, "json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" }, - "1a160fc2d137": { - "name": "host.platform#1", - "ordinal": 9, - "args": [ - { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "pending", - "startedAt": 0 - } - }, - "1c0fba41a128": { + "299d571abf13": { "name": "ssh.listTargetSummaries#1", - "ordinal": 7, + "ordinal": 5, "args": [ { "name": "method", @@ -202,50 +76,21 @@ "settledAt": 0, "value": { "id": "frame-2", - "ok": true + "ok": true, + "result": { + "targets": [ + { + "id": "ssh-1", + "label": "SSH" + } + ] + } } } }, - "1ff13ee8621c": { - "name": "repoIconsByName", - "ordinal": 4, - "value": [] - }, - "25c61ac0428d": { + "35dd46bd7f86": { "name": "ssh.listTargetSummaries#1", - "ordinal": 7, - "args": [ - { - "name": "method", - "value": "ssh.listTargetSummaries" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "rejected", - "startedAt": 0, - "settledAt": 0, - "error": { - "category": "Error", - "message": "transport failure", - "isRpcDeliveryUnknown": true - } - } - }, - "2946dad921bd": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 7, + "ordinal": 5, "args": [ { "name": "method", @@ -278,18 +123,10 @@ } } }, - "2f1b95eac8f1": { - "name": "repoColorsByName", - "ordinal": 3, - "value": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ] - }, - "36a96ddbd396": { - "name": "host.platform#1", + "379c0eb6dc14": { + "name": "hostLabelById", "ordinal": 12, - "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" + "value": [] }, "4526bff18b74": { "name": "repo.list#1", @@ -338,9 +175,9 @@ } } }, - "4e65ef8c0641": { + "4c7b73c1c4f4": { "name": "ssh.listTargetSummaries#1", - "ordinal": 7, + "ordinal": 5, "args": [ { "name": "method", @@ -367,15 +204,135 @@ "id": "frame-2", "ok": true, "result": { - "error": "inner refused", - "ok": false + "$rpc": "null" } } } }, - "4ffa98ab8ca7": { - "name": "ssh.listTargetSummaries#1", + "5248b37ec085": { + "name": "host.platform#1", "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-4", + "ok": true, + "result": { + "platform": "linux" + } + } + } + }, + "661b4721aec6": { + "name": "desktopRepos", + "ordinal": 3, + "value": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ] + }, + "68916f125c3c": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ], + "repoHostIdByRepoId": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] + }, + "6d43117882f3": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ], + "hostHealthById": [], + "hostLabelById": [["ssh:ssh-1", "SSH"]], + "hostPlatform": "linux", + "repoHostIdByRepoId": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] + }, + "7c508088602c": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "pending", + "startedAt": 0 + } + }, + "887fb09573e8": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 5, "args": [ { "name": "method", @@ -410,37 +367,49 @@ } } }, - "523753167e39": { - "hostHealthById": [], - "hostLabelById": [], - "hostPlatform": "linux", - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ], - "repoHostIdByRepoId": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] + "8b618db22116": { + "name": "settings.get#1", + "ordinal": 9, + "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" }, - "53226cb82ec3": { + "902c0b2ed3ce": { + "name": "settings.get#1", + "ordinal": 6, + "args": [ + { + "name": "method", + "value": "settings.get" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "pending", + "startedAt": 0 + } + }, + "91e7f4ef9a77": { "name": "hostPlatform", - "ordinal": 15, + "ordinal": 13, "value": "linux" }, - "6063e534d9db": { - "name": "hostHealthById", - "ordinal": 13, - "value": [] + "9270aeb7d9c6": { + "status": "pending", + "startedAt": 0 }, - "60676e66d4b6": { + "943ef4ae0f6c": { "name": "ssh.listTargetSummaries#1", - "ordinal": 7, + "ordinal": 5, "args": [ { "name": "method", @@ -473,53 +442,147 @@ } } }, - "6134b73f18d0": { - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] + "99367b73f33f": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } ], + "hostHealthById": [], + "hostLabelById": [], + "hostPlatform": "linux", "repoHostIdByRepoId": [ ["repo-1", "local"], ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] ] }, - "668a7e313975": { + "9ad75b2a47cd": { "name": "ssh.listTargetSummaries#1", - "ordinal": 10, + "ordinal": 5, + "args": [ + { + "name": "method", + "value": "ssh.listTargetSummaries" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "rejected", + "startedAt": 0, + "settledAt": 0, + "error": { + "category": "Error", + "message": "", + "isRpcDeliveryUnknown": true + } + } + }, + "a7a5b2905c3e": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 5, + "args": [ + { + "name": "method", + "value": "ssh.listTargetSummaries" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-2", + "ok": true, + "result": { + "error": "refused" + } + } + } + }, + "a8b44d1f7b7f": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 5, + "args": [ + { + "name": "method", + "value": "ssh.listTargetSummaries" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-2", + "ok": true, + "result": { + "error": "inner refused", + "ok": false + } + } + } + }, + "c1246f5792ee": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 8, "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" }, - "7e772c1c111d": { - "name": "repoIdsByName", + "c1d6fc758acc": { + "name": "host.platform#1", + "ordinal": 10, + "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" + }, + "cda9ce953e6b": { + "name": "ssh.listTargetSummaries#1", "ordinal": 5, - "value": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "8ef3b348c179": { - "name": "repoHostIdByRepoId", - "ordinal": 6, - "value": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ] - }, - "9270aeb7d9c6": { - "status": "pending", - "startedAt": 0 - }, - "96e0c54d86b0": { - "name": "settings.get#1", - "ordinal": 8, "args": [ { "name": "method", - "value": "settings.get" + "value": "ssh.listTargetSummaries" }, { "name": "params", @@ -535,13 +598,19 @@ } ], "settlement": { - "status": "pending", - "startedAt": 0 + "status": "rejected", + "startedAt": 0, + "settledAt": 0, + "error": { + "category": "Error", + "message": "transport failure", + "isRpcDeliveryUnknown": true + } } }, - "ac84a93b5ea0": { + "cea6f15efed0": { "name": "ssh.listTargetSummaries#1", - "ordinal": 7, + "ordinal": 5, "args": [ { "name": "method", @@ -566,96 +635,31 @@ "settledAt": 0, "value": { "id": "frame-2", - "ok": true, - "result": { - "$rpc": "null" - } + "ok": true } } }, - "b428bce85ee7": { - "name": "settings.get#1", - "ordinal": 11, - "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" - }, - "bea13a4a2e87": { + "d9c464d0dc78": { "name": "hostLabelById", - "ordinal": 14, + "ordinal": 12, "value": [["ssh:ssh-1", "SSH"]] }, - "c752e67787f9": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 7, - "args": [ - { - "name": "method", - "value": "ssh.listTargetSummaries" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-2", - "ok": true, - "result": { - "targets": [ - { - "id": "ssh-1", - "label": "SSH" - } - ] - } - } - } - }, - "daecdc0e521b": { - "name": "hostLabelById", - "ordinal": 14, + "e2881f748a46": { + "name": "hostHealthById", + "ordinal": 11, "value": [] }, - "eb79a9b3682a": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "$rpc": "undefined" - } - }, - "f4667dda0187": { - "hostHealthById": [], - "hostLabelById": [["ssh:ssh-1", "SSH"]], - "hostPlatform": "linux", - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ], - "repoHostIdByRepoId": [ + "e49da3e1abca": { + "name": "repoHostIdByRepoId", + "ordinal": 4, + "value": [ ["repo-1", "local"], ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] ] }, - "fa2cb04c517b": { + "e9b2f982bd10": { "name": "ssh.listTargetSummaries#1", - "ordinal": 7, + "ordinal": 5, "args": [ { "name": "method", @@ -687,6 +691,14 @@ "ok": false } } + }, + "eb79a9b3682a": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "$rpc": "undefined" + } } }, "recording": { @@ -695,374 +707,352 @@ { "id": "settings-repo-metadata-fulfilled.normal:settings-pending", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.normal:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.result-absent:settings-pending", "observation": { - "sender": ["4526bff18b74", "1c0fba41a128", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "cea6f15efed0", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.result-absent:settled", "observation": { - "sender": ["4526bff18b74", "1c0fba41a128", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "cea6f15efed0", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "523753167e39", + "state": "99367b73f33f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "daecdc0e521b", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "379c0eb6dc14", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.result-null:settings-pending", "observation": { - "sender": ["4526bff18b74", "ac84a93b5ea0", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "4c7b73c1c4f4", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.result-null:settled", "observation": { - "sender": ["4526bff18b74", "ac84a93b5ea0", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "4c7b73c1c4f4", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "523753167e39", + "state": "99367b73f33f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "daecdc0e521b", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "379c0eb6dc14", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.inner-ok-missing:settings-pending", "observation": { - "sender": ["4526bff18b74", "069215767aa7", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "a7a5b2905c3e", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.inner-ok-missing:settled", "observation": { - "sender": ["4526bff18b74", "069215767aa7", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "a7a5b2905c3e", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "523753167e39", + "state": "99367b73f33f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "daecdc0e521b", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "379c0eb6dc14", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.inner-false-string-error:settings-pending", "observation": { - "sender": ["4526bff18b74", "4e65ef8c0641", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "a8b44d1f7b7f", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.inner-false-string-error:settled", "observation": { - "sender": ["4526bff18b74", "4e65ef8c0641", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "a8b44d1f7b7f", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "523753167e39", + "state": "99367b73f33f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "daecdc0e521b", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "379c0eb6dc14", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.inner-false-object-error:settings-pending", "observation": { - "sender": ["4526bff18b74", "4ffa98ab8ca7", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "887fb09573e8", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.inner-false-object-error:settled", "observation": { - "sender": ["4526bff18b74", "4ffa98ab8ca7", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "887fb09573e8", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "523753167e39", + "state": "99367b73f33f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "daecdc0e521b", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "379c0eb6dc14", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.outer-refused:settings-pending", "observation": { - "sender": ["4526bff18b74", "2946dad921bd", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "35dd46bd7f86", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.outer-refused:settled", "observation": { - "sender": ["4526bff18b74", "2946dad921bd", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "35dd46bd7f86", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "523753167e39", + "state": "99367b73f33f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "daecdc0e521b", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "379c0eb6dc14", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.outer-refused-no-message:settings-pending", "observation": { - "sender": ["4526bff18b74", "fa2cb04c517b", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "e9b2f982bd10", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.outer-refused-no-message:settled", "observation": { - "sender": ["4526bff18b74", "fa2cb04c517b", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "e9b2f982bd10", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "523753167e39", + "state": "99367b73f33f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "daecdc0e521b", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "379c0eb6dc14", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.method-not-found:settings-pending", "observation": { - "sender": ["4526bff18b74", "60676e66d4b6", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "943ef4ae0f6c", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.method-not-found:settled", "observation": { - "sender": ["4526bff18b74", "60676e66d4b6", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "943ef4ae0f6c", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "523753167e39", + "state": "99367b73f33f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "daecdc0e521b", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "379c0eb6dc14", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.transport-rejection:settings-pending", "observation": { - "sender": ["4526bff18b74", "25c61ac0428d", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "cda9ce953e6b", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.transport-rejection:settled", "observation": { - "sender": ["4526bff18b74", "25c61ac0428d", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "cda9ce953e6b", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "523753167e39", + "state": "99367b73f33f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "daecdc0e521b", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "379c0eb6dc14", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.transport-rejection-no-message:settings-pending", "observation": { - "sender": ["4526bff18b74", "08d05f42e4a2", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "9ad75b2a47cd", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.transport-rejection-no-message:settled", "observation": { - "sender": ["4526bff18b74", "08d05f42e4a2", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "9ad75b2a47cd", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "523753167e39", + "state": "99367b73f33f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "daecdc0e521b", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "379c0eb6dc14", + "91e7f4ef9a77" ] } } diff --git a/mobile/rpc-foundation/goldens/schedules-settings-repo-metadata-fulfilled.json b/mobile/rpc-foundation/goldens/schedules-settings-repo-metadata-fulfilled.json index a81b7772b49..2a1528af935 100644 --- a/mobile/rpc-foundation/goldens/schedules-settings-repo-metadata-fulfilled.json +++ b/mobile/rpc-foundation/goldens/schedules-settings-repo-metadata-fulfilled.json @@ -4,13 +4,13 @@ "family": "settings.repo-metadata", "namedDeltas": [], "values": { - "031c2df78b67": { - "name": "host.platform#1", - "ordinal": 9, + "0603f3cff57a": { + "name": "settings.get#1", + "ordinal": 6, "args": [ { "name": "method", - "value": "host.platform" + "value": "settings.get" }, { "name": "params", @@ -41,9 +41,9 @@ } } }, - "03c9a2ba60b9": { + "0788f40c85d2": { "name": "host.platform#1", - "ordinal": 9, + "ordinal": 7, "args": [ { "name": "method", @@ -63,21 +63,19 @@ } ], "settlement": { - "status": "fulfilled", + "status": "rejected", "startedAt": 0, "settledAt": 0, - "value": { - "id": "frame-4", - "ok": true, - "result": { - "platform": "linux" - } + "error": { + "category": "Error", + "message": "Connection lost", + "isRpcDeliveryUnknown": true } } }, - "0c4296f215a4": { + "18762ff064ed": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 6, "args": [ { "name": "method", @@ -115,14 +113,46 @@ } } }, + "19172e0dbeb5": { + "name": "settings.get#1", + "ordinal": 6, + "args": [ + { + "name": "method", + "value": "settings.get" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "rejected", + "startedAt": 0, + "settledAt": 0, + "error": { + "category": "Error", + "message": "settings.get#1 rejected", + "isRpcDeliveryUnknown": true + } + } + }, "19b2979d7fc2": { "name": "repo.list#1", "ordinal": 2, "json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" }, - "1a160fc2d137": { + "1e027d0127f3": { "name": "host.platform#1", - "ordinal": 9, + "ordinal": 7, "args": [ { "name": "method", @@ -142,26 +172,166 @@ } ], "settlement": { - "status": "pending", - "startedAt": 0 + "status": "rejected", + "startedAt": 0, + "settledAt": 0, + "error": { + "category": "Error", + "message": "Connection closed", + "isRpcDeliveryUnknown": true + } } }, - "1ff13ee8621c": { - "name": "repoIconsByName", - "ordinal": 4, - "value": [] + "1f909a505b54": { + "name": "hostPlatform", + "ordinal": 13, + "value": { + "$rpc": "null" + } }, - "2f1b95eac8f1": { - "name": "repoColorsByName", - "ordinal": 3, - "value": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ] - }, - "32bd1f0384a8": { + "271940c15814": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 6, + "args": [ + { + "name": "method", + "value": "settings.get" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "rejected", + "startedAt": 0, + "settledAt": 30000, + "error": { + "category": "Error", + "message": "Request timed out: settings.get", + "isRpcDeliveryUnknown": true + } + } + }, + "278a60f78954": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "rejected", + "startedAt": 0, + "settledAt": 0, + "error": { + "category": "LogicalClientCutoverError", + "message": "RPC interrupted by connection migration", + "isRpcDeliveryUnknown": true, + "cause": { + "category": "Error", + "message": "Connection closed", + "isRpcDeliveryUnknown": true + } + } + } + }, + "299d571abf13": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 5, + "args": [ + { + "name": "method", + "value": "ssh.listTargetSummaries" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-2", + "ok": true, + "result": { + "targets": [ + { + "id": "ssh-1", + "label": "SSH" + } + ] + } + } + } + }, + "335f5d729e86": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "rejected", + "startedAt": 0, + "settledAt": 0, + "error": { + "category": "Error", + "message": "host.platform#1 rejected", + "isRpcDeliveryUnknown": true + } + } + }, + "3af207882a60": { + "name": "settings.get#1", + "ordinal": 6, "args": [ { "name": "method", @@ -191,11 +361,6 @@ } } }, - "36a96ddbd396": { - "name": "host.platform#1", - "ordinal": 12, - "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" - }, "4526bff18b74": { "name": "repo.list#1", "ordinal": 1, @@ -243,271 +408,13 @@ } } }, - "53226cb82ec3": { - "name": "hostPlatform", - "ordinal": 15, - "value": "linux" - }, - "53dea128b181": { + "5248b37ec085": { "name": "host.platform#1", - "ordinal": 9, - "args": [ - { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "rejected", - "startedAt": 0, - "settledAt": 0, - "error": { - "category": "Error", - "message": "Connection closed", - "isRpcDeliveryUnknown": true - } - } - }, - "5b781918b903": { - "name": "host.platform#1", - "ordinal": 9, - "args": [ - { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "rejected", - "startedAt": 0, - "settledAt": 0, - "error": { - "category": "Error", - "message": "Connection lost", - "isRpcDeliveryUnknown": true - } - } - }, - "6063e534d9db": { - "name": "hostHealthById", - "ordinal": 13, - "value": [] - }, - "6134b73f18d0": { - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ], - "repoHostIdByRepoId": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "668a7e313975": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 10, - "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" - }, - "6b33c36aba17": { - "name": "settings.get#1", - "ordinal": 8, - "args": [ - { - "name": "method", - "value": "settings.get" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "rejected", - "startedAt": 0, - "settledAt": 0, - "error": { - "category": "LogicalClientCutoverError", - "message": "RPC interrupted by connection migration", - "isRpcDeliveryUnknown": true, - "cause": { - "category": "Error", - "message": "Connection closed", - "isRpcDeliveryUnknown": true - } - } - } - }, - "7e772c1c111d": { - "name": "repoIdsByName", - "ordinal": 5, - "value": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "823fd36df8ac": { - "name": "settings.get#1", - "ordinal": 8, - "args": [ - { - "name": "method", - "value": "settings.get" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "rejected", - "startedAt": 0, - "settledAt": 30000, - "error": { - "category": "Error", - "message": "Request timed out: settings.get", - "isRpcDeliveryUnknown": true - } - } - }, - "8ef3b348c179": { - "name": "repoHostIdByRepoId", - "ordinal": 6, - "value": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ] - }, - "9270aeb7d9c6": { - "status": "pending", - "startedAt": 0 - }, - "96e0c54d86b0": { - "name": "settings.get#1", - "ordinal": 8, - "args": [ - { - "name": "method", - "value": "settings.get" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "pending", - "startedAt": 0 - } - }, - "9ae54f671699": { - "name": "host.platform#1", - "ordinal": 9, - "args": [ - { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "rejected", - "startedAt": 0, - "settledAt": 0, - "error": { - "category": "Error", - "message": "host.platform#1 rejected", - "isRpcDeliveryUnknown": true - } - } - }, - "9d20695fdb34": { - "name": "hostPlatform", - "ordinal": 15, - "value": { - "$rpc": "null" - } - }, - "b428bce85ee7": { - "name": "settings.get#1", - "ordinal": 11, - "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" - }, - "bea13a4a2e87": { - "name": "hostLabelById", - "ordinal": 14, - "value": [["ssh:ssh-1", "SSH"]] - }, - "c752e67787f9": { - "name": "ssh.listTargetSummaries#1", "ordinal": 7, "args": [ { "name": "method", - "value": "ssh.listTargetSummaries" + "value": "host.platform" }, { "name": "params", @@ -527,30 +434,17 @@ "startedAt": 0, "settledAt": 0, "value": { - "id": "frame-2", + "id": "frame-4", "ok": true, "result": { - "targets": [ - { - "id": "ssh-1", - "label": "SSH" - } - ] + "platform": "linux" } } } }, - "c7f150c7a054": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 30000, - "value": { - "$rpc": "undefined" - } - }, - "df9bd2cc2b3c": { + "62384d41a9c4": { "name": "host.platform#1", - "ordinal": 9, + "ordinal": 7, "args": [ { "name": "method", @@ -580,9 +474,101 @@ } } }, - "e03d2292c377": { + "661b4721aec6": { + "name": "desktopRepos", + "ordinal": 3, + "value": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ] + }, + "68916f125c3c": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ], + "repoHostIdByRepoId": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] + }, + "6d43117882f3": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ], + "hostHealthById": [], + "hostLabelById": [["ssh:ssh-1", "SSH"]], + "hostPlatform": "linux", + "repoHostIdByRepoId": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] + }, + "7c508088602c": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "pending", + "startedAt": 0 + } + }, + "8b618db22116": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 9, + "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" + }, + "902c0b2ed3ce": { + "name": "settings.get#1", + "ordinal": 6, "args": [ { "name": "method", @@ -602,34 +588,78 @@ } ], "settlement": { - "status": "rejected", - "startedAt": 0, - "settledAt": 0, - "error": { - "category": "Error", - "message": "settings.get#1 rejected", - "isRpcDeliveryUnknown": true - } + "status": "pending", + "startedAt": 0 } }, - "e841f87c65aa": { + "91e7f4ef9a77": { + "name": "hostPlatform", + "ordinal": 13, + "value": "linux" + }, + "9270aeb7d9c6": { + "status": "pending", + "startedAt": 0 + }, + "c1246f5792ee": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 8, + "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" + }, + "c1d6fc758acc": { + "name": "host.platform#1", + "ordinal": 10, + "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" + }, + "c7f150c7a054": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 30000, + "value": { + "$rpc": "undefined" + } + }, + "d88cba4e044f": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ], "hostHealthById": [], "hostLabelById": [["ssh:ssh-1", "SSH"]], "hostPlatform": { "$rpc": "null" }, - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ], "repoHostIdByRepoId": [ ["repo-1", "local"], ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] + ] + }, + "d9c464d0dc78": { + "name": "hostLabelById", + "ordinal": 12, + "value": [["ssh:ssh-1", "SSH"]] + }, + "e2881f748a46": { + "name": "hostHealthById", + "ordinal": 11, + "value": [] + }, + "e49da3e1abca": { + "name": "repoHostIdByRepoId", + "ordinal": 4, + "value": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] ] }, "eb79a9b3682a": { @@ -639,24 +669,6 @@ "value": { "$rpc": "undefined" } - }, - "f4667dda0187": { - "hostHealthById": [], - "hostLabelById": [["ssh:ssh-1", "SSH"]], - "hostPlatform": "linux", - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ], - "repoHostIdByRepoId": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] } }, "recording": { @@ -665,248 +677,232 @@ { "id": "settings-repo-metadata-fulfilled.forward:sibling-pending", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.forward:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.reverse:sibling-pending", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "96e0c54d86b0", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "902c0b2ed3ce", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.reverse:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, { "id": "settings-repo-metadata-fulfilled.both-reject-forward:sibling-pending", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "e03d2292c377", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "19172e0dbeb5", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.both-reject-forward:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "e03d2292c377", "9ae54f671699"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "19172e0dbeb5", "335f5d729e86"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "e841f87c65aa", + "state": "d88cba4e044f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } }, { "id": "settings-repo-metadata-fulfilled.both-reject-reverse:sibling-pending", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "96e0c54d86b0", "9ae54f671699"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "902c0b2ed3ce", "335f5d729e86"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.both-reject-reverse:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "e03d2292c377", "9ae54f671699"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "19172e0dbeb5", "335f5d729e86"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "e841f87c65aa", + "state": "d88cba4e044f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } }, { "id": "settings-repo-metadata-fulfilled.reject-peer-pending:sibling-pending", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "e03d2292c377", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "19172e0dbeb5", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.reject-peer-pending:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "e03d2292c377", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "19172e0dbeb5", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settings-repo-metadata-fulfilled.reject-peer-pending:cleanup", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "e03d2292c377", "53dea128b181"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "19172e0dbeb5", "1e027d0127f3"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "6134b73f18d0", + "state": "68916f125c3c", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } }, { "id": "settings-repo-metadata-fulfilled.timeout:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "823fd36df8ac", "df9bd2cc2b3c"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "271940c15814", "62384d41a9c4"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "c7f150c7a054" }, - "state": "e841f87c65aa", + "state": "d88cba4e044f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } }, { "id": "settings-repo-metadata-fulfilled.disconnect:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "32bd1f0384a8", "5b781918b903"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "3af207882a60", "0788f40c85d2"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a", "disconnect": "eb79a9b3682a" }, - "state": "e841f87c65aa", + "state": "d88cba4e044f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } }, { "id": "settings-repo-metadata-fulfilled.client-cutover:settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "6b33c36aba17", "031c2df78b67"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "0603f3cff57a", "278a60f78954"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a", "cutover": "eb79a9b3682a" }, - "state": "e841f87c65aa", + "state": "d88cba4e044f", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "9d20695fdb34" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "1f909a505b54" ] } } diff --git a/mobile/rpc-foundation/goldens/settings-repo-cache-expiry.json b/mobile/rpc-foundation/goldens/settings-repo-cache-expiry.json index 57c042b30b8..f7445955a8e 100644 --- a/mobile/rpc-foundation/goldens/settings-repo-cache-expiry.json +++ b/mobile/rpc-foundation/goldens/settings-repo-cache-expiry.json @@ -4,43 +4,9 @@ "family": "settings.repo-metadata", "namedDeltas": [], "values": { - "03c9a2ba60b9": { - "name": "host.platform#1", - "ordinal": 9, - "args": [ - { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-4", - "ok": true, - "result": { - "platform": "linux" - } - } - } - }, - "0c4296f215a4": { + "18762ff064ed": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 6, "args": [ { "name": "method", @@ -83,13 +49,13 @@ "ordinal": 2, "json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" }, - "1a160fc2d137": { - "name": "host.platform#1", - "ordinal": 9, + "299d571abf13": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 5, "args": [ { "name": "method", - "value": "host.platform" + "value": "ssh.listTargetSummaries" }, { "name": "params", @@ -105,33 +71,28 @@ } ], "settlement": { - "status": "pending", - "startedAt": 0 + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-2", + "ok": true, + "result": { + "targets": [ + { + "id": "ssh-1", + "label": "SSH" + } + ] + } + } } }, - "1ff13ee8621c": { - "name": "repoIconsByName", - "ordinal": 4, - "value": [] - }, - "2053629911e7": { + "35f30397fcb2": { "name": "repo.list#2", - "ordinal": 17, + "ordinal": 15, "json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" }, - "2f1b95eac8f1": { - "name": "repoColorsByName", - "ordinal": 3, - "value": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ] - }, - "36a96ddbd396": { - "name": "host.platform#1", - "ordinal": 12, - "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" - }, "4526bff18b74": { "name": "repo.list#1", "ordinal": 1, @@ -179,59 +140,135 @@ } } }, - "53226cb82ec3": { - "name": "hostPlatform", - "ordinal": 15, - "value": "linux" + "5248b37ec085": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-4", + "ok": true, + "result": { + "platform": "linux" + } + } + } }, - "6063e534d9db": { - "name": "hostHealthById", - "ordinal": 13, - "value": [] + "661b4721aec6": { + "name": "desktopRepos", + "ordinal": 3, + "value": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ] }, - "6134b73f18d0": { - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] + "68916f125c3c": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } ], "repoHostIdByRepoId": [ ["repo-1", "local"], ["repo-2", "ssh:ssh-1"] + ] + }, + "6d43117882f3": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "668a7e313975": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 10, - "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" - }, - "7e772c1c111d": { - "name": "repoIdsByName", - "ordinal": 5, - "value": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "8ef3b348c179": { - "name": "repoHostIdByRepoId", - "ordinal": 6, - "value": [ + "hostHealthById": [], + "hostLabelById": [["ssh:ssh-1", "SSH"]], + "hostPlatform": "linux", + "repoHostIdByRepoId": [ ["repo-1", "local"], ["repo-2", "ssh:ssh-1"] ] }, - "9270aeb7d9c6": { - "status": "pending", - "startedAt": 0 + "7c508088602c": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "pending", + "startedAt": 0 + } }, - "96e0c54d86b0": { + "8b618db22116": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 9, + "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" + }, + "902c0b2ed3ce": { + "name": "settings.get#1", + "ordinal": 6, "args": [ { "name": "method", @@ -255,23 +292,9 @@ "startedAt": 0 } }, - "b428bce85ee7": { - "name": "settings.get#1", - "ordinal": 11, - "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" - }, - "ba8a19e3e2b1": { - "status": "pending", - "startedAt": 60000 - }, - "bea13a4a2e87": { - "name": "hostLabelById", - "ordinal": 14, - "value": [["ssh:ssh-1", "SSH"]] - }, - "c402c05b8b96": { + "9157a9ad3cbd": { "name": "repo.list#2", - "ordinal": 16, + "ordinal": 14, "args": [ { "name": "method", @@ -295,44 +318,46 @@ "startedAt": 60000 } }, - "c752e67787f9": { + "91e7f4ef9a77": { + "name": "hostPlatform", + "ordinal": 13, + "value": "linux" + }, + "9270aeb7d9c6": { + "status": "pending", + "startedAt": 0 + }, + "ba8a19e3e2b1": { + "status": "pending", + "startedAt": 60000 + }, + "c1246f5792ee": { "name": "ssh.listTargetSummaries#1", - "ordinal": 7, - "args": [ - { - "name": "method", - "value": "ssh.listTargetSummaries" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-2", - "ok": true, - "result": { - "targets": [ - { - "id": "ssh-1", - "label": "SSH" - } - ] - } - } - } + "ordinal": 8, + "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" + }, + "c1d6fc758acc": { + "name": "host.platform#1", + "ordinal": 10, + "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" + }, + "d9c464d0dc78": { + "name": "hostLabelById", + "ordinal": 12, + "value": [["ssh:ssh-1", "SSH"]] + }, + "e2881f748a46": { + "name": "hostHealthById", + "ordinal": 11, + "value": [] + }, + "e49da3e1abca": { + "name": "repoHostIdByRepoId", + "ordinal": 4, + "value": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] }, "eb79a9b3682a": { "status": "fulfilled", @@ -349,24 +374,6 @@ "value": { "$rpc": "undefined" } - }, - "f4667dda0187": { - "hostHealthById": [], - "hostLabelById": [["ssh:ssh-1", "SSH"]], - "hostPlatform": "linux", - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ], - "repoHostIdByRepoId": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] } }, "recording": { @@ -375,79 +382,73 @@ { "id": "settings-pending", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, { "id": "cache-hit", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a", "cached-query": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, { "id": "cache-warm", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a", "cached-query": "eb79a9b3682a", "warm-query": "f43afee17848" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, @@ -456,17 +457,17 @@ "observation": { "sender": [ "4526bff18b74", - "c752e67787f9", - "0c4296f215a4", - "03c9a2ba60b9", - "c402c05b8b96" + "299d571abf13", + "18762ff064ed", + "5248b37ec085", + "9157a9ad3cbd" ], "payloads": [ "19b2979d7fc2", - "668a7e313975", - "b428bce85ee7", - "36a96ddbd396", - "2053629911e7" + "c1246f5792ee", + "8b618db22116", + "c1d6fc758acc", + "35f30397fcb2" ], "settlements": { "mount": "eb79a9b3682a", @@ -475,15 +476,13 @@ "warm-query": "f43afee17848", "expired-query": "ba8a19e3e2b1" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } } diff --git a/mobile/rpc-foundation/goldens/settings-repo-metadata-fulfilled.json b/mobile/rpc-foundation/goldens/settings-repo-metadata-fulfilled.json index e3be4ab006b..bf3e74423bf 100644 --- a/mobile/rpc-foundation/goldens/settings-repo-metadata-fulfilled.json +++ b/mobile/rpc-foundation/goldens/settings-repo-metadata-fulfilled.json @@ -4,43 +4,9 @@ "family": "settings.repo-metadata", "namedDeltas": [], "values": { - "03c9a2ba60b9": { - "name": "host.platform#1", - "ordinal": 9, - "args": [ - { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-4", - "ok": true, - "result": { - "platform": "linux" - } - } - } - }, - "0c4296f215a4": { + "18762ff064ed": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 6, "args": [ { "name": "method", @@ -83,13 +49,13 @@ "ordinal": 2, "json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" }, - "1a160fc2d137": { - "name": "host.platform#1", - "ordinal": 9, + "299d571abf13": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 5, "args": [ { "name": "method", - "value": "host.platform" + "value": "ssh.listTargetSummaries" }, { "name": "params", @@ -105,28 +71,23 @@ } ], "settlement": { - "status": "pending", - "startedAt": 0 + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-2", + "ok": true, + "result": { + "targets": [ + { + "id": "ssh-1", + "label": "SSH" + } + ] + } + } } }, - "1ff13ee8621c": { - "name": "repoIconsByName", - "ordinal": 4, - "value": [] - }, - "2f1b95eac8f1": { - "name": "repoColorsByName", - "ordinal": 3, - "value": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ] - }, - "36a96ddbd396": { - "name": "host.platform#1", - "ordinal": 12, - "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" - }, "4526bff18b74": { "name": "repo.list#1", "ordinal": 1, @@ -174,59 +135,135 @@ } } }, - "53226cb82ec3": { - "name": "hostPlatform", - "ordinal": 15, - "value": "linux" + "5248b37ec085": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-4", + "ok": true, + "result": { + "platform": "linux" + } + } + } }, - "6063e534d9db": { - "name": "hostHealthById", - "ordinal": 13, - "value": [] + "661b4721aec6": { + "name": "desktopRepos", + "ordinal": 3, + "value": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ] }, - "6134b73f18d0": { - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] + "68916f125c3c": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } ], "repoHostIdByRepoId": [ ["repo-1", "local"], ["repo-2", "ssh:ssh-1"] + ] + }, + "6d43117882f3": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "668a7e313975": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 10, - "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" - }, - "7e772c1c111d": { - "name": "repoIdsByName", - "ordinal": 5, - "value": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "8ef3b348c179": { - "name": "repoHostIdByRepoId", - "ordinal": 6, - "value": [ + "hostHealthById": [], + "hostLabelById": [["ssh:ssh-1", "SSH"]], + "hostPlatform": "linux", + "repoHostIdByRepoId": [ ["repo-1", "local"], ["repo-2", "ssh:ssh-1"] ] }, - "9270aeb7d9c6": { - "status": "pending", - "startedAt": 0 + "7c508088602c": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "pending", + "startedAt": 0 + } }, - "96e0c54d86b0": { + "8b618db22116": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 9, + "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" + }, + "902c0b2ed3ce": { + "name": "settings.get#1", + "ordinal": 6, "args": [ { "name": "method", @@ -250,54 +287,42 @@ "startedAt": 0 } }, - "b428bce85ee7": { - "name": "settings.get#1", - "ordinal": 11, - "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" + "91e7f4ef9a77": { + "name": "hostPlatform", + "ordinal": 13, + "value": "linux" }, - "bea13a4a2e87": { + "9270aeb7d9c6": { + "status": "pending", + "startedAt": 0 + }, + "c1246f5792ee": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 8, + "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" + }, + "c1d6fc758acc": { + "name": "host.platform#1", + "ordinal": 10, + "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" + }, + "d9c464d0dc78": { "name": "hostLabelById", - "ordinal": 14, + "ordinal": 12, "value": [["ssh:ssh-1", "SSH"]] }, - "c752e67787f9": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 7, - "args": [ - { - "name": "method", - "value": "ssh.listTargetSummaries" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-2", - "ok": true, - "result": { - "targets": [ - { - "id": "ssh-1", - "label": "SSH" - } - ] - } - } - } + "e2881f748a46": { + "name": "hostHealthById", + "ordinal": 11, + "value": [] + }, + "e49da3e1abca": { + "name": "repoHostIdByRepoId", + "ordinal": 4, + "value": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] }, "eb79a9b3682a": { "status": "fulfilled", @@ -306,24 +331,6 @@ "value": { "$rpc": "undefined" } - }, - "f4667dda0187": { - "hostHealthById": [], - "hostLabelById": [["ssh:ssh-1", "SSH"]], - "hostPlatform": "linux", - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ], - "repoHostIdByRepoId": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] } }, "recording": { @@ -332,34 +339,32 @@ { "id": "settings-pending", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } } diff --git a/mobile/rpc-foundation/goldens/settings-repo-metadata-icons.json b/mobile/rpc-foundation/goldens/settings-repo-metadata-icons.json index dcd54acfef5..1fd076e0b98 100644 --- a/mobile/rpc-foundation/goldens/settings-repo-metadata-icons.json +++ b/mobile/rpc-foundation/goldens/settings-repo-metadata-icons.json @@ -4,27 +4,68 @@ "family": "settings.repo-metadata", "namedDeltas": [], "values": { + "0fd72e1aba93": { + "desktopRepos": [ + { + "badgeColor": "#123456", + "displayName": "Lucide", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1", + "repoIcon": { + "name": "Rocket", + "type": "lucide" + } + }, + { + "displayName": "Emoji", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2", + "repoIcon": { + "emoji": "🐳", + "type": "emoji" + } + }, + { + "displayName": "Avatar", + "executionHostId": "ssh:ssh-1", + "id": "repo-3", + "path": "repo-3", + "repoIcon": { + "label": "acme/orca", + "source": "github", + "src": "https://github.com/acme.png?size=64", + "type": "image" + } + } + ], + "hostHealthById": [], + "hostLabelById": [["ssh:ssh-1", "SSH"]], + "hostPlatform": "darwin", + "repoHostIdByRepoId": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"], + ["repo-3", "ssh:ssh-1"] + ] + }, "19b2979d7fc2": { "name": "repo.list#1", "ordinal": 2, "json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" }, - "19bf6d2469a6": { - "name": "repoColorsByName", - "ordinal": 3, - "value": [ - ["Lucide", "#123456"], - ["Emoji", "#f97316"], - ["Avatar", "#f97316"] - ] + "1c76208c9beb": { + "name": "hostPlatform", + "ordinal": 13, + "value": "darwin" }, - "1a160fc2d137": { - "name": "host.platform#1", - "ordinal": 9, + "299d571abf13": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 5, "args": [ { "name": "method", - "value": "host.platform" + "value": "ssh.listTargetSummaries" }, { "name": "params", @@ -40,70 +81,26 @@ } ], "settlement": { - "status": "pending", - "startedAt": 0 + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-2", + "ok": true, + "result": { + "targets": [ + { + "id": "ssh-1", + "label": "SSH" + } + ] + } + } } }, - "25b44bca9e45": { - "name": "hostPlatform", - "ordinal": 15, - "value": "darwin" - }, - "36a96ddbd396": { - "name": "host.platform#1", - "ordinal": 12, - "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" - }, - "6063e534d9db": { - "name": "hostHealthById", - "ordinal": 13, - "value": [] - }, - "6338d57f282f": { - "repoColorsByName": [ - ["Lucide", "#123456"], - ["Emoji", "#f97316"], - ["Avatar", "#f97316"] - ], - "repoHostIdByRepoId": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"], - ["repo-3", "ssh:ssh-1"] - ], - "repoIconsByName": [ - [ - "Lucide", - { - "name": "Rocket", - "type": "lucide" - } - ], - [ - "Emoji", - { - "emoji": "🐳", - "type": "emoji" - } - ], - [ - "Avatar", - { - "label": "acme/orca", - "source": "github", - "src": "https://github.com/acme.png?size=64", - "type": "image" - } - ] - ], - "repoIdsByName": [ - ["Lucide", "repo-1"], - ["Emoji", "repo-2"], - ["Avatar", "repo-3"] - ] - }, - "64f5522cc303": { + "466c0317a24a": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 6, "args": [ { "name": "method", @@ -139,36 +136,40 @@ } } }, - "668a7e313975": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 10, - "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" + "7c508088602c": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "pending", + "startedAt": 0 + } }, - "6bc5e5b7a259": { - "name": "repoHostIdByRepoId", - "ordinal": 6, - "value": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"], - ["repo-3", "ssh:ssh-1"] - ] - }, - "77cbfd1f087c": { - "name": "repoIdsByName", - "ordinal": 5, - "value": [ - ["Lucide", "repo-1"], - ["Emoji", "repo-2"], - ["Avatar", "repo-3"] - ] - }, - "9270aeb7d9c6": { - "status": "pending", - "startedAt": 0 - }, - "96e0c54d86b0": { + "8b618db22116": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 9, + "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" + }, + "902c0b2ed3ce": { + "name": "settings.get#1", + "ordinal": 6, "args": [ { "name": "method", @@ -192,6 +193,86 @@ "startedAt": 0 } }, + "910617d262f4": { + "desktopRepos": [ + { + "badgeColor": "#123456", + "displayName": "Lucide", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1", + "repoIcon": { + "name": "Rocket", + "type": "lucide" + } + }, + { + "displayName": "Emoji", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2", + "repoIcon": { + "emoji": "🐳", + "type": "emoji" + } + }, + { + "displayName": "Avatar", + "executionHostId": "ssh:ssh-1", + "id": "repo-3", + "path": "repo-3", + "repoIcon": { + "label": "acme/orca", + "source": "github", + "src": "https://github.com/acme.png?size=64", + "type": "image" + } + } + ], + "repoHostIdByRepoId": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"], + ["repo-3", "ssh:ssh-1"] + ] + }, + "9270aeb7d9c6": { + "status": "pending", + "startedAt": 0 + }, + "9bc91d02ce24": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-4", + "ok": true, + "result": { + "platform": "darwin" + } + } + } + }, "a9add325bb0a": { "name": "repo.list#1", "ordinal": 1, @@ -259,128 +340,34 @@ } } }, - "ae27a5b525fa": { - "name": "repoIconsByName", + "b4ad28ffe331": { + "name": "repoHostIdByRepoId", "ordinal": 4, "value": [ - [ - "Lucide", - { - "name": "Rocket", - "type": "lucide" - } - ], - [ - "Emoji", - { - "emoji": "🐳", - "type": "emoji" - } - ], - [ - "Avatar", - { - "label": "acme/orca", - "source": "github", - "src": "https://github.com/acme.png?size=64", - "type": "image" - } - ] - ] - }, - "b428bce85ee7": { - "name": "settings.get#1", - "ordinal": 11, - "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" - }, - "bea13a4a2e87": { - "name": "hostLabelById", - "ordinal": 14, - "value": [["ssh:ssh-1", "SSH"]] - }, - "c020f709d835": { - "hostHealthById": [], - "hostLabelById": [["ssh:ssh-1", "SSH"]], - "hostPlatform": "darwin", - "repoColorsByName": [ - ["Lucide", "#123456"], - ["Emoji", "#f97316"], - ["Avatar", "#f97316"] - ], - "repoHostIdByRepoId": [ ["repo-1", "local"], ["repo-2", "ssh:ssh-1"], ["repo-3", "ssh:ssh-1"] - ], - "repoIconsByName": [ - [ - "Lucide", - { - "name": "Rocket", - "type": "lucide" - } - ], - [ - "Emoji", - { - "emoji": "🐳", - "type": "emoji" - } - ], - [ - "Avatar", - { - "label": "acme/orca", - "source": "github", - "src": "https://github.com/acme.png?size=64", - "type": "image" - } - ] - ], - "repoIdsByName": [ - ["Lucide", "repo-1"], - ["Emoji", "repo-2"], - ["Avatar", "repo-3"] ] }, - "c752e67787f9": { + "c1246f5792ee": { "name": "ssh.listTargetSummaries#1", - "ordinal": 7, - "args": [ - { - "name": "method", - "value": "ssh.listTargetSummaries" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-2", - "ok": true, - "result": { - "targets": [ - { - "id": "ssh-1", - "label": "SSH" - } - ] - } - } - } + "ordinal": 8, + "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" + }, + "c1d6fc758acc": { + "name": "host.platform#1", + "ordinal": 10, + "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" + }, + "d9c464d0dc78": { + "name": "hostLabelById", + "ordinal": 12, + "value": [["ssh:ssh-1", "SSH"]] + }, + "e2881f748a46": { + "name": "hostHealthById", + "ordinal": 11, + "value": [] }, "eb79a9b3682a": { "status": "fulfilled", @@ -390,39 +377,44 @@ "$rpc": "undefined" } }, - "ee8e16df5187": { - "name": "host.platform#1", - "ordinal": 9, - "args": [ + "f9217d79f53e": { + "name": "desktopRepos", + "ordinal": 3, + "value": [ { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" + "badgeColor": "#123456", + "displayName": "Lucide", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1", + "repoIcon": { + "name": "Rocket", + "type": "lucide" } }, { - "name": "options", - "value": { - "$rpc": "absent" + "displayName": "Emoji", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2", + "repoIcon": { + "emoji": "🐳", + "type": "emoji" + } + }, + { + "displayName": "Avatar", + "executionHostId": "ssh:ssh-1", + "id": "repo-3", + "path": "repo-3", + "repoIcon": { + "label": "acme/orca", + "source": "github", + "src": "https://github.com/acme.png?size=64", + "type": "image" } } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-4", - "ok": true, - "result": { - "platform": "darwin" - } - } - } + ] } }, "recording": { @@ -431,34 +423,32 @@ { "id": "settings-pending", "observation": { - "sender": ["a9add325bb0a", "c752e67787f9", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["a9add325bb0a", "299d571abf13", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6338d57f282f", - "effects": ["19bf6d2469a6", "ae27a5b525fa", "77cbfd1f087c", "6bc5e5b7a259"] + "state": "910617d262f4", + "effects": ["f9217d79f53e", "b4ad28ffe331"] } }, { "id": "settled", "observation": { - "sender": ["a9add325bb0a", "c752e67787f9", "64f5522cc303", "ee8e16df5187"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["a9add325bb0a", "299d571abf13", "466c0317a24a", "9bc91d02ce24"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "c020f709d835", + "state": "0fd72e1aba93", "effects": [ - "19bf6d2469a6", - "ae27a5b525fa", - "77cbfd1f087c", - "6bc5e5b7a259", - "6063e534d9db", - "bea13a4a2e87", - "25b44bca9e45" + "f9217d79f53e", + "b4ad28ffe331", + "e2881f748a46", + "d9c464d0dc78", + "1c76208c9beb" ] } } diff --git a/mobile/rpc-foundation/goldens/settings-repo-metadata-refuse-after-data.json b/mobile/rpc-foundation/goldens/settings-repo-metadata-refuse-after-data.json index 53182455b8d..0b7ad2b8038 100644 --- a/mobile/rpc-foundation/goldens/settings-repo-metadata-refuse-after-data.json +++ b/mobile/rpc-foundation/goldens/settings-repo-metadata-refuse-after-data.json @@ -4,43 +4,14 @@ "family": "settings.repo-metadata", "namedDeltas": [], "values": { - "03c9a2ba60b9": { - "name": "host.platform#1", - "ordinal": 9, - "args": [ - { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-4", - "ok": true, - "result": { - "platform": "linux" - } - } - } + "00d007a90b2c": { + "name": "hostPlatform", + "ordinal": 26, + "value": "linux" }, - "0c4296f215a4": { + "18762ff064ed": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 6, "args": [ { "name": "method", @@ -78,19 +49,14 @@ } } }, - "0ef3d910f42e": { - "name": "hostLabelById", - "ordinal": 29, - "value": [["ssh:ssh-1", "SSH"]] - }, "19b2979d7fc2": { "name": "repo.list#1", "ordinal": 2, "json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" }, - "1a160fc2d137": { - "name": "host.platform#1", - "ordinal": 9, + "1e4a3aa71af5": { + "name": "host.platform#2", + "ordinal": 20, "args": [ { "name": "method", @@ -110,37 +76,79 @@ } ], "settlement": { - "status": "pending", - "startedAt": 0 + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-8", + "ok": true, + "result": { + "platform": "linux" + } + } } }, - "1ff13ee8621c": { - "name": "repoIconsByName", - "ordinal": 4, - "value": [] + "299d571abf13": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 5, + "args": [ + { + "name": "method", + "value": "ssh.listTargetSummaries" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-2", + "ok": true, + "result": { + "targets": [ + { + "id": "ssh-1", + "label": "SSH" + } + ] + } + } + } }, - "2053629911e7": { - "name": "repo.list#2", - "ordinal": 17, - "json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" - }, - "2f1b95eac8f1": { - "name": "repoColorsByName", - "ordinal": 3, + "306fcf2b8536": { + "name": "desktopRepos", + "ordinal": 16, "value": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } ] }, - "36a96ddbd396": { - "name": "host.platform#1", - "ordinal": 12, - "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" - }, - "376d3874239c": { - "name": "repoIconsByName", - "ordinal": 19, - "value": [] + "35f30397fcb2": { + "name": "repo.list#2", + "ordinal": 15, + "json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" }, "4526bff18b74": { "name": "repo.list#1", @@ -189,44 +197,9 @@ } } }, - "455855a85ab4": { - "name": "settings.get#2", - "ordinal": 23, - "args": [ - { - "name": "method", - "value": "settings.get" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "error": { - "code": "refused", - "message": "settings refused" - }, - "id": "frame-7", - "ok": false - } - } - }, - "4771e3dbbbcf": { - "name": "host.platform#2", - "ordinal": 24, + "5248b37ec085": { + "name": "host.platform#1", + "ordinal": 7, "args": [ { "name": "method", @@ -250,7 +223,7 @@ "startedAt": 0, "settledAt": 0, "value": { - "id": "frame-8", + "id": "frame-4", "ok": true, "result": { "platform": "linux" @@ -258,64 +231,106 @@ } } }, - "53226cb82ec3": { - "name": "hostPlatform", - "ordinal": 15, - "value": "linux" + "661b4721aec6": { + "name": "desktopRepos", + "ordinal": 3, + "value": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ] }, - "6063e534d9db": { - "name": "hostHealthById", - "ordinal": 13, - "value": [] - }, - "6134b73f18d0": { - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] + "68916f125c3c": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } ], "repoHostIdByRepoId": [ ["repo-1", "local"], ["repo-2", "ssh:ssh-1"] + ] + }, + "69f575e15e15": { + "name": "settings.get#2", + "ordinal": 22, + "json": "{\"id\":\"frame-7\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" + }, + "6d43117882f3": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "668a7e313975": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 10, - "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" - }, - "7e772c1c111d": { - "name": "repoIdsByName", - "ordinal": 5, - "value": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "8c5c812580ad": { - "name": "host.platform#2", - "ordinal": 27, - "json": "{\"id\":\"frame-8\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" - }, - "8ef3b348c179": { - "name": "repoHostIdByRepoId", - "ordinal": 6, - "value": [ + "hostHealthById": [], + "hostLabelById": [["ssh:ssh-1", "SSH"]], + "hostPlatform": "linux", + "repoHostIdByRepoId": [ ["repo-1", "local"], ["repo-2", "ssh:ssh-1"] ] }, - "9270aeb7d9c6": { - "status": "pending", - "startedAt": 0 + "7c508088602c": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "pending", + "startedAt": 0 + } }, - "96e0c54d86b0": { + "8b618db22116": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 9, + "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" + }, + "902c0b2ed3ce": { + "name": "settings.get#1", + "ordinal": 6, "args": [ { "name": "method", @@ -339,78 +354,33 @@ "startedAt": 0 } }, - "a67416f78b93": { - "name": "hostHealthById", - "ordinal": 28, - "value": [] - }, - "b428bce85ee7": { - "name": "settings.get#1", - "ordinal": 11, - "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" - }, - "bea13a4a2e87": { - "name": "hostLabelById", - "ordinal": 14, - "value": [["ssh:ssh-1", "SSH"]] - }, - "c497d2d846c9": { - "name": "ssh.listTargetSummaries#2", - "ordinal": 25, - "json": "{\"id\":\"frame-6\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" - }, - "c752e67787f9": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 7, - "args": [ - { - "name": "method", - "value": "ssh.listTargetSummaries" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-2", - "ok": true, - "result": { - "targets": [ - { - "id": "ssh-1", - "label": "SSH" - } - ] - } - } - } - }, - "ceef481be25c": { + "91e7f4ef9a77": { "name": "hostPlatform", - "ordinal": 30, + "ordinal": 13, "value": "linux" }, - "d51254d2537f": { - "name": "settings.get#2", - "ordinal": 26, - "json": "{\"id\":\"frame-7\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" + "9270aeb7d9c6": { + "status": "pending", + "startedAt": 0 }, - "d569b5b2500f": { + "9b915e5b8831": { + "name": "host.platform#2", + "ordinal": 23, + "json": "{\"id\":\"frame-8\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" + }, + "a465a07eb4b0": { + "name": "hostLabelById", + "ordinal": 25, + "value": [["ssh:ssh-1", "SSH"]] + }, + "af3366c70550": { "name": "ssh.listTargetSummaries#2", - "ordinal": 22, + "ordinal": 21, + "json": "{\"id\":\"frame-6\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" + }, + "bc879379ce13": { + "name": "ssh.listTargetSummaries#2", + "ordinal": 18, "args": [ { "name": "method", @@ -447,17 +417,63 @@ } } }, - "da7cf9752395": { - "name": "repoColorsByName", - "ordinal": 18, + "bd0d18cd2160": { + "name": "repoHostIdByRepoId", + "ordinal": 17, "value": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] ] }, - "df78d2202264": { + "c1246f5792ee": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 8, + "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" + }, + "c1d6fc758acc": { + "name": "host.platform#1", + "ordinal": 10, + "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" + }, + "d9c464d0dc78": { + "name": "hostLabelById", + "ordinal": 12, + "value": [["ssh:ssh-1", "SSH"]] + }, + "df3bf9bbb7bf": { "name": "repo.list#2", - "ordinal": 16, + "ordinal": 14, + "args": [ + { + "name": "method", + "value": "repo.list" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "pending", + "startedAt": 0 + } + }, + "e2881f748a46": { + "name": "hostHealthById", + "ordinal": 11, + "value": [] + }, + "e3e3d558d06c": { + "name": "repo.list#2", + "ordinal": 14, "args": [ { "name": "method", @@ -502,55 +518,26 @@ } } }, - "ea2ced0895f3": { + "e49da3e1abca": { "name": "repoHostIdByRepoId", - "ordinal": 21, + "ordinal": 4, "value": [ ["repo-1", "local"], ["repo-2", "ssh:ssh-1"] ] }, - "eb79a9b3682a": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "$rpc": "undefined" - } + "e7d590a1a17e": { + "name": "hostHealthById", + "ordinal": 24, + "value": [] }, - "eb885d2ceee5": { - "name": "repoIdsByName", - "ordinal": 20, - "value": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "f4667dda0187": { - "hostHealthById": [], - "hostLabelById": [["ssh:ssh-1", "SSH"]], - "hostPlatform": "linux", - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ], - "repoHostIdByRepoId": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "fa585aceeec2": { - "name": "repo.list#2", - "ordinal": 16, + "eab4940c8ca3": { + "name": "settings.get#2", + "ordinal": 19, "args": [ { "name": "method", - "value": "repo.list" + "value": "settings.get" }, { "name": "params", @@ -566,8 +553,25 @@ } ], "settlement": { - "status": "pending", - "startedAt": 0 + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "error": { + "code": "refused", + "message": "settings refused" + }, + "id": "frame-7", + "ok": false + } + } + }, + "eb79a9b3682a": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "$rpc": "undefined" } } }, @@ -577,55 +581,51 @@ { "id": "settings-pending", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, { "id": "data-present", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "0c4296f215a4", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "18762ff064ed", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, @@ -634,32 +634,30 @@ "observation": { "sender": [ "4526bff18b74", - "c752e67787f9", - "0c4296f215a4", - "03c9a2ba60b9", - "fa585aceeec2" + "299d571abf13", + "18762ff064ed", + "5248b37ec085", + "df3bf9bbb7bf" ], "payloads": [ "19b2979d7fc2", - "668a7e313975", - "b428bce85ee7", - "36a96ddbd396", - "2053629911e7" + "c1246f5792ee", + "8b618db22116", + "c1d6fc758acc", + "35f30397fcb2" ], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a", "reload": "9270aeb7d9c6" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } }, @@ -668,45 +666,41 @@ "observation": { "sender": [ "4526bff18b74", - "c752e67787f9", - "0c4296f215a4", - "03c9a2ba60b9", - "df78d2202264", - "d569b5b2500f", - "455855a85ab4", - "4771e3dbbbcf" + "299d571abf13", + "18762ff064ed", + "5248b37ec085", + "e3e3d558d06c", + "bc879379ce13", + "eab4940c8ca3", + "1e4a3aa71af5" ], "payloads": [ "19b2979d7fc2", - "668a7e313975", - "b428bce85ee7", - "36a96ddbd396", - "2053629911e7", - "c497d2d846c9", - "d51254d2537f", - "8c5c812580ad" + "c1246f5792ee", + "8b618db22116", + "c1d6fc758acc", + "35f30397fcb2", + "af3366c70550", + "69f575e15e15", + "9b915e5b8831" ], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a", "reload": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3", - "da7cf9752395", - "376d3874239c", - "eb885d2ceee5", - "ea2ced0895f3", - "a67416f78b93", - "0ef3d910f42e", - "ceef481be25c" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77", + "306fcf2b8536", + "bd0d18cd2160", + "e7d590a1a17e", + "a465a07eb4b0", + "00d007a90b2c" ] } } diff --git a/mobile/rpc-foundation/goldens/settings-repo-metadata-refused.json b/mobile/rpc-foundation/goldens/settings-repo-metadata-refused.json index 17e304bc7bc..7c884f9d9e4 100644 --- a/mobile/rpc-foundation/goldens/settings-repo-metadata-refused.json +++ b/mobile/rpc-foundation/goldens/settings-repo-metadata-refused.json @@ -4,83 +4,18 @@ "family": "settings.repo-metadata", "namedDeltas": [], "values": { - "03c9a2ba60b9": { - "name": "host.platform#1", - "ordinal": 9, - "args": [ - { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-4", - "ok": true, - "result": { - "platform": "linux" - } - } - } - }, "19b2979d7fc2": { "name": "repo.list#1", "ordinal": 2, "json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" }, - "1a160fc2d137": { - "name": "host.platform#1", - "ordinal": 9, + "299d571abf13": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 5, "args": [ { "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "pending", - "startedAt": 0 - } - }, - "1ff13ee8621c": { - "name": "repoIconsByName", - "ordinal": 4, - "value": [] - }, - "2e2ca6f7dc3f": { - "name": "settings.get#1", - "ordinal": 8, - "args": [ - { - "name": "method", - "value": "settings.get" + "value": "ssh.listTargetSummaries" }, { "name": "params", @@ -100,28 +35,19 @@ "startedAt": 0, "settledAt": 0, "value": { - "error": { - "code": "refused", - "message": "settings refused" - }, - "id": "frame-3", - "ok": false + "id": "frame-2", + "ok": true, + "result": { + "targets": [ + { + "id": "ssh-1", + "label": "SSH" + } + ] + } } } }, - "2f1b95eac8f1": { - "name": "repoColorsByName", - "ordinal": 3, - "value": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ] - }, - "36a96ddbd396": { - "name": "host.platform#1", - "ordinal": 12, - "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" - }, "4526bff18b74": { "name": "repo.list#1", "ordinal": 1, @@ -169,59 +95,170 @@ } } }, - "53226cb82ec3": { - "name": "hostPlatform", - "ordinal": 15, - "value": "linux" + "5248b37ec085": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-4", + "ok": true, + "result": { + "platform": "linux" + } + } + } }, - "6063e534d9db": { - "name": "hostHealthById", - "ordinal": 13, - "value": [] + "61b27dbdf3a7": { + "name": "settings.get#1", + "ordinal": 6, + "args": [ + { + "name": "method", + "value": "settings.get" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "error": { + "code": "refused", + "message": "settings refused" + }, + "id": "frame-3", + "ok": false + } + } }, - "6134b73f18d0": { - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] + "661b4721aec6": { + "name": "desktopRepos", + "ordinal": 3, + "value": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ] + }, + "68916f125c3c": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } ], "repoHostIdByRepoId": [ ["repo-1", "local"], ["repo-2", "ssh:ssh-1"] + ] + }, + "6d43117882f3": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "668a7e313975": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 10, - "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" - }, - "7e772c1c111d": { - "name": "repoIdsByName", - "ordinal": 5, - "value": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "8ef3b348c179": { - "name": "repoHostIdByRepoId", - "ordinal": 6, - "value": [ + "hostHealthById": [], + "hostLabelById": [["ssh:ssh-1", "SSH"]], + "hostPlatform": "linux", + "repoHostIdByRepoId": [ ["repo-1", "local"], ["repo-2", "ssh:ssh-1"] ] }, - "9270aeb7d9c6": { - "status": "pending", - "startedAt": 0 + "7c508088602c": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "pending", + "startedAt": 0 + } }, - "96e0c54d86b0": { + "8b618db22116": { "name": "settings.get#1", - "ordinal": 8, + "ordinal": 9, + "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" + }, + "902c0b2ed3ce": { + "name": "settings.get#1", + "ordinal": 6, "args": [ { "name": "method", @@ -245,54 +282,42 @@ "startedAt": 0 } }, - "b428bce85ee7": { - "name": "settings.get#1", - "ordinal": 11, - "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" + "91e7f4ef9a77": { + "name": "hostPlatform", + "ordinal": 13, + "value": "linux" }, - "bea13a4a2e87": { + "9270aeb7d9c6": { + "status": "pending", + "startedAt": 0 + }, + "c1246f5792ee": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 8, + "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" + }, + "c1d6fc758acc": { + "name": "host.platform#1", + "ordinal": 10, + "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" + }, + "d9c464d0dc78": { "name": "hostLabelById", - "ordinal": 14, + "ordinal": 12, "value": [["ssh:ssh-1", "SSH"]] }, - "c752e67787f9": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 7, - "args": [ - { - "name": "method", - "value": "ssh.listTargetSummaries" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-2", - "ok": true, - "result": { - "targets": [ - { - "id": "ssh-1", - "label": "SSH" - } - ] - } - } - } + "e2881f748a46": { + "name": "hostHealthById", + "ordinal": 11, + "value": [] + }, + "e49da3e1abca": { + "name": "repoHostIdByRepoId", + "ordinal": 4, + "value": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] }, "eb79a9b3682a": { "status": "fulfilled", @@ -301,24 +326,6 @@ "value": { "$rpc": "undefined" } - }, - "f4667dda0187": { - "hostHealthById": [], - "hostLabelById": [["ssh:ssh-1", "SSH"]], - "hostPlatform": "linux", - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ], - "repoHostIdByRepoId": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] } }, "recording": { @@ -327,34 +334,32 @@ { "id": "settings-pending", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "2e2ca6f7dc3f", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "61b27dbdf3a7", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } } diff --git a/mobile/rpc-foundation/goldens/settings-repo-metadata-single-host.json b/mobile/rpc-foundation/goldens/settings-repo-metadata-single-host.json index 277522dfed5..a05b52f8cc6 100644 --- a/mobile/rpc-foundation/goldens/settings-repo-metadata-single-host.json +++ b/mobile/rpc-foundation/goldens/settings-repo-metadata-single-host.json @@ -9,9 +9,9 @@ "ordinal": 2, "json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" }, - "1a160fc2d137": { + "1e027d0127f3": { "name": "host.platform#1", - "ordinal": 9, + "ordinal": 7, "args": [ { "name": "method", @@ -30,35 +30,118 @@ } } ], + "settlement": { + "status": "rejected", + "startedAt": 0, + "settledAt": 0, + "error": { + "category": "Error", + "message": "Connection closed", + "isRpcDeliveryUnknown": true + } + } + }, + "1f909a505b54": { + "name": "hostPlatform", + "ordinal": 13, + "value": { + "$rpc": "null" + } + }, + "2207323d0bd5": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 5, + "args": [ + { + "name": "method", + "value": "ssh.listTargetSummaries" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "rejected", + "startedAt": 0, + "settledAt": 0, + "error": { + "category": "Error", + "message": "Connection closed", + "isRpcDeliveryUnknown": true + } + } + }, + "302f82cb9a68": { + "name": "settings.get#1", + "ordinal": 6, + "args": [ + { + "name": "method", + "value": "settings.get" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "rejected", + "startedAt": 0, + "settledAt": 0, + "error": { + "category": "Error", + "message": "Connection closed", + "isRpcDeliveryUnknown": true + } + } + }, + "35960f0ff1b9": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 5, + "args": [ + { + "name": "method", + "value": "ssh.listTargetSummaries" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], "settlement": { "status": "pending", "startedAt": 0 } }, - "1ff13ee8621c": { - "name": "repoIconsByName", - "ordinal": 4, - "value": [] - }, - "2bd489a9fa29": { - "repoColorsByName": [ - ["Remote", "#f97316"], - ["Remote folder", "#ec4899"] - ], - "repoHostIdByRepoId": [ - ["repo-1", "ssh:ssh-1"], - ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Remote", "repo-1"], - ["Remote folder", "repo-2"] - ] - }, - "36a96ddbd396": { - "name": "host.platform#1", + "379c0eb6dc14": { + "name": "hostLabelById", "ordinal": 12, - "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" + "value": [] }, "4b6956e45bac": { "name": "repo.list#1", @@ -105,41 +188,27 @@ } } }, - "4bf1ed33de14": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 7, - "args": [ + "6e0383a0869e": { + "name": "desktopRepos", + "ordinal": 3, + "value": [ { - "name": "method", - "value": "ssh.listTargetSummaries" + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-1", + "path": "repo-1" }, { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } + "displayName": "Remote folder", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" } - ], - "settlement": { - "status": "rejected", - "startedAt": 0, - "settledAt": 0, - "error": { - "category": "Error", - "message": "Connection closed", - "isRpcDeliveryUnknown": true - } - } + ] }, - "53dea128b181": { + "7c508088602c": { "name": "host.platform#1", - "ordinal": 9, + "ordinal": 7, "args": [ { "name": "method", @@ -159,33 +228,22 @@ } ], "settlement": { - "status": "rejected", - "startedAt": 0, - "settledAt": 0, - "error": { - "category": "Error", - "message": "Connection closed", - "isRpcDeliveryUnknown": true - } + "status": "pending", + "startedAt": 0 } }, - "6063e534d9db": { - "name": "hostHealthById", - "ordinal": 13, - "value": [] + "8b618db22116": { + "name": "settings.get#1", + "ordinal": 9, + "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" }, - "668a7e313975": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 10, - "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" - }, - "6a227ad27189": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 7, + "902c0b2ed3ce": { + "name": "settings.get#1", + "ordinal": 6, "args": [ { "name": "method", - "value": "ssh.listTargetSummaries" + "value": "settings.get" }, { "name": "params", @@ -209,96 +267,48 @@ "status": "pending", "startedAt": 0 }, - "937107a86348": { - "name": "repoIdsByName", - "ordinal": 5, - "value": [ - ["Remote", "repo-1"], - ["Remote folder", "repo-2"] - ] - }, - "96e0c54d86b0": { - "name": "settings.get#1", + "c1246f5792ee": { + "name": "ssh.listTargetSummaries#1", "ordinal": 8, - "args": [ + "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" + }, + "c1d6fc758acc": { + "name": "host.platform#1", + "ordinal": 10, + "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" + }, + "d23c5c4cde9a": { + "desktopRepos": [ { - "name": "method", - "value": "settings.get" + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-1", + "path": "repo-1" }, { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } + "displayName": "Remote folder", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" } ], - "settlement": { - "status": "pending", - "startedAt": 0 - } - }, - "9d20695fdb34": { - "name": "hostPlatform", - "ordinal": 15, - "value": { - "$rpc": "null" - } - }, - "a086cfd03185": { - "name": "repoHostIdByRepoId", - "ordinal": 6, - "value": [ + "repoHostIdByRepoId": [ ["repo-1", "ssh:ssh-1"], ["repo-2", "ssh:ssh-1"] ] }, - "b428bce85ee7": { - "name": "settings.get#1", + "e2881f748a46": { + "name": "hostHealthById", "ordinal": 11, - "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" - }, - "daecdc0e521b": { - "name": "hostLabelById", - "ordinal": 14, "value": [] }, - "dd3998116e8e": { - "name": "settings.get#1", - "ordinal": 8, - "args": [ - { - "name": "method", - "value": "settings.get" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "rejected", - "startedAt": 0, - "settledAt": 0, - "error": { - "category": "Error", - "message": "Connection closed", - "isRpcDeliveryUnknown": true - } - } + "e7dd8e429107": { + "name": "repoHostIdByRepoId", + "ordinal": 4, + "value": [ + ["repo-1", "ssh:ssh-1"], + ["repo-2", "ssh:ssh-1"] + ] }, "eb79a9b3682a": { "status": "fulfilled", @@ -307,14 +317,6 @@ "value": { "$rpc": "undefined" } - }, - "fc0785cc0b08": { - "name": "repoColorsByName", - "ordinal": 3, - "value": [ - ["Remote", "#f97316"], - ["Remote folder", "#ec4899"] - ] } }, "recording": { @@ -323,34 +325,32 @@ { "id": "single-host-without-label-lookups", "observation": { - "sender": ["4b6956e45bac", "6a227ad27189", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4b6956e45bac", "35960f0ff1b9", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "2bd489a9fa29", - "effects": ["fc0785cc0b08", "1ff13ee8621c", "937107a86348", "a086cfd03185"] + "state": "d23c5c4cde9a", + "effects": ["6e0383a0869e", "e7dd8e429107"] } }, { "id": "cleanup", "observation": { - "sender": ["4b6956e45bac", "4bf1ed33de14", "dd3998116e8e", "53dea128b181"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4b6956e45bac", "2207323d0bd5", "302f82cb9a68", "1e027d0127f3"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "2bd489a9fa29", + "state": "d23c5c4cde9a", "effects": [ - "fc0785cc0b08", - "1ff13ee8621c", - "937107a86348", - "a086cfd03185", - "6063e534d9db", - "daecdc0e521b", - "9d20695fdb34" + "6e0383a0869e", + "e7dd8e429107", + "e2881f748a46", + "379c0eb6dc14", + "1f909a505b54" ] } } diff --git a/mobile/rpc-foundation/goldens/settings-repo-metadata-transport-error.json b/mobile/rpc-foundation/goldens/settings-repo-metadata-transport-error.json index 965d2f81320..479a4146191 100644 --- a/mobile/rpc-foundation/goldens/settings-repo-metadata-transport-error.json +++ b/mobile/rpc-foundation/goldens/settings-repo-metadata-transport-error.json @@ -4,13 +4,18 @@ "family": "settings.repo-metadata", "namedDeltas": [], "values": { - "03c9a2ba60b9": { - "name": "host.platform#1", - "ordinal": 9, + "19b2979d7fc2": { + "name": "repo.list#1", + "ordinal": 2, + "json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" + }, + "299d571abf13": { + "name": "ssh.listTargetSummaries#1", + "ordinal": 5, "args": [ { "name": "method", - "value": "host.platform" + "value": "ssh.listTargetSummaries" }, { "name": "params", @@ -30,63 +35,19 @@ "startedAt": 0, "settledAt": 0, "value": { - "id": "frame-4", + "id": "frame-2", "ok": true, "result": { - "platform": "linux" + "targets": [ + { + "id": "ssh-1", + "label": "SSH" + } + ] } } } }, - "19b2979d7fc2": { - "name": "repo.list#1", - "ordinal": 2, - "json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"repo.list\"}" - }, - "1a160fc2d137": { - "name": "host.platform#1", - "ordinal": 9, - "args": [ - { - "name": "method", - "value": "host.platform" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "pending", - "startedAt": 0 - } - }, - "1ff13ee8621c": { - "name": "repoIconsByName", - "ordinal": 4, - "value": [] - }, - "2f1b95eac8f1": { - "name": "repoColorsByName", - "ordinal": 3, - "value": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ] - }, - "36a96ddbd396": { - "name": "host.platform#1", - "ordinal": 12, - "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" - }, "4526bff18b74": { "name": "repo.list#1", "ordinal": 1, @@ -134,14 +95,180 @@ } } }, - "53226cb82ec3": { + "5248b37ec085": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "fulfilled", + "startedAt": 0, + "settledAt": 0, + "value": { + "id": "frame-4", + "ok": true, + "result": { + "platform": "linux" + } + } + } + }, + "661b4721aec6": { + "name": "desktopRepos", + "ordinal": 3, + "value": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ] + }, + "68916f125c3c": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ], + "repoHostIdByRepoId": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] + }, + "6d43117882f3": { + "desktopRepos": [ + { + "displayName": "Local", + "executionHostId": "local", + "id": "repo-1", + "path": "repo-1" + }, + { + "displayName": "Remote", + "executionHostId": "ssh:ssh-1", + "id": "repo-2", + "path": "repo-2" + } + ], + "hostHealthById": [], + "hostLabelById": [["ssh:ssh-1", "SSH"]], + "hostPlatform": "linux", + "repoHostIdByRepoId": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] + }, + "7c508088602c": { + "name": "host.platform#1", + "ordinal": 7, + "args": [ + { + "name": "method", + "value": "host.platform" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "pending", + "startedAt": 0 + } + }, + "8b618db22116": { + "name": "settings.get#1", + "ordinal": 9, + "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" + }, + "902c0b2ed3ce": { + "name": "settings.get#1", + "ordinal": 6, + "args": [ + { + "name": "method", + "value": "settings.get" + }, + { + "name": "params", + "value": { + "$rpc": "absent" + } + }, + { + "name": "options", + "value": { + "$rpc": "absent" + } + } + ], + "settlement": { + "status": "pending", + "startedAt": 0 + } + }, + "91e7f4ef9a77": { "name": "hostPlatform", - "ordinal": 15, + "ordinal": 13, "value": "linux" }, - "6021c58b2fd2": { - "name": "settings.get#1", + "9270aeb7d9c6": { + "status": "pending", + "startedAt": 0 + }, + "c1246f5792ee": { + "name": "ssh.listTargetSummaries#1", "ordinal": 8, + "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" + }, + "c1d6fc758acc": { + "name": "host.platform#1", + "ordinal": 10, + "json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"host.platform\"}" + }, + "ce32fe5b8377": { + "name": "settings.get#1", + "ordinal": 6, "args": [ { "name": "method", @@ -171,125 +298,23 @@ } } }, - "6063e534d9db": { - "name": "hostHealthById", - "ordinal": 13, - "value": [] - }, - "6134b73f18d0": { - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ], - "repoHostIdByRepoId": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "668a7e313975": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 10, - "json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"ssh.listTargetSummaries\"}" - }, - "7e772c1c111d": { - "name": "repoIdsByName", - "ordinal": 5, - "value": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] - }, - "8ef3b348c179": { - "name": "repoHostIdByRepoId", - "ordinal": 6, - "value": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ] - }, - "9270aeb7d9c6": { - "status": "pending", - "startedAt": 0 - }, - "96e0c54d86b0": { - "name": "settings.get#1", - "ordinal": 8, - "args": [ - { - "name": "method", - "value": "settings.get" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "pending", - "startedAt": 0 - } - }, - "b428bce85ee7": { - "name": "settings.get#1", - "ordinal": 11, - "json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}" - }, - "bea13a4a2e87": { + "d9c464d0dc78": { "name": "hostLabelById", - "ordinal": 14, + "ordinal": 12, "value": [["ssh:ssh-1", "SSH"]] }, - "c752e67787f9": { - "name": "ssh.listTargetSummaries#1", - "ordinal": 7, - "args": [ - { - "name": "method", - "value": "ssh.listTargetSummaries" - }, - { - "name": "params", - "value": { - "$rpc": "absent" - } - }, - { - "name": "options", - "value": { - "$rpc": "absent" - } - } - ], - "settlement": { - "status": "fulfilled", - "startedAt": 0, - "settledAt": 0, - "value": { - "id": "frame-2", - "ok": true, - "result": { - "targets": [ - { - "id": "ssh-1", - "label": "SSH" - } - ] - } - } - } + "e2881f748a46": { + "name": "hostHealthById", + "ordinal": 11, + "value": [] + }, + "e49da3e1abca": { + "name": "repoHostIdByRepoId", + "ordinal": 4, + "value": [ + ["repo-1", "local"], + ["repo-2", "ssh:ssh-1"] + ] }, "eb79a9b3682a": { "status": "fulfilled", @@ -298,24 +323,6 @@ "value": { "$rpc": "undefined" } - }, - "f4667dda0187": { - "hostHealthById": [], - "hostLabelById": [["ssh:ssh-1", "SSH"]], - "hostPlatform": "linux", - "repoColorsByName": [ - ["Local", "#6366f1"], - ["Remote", "#f97316"] - ], - "repoHostIdByRepoId": [ - ["repo-1", "local"], - ["repo-2", "ssh:ssh-1"] - ], - "repoIconsByName": [], - "repoIdsByName": [ - ["Local", "repo-1"], - ["Remote", "repo-2"] - ] } }, "recording": { @@ -324,34 +331,32 @@ { "id": "settings-pending", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "96e0c54d86b0", "1a160fc2d137"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "902c0b2ed3ce", "7c508088602c"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "9270aeb7d9c6" }, - "state": "6134b73f18d0", - "effects": ["2f1b95eac8f1", "1ff13ee8621c", "7e772c1c111d", "8ef3b348c179"] + "state": "68916f125c3c", + "effects": ["661b4721aec6", "e49da3e1abca"] } }, { "id": "settled", "observation": { - "sender": ["4526bff18b74", "c752e67787f9", "6021c58b2fd2", "03c9a2ba60b9"], - "payloads": ["19b2979d7fc2", "668a7e313975", "b428bce85ee7", "36a96ddbd396"], + "sender": ["4526bff18b74", "299d571abf13", "ce32fe5b8377", "5248b37ec085"], + "payloads": ["19b2979d7fc2", "c1246f5792ee", "8b618db22116", "c1d6fc758acc"], "settlements": { "mount": "eb79a9b3682a", "load": "eb79a9b3682a" }, - "state": "f4667dda0187", + "state": "6d43117882f3", "effects": [ - "2f1b95eac8f1", - "1ff13ee8621c", - "7e772c1c111d", - "8ef3b348c179", - "6063e534d9db", - "bea13a4a2e87", - "53226cb82ec3" + "661b4721aec6", + "e49da3e1abca", + "e2881f748a46", + "d9c464d0dc78", + "91e7f4ef9a77" ] } } diff --git a/mobile/src/host-screen/host-repo-looks.ts b/mobile/src/host-screen/host-repo-looks.ts new file mode 100644 index 00000000000..9fe08ea5ca1 --- /dev/null +++ b/mobile/src/host-screen/host-repo-looks.ts @@ -0,0 +1,18 @@ +import type { SectionRepo } from '../worktree/workspace-list-types' +import { sectionRepoColor } from '../worktree/workspace-filter-repos' +import { readMobileRepoIcon, type MobileHostRepoIcon } from './host-screen-reply-schema' + +/** The colour and icon a repo's header and rows draw. */ +export type RepoLook = { color: string; icon: MobileHostRepoIcon | null } + +/** Decoded once per catalog, so a row's props keep their identity across polls. */ +export function buildRepoLookById( + repoById: ReadonlyMap +): ReadonlyMap { + return new Map( + [...repoById].map(([id, repo]) => [ + id, + { color: sectionRepoColor(repo), icon: readMobileRepoIcon(repo.repoIcon) } + ]) + ) +} diff --git a/mobile/src/host-screen/host-screen-overlays.tsx b/mobile/src/host-screen/host-screen-overlays.tsx index f02078e0e3a..ce65935f9e6 100644 --- a/mobile/src/host-screen/host-screen-overlays.tsx +++ b/mobile/src/host-screen/host-screen-overlays.tsx @@ -95,7 +95,7 @@ export function HostScreenOverlays({ controller }: { controller: HostScreenContr Repositories {controller.sectionsResult.uniqueRepos.map((repo, i) => ( - + {i > 0 && } > +const mobileRepoIconSchema = z.union([ + repoIconBranches.lucide, + repoIconBranches.emoji, + repoIconBranches.image +]) + +/** A catalog repo's icon as MobileRepoIcon draws it; null for an arm this build cannot read. */ +export function readMobileRepoIcon(value: unknown): MobileHostRepoIcon | null { + const parsed = mobileRepoIconSchema.safeParse(value) + return parsed.success ? parsed.data : null +} // What the host screen reads to label its rows and to mirror the desktop's workspace view store. // Checked against src/main/runtime/rpc/methods/repo.ts:29, ssh.ts:55, host-capabilities.ts:8 and @@ -37,12 +48,12 @@ const repoIconBranches = { // PersistedUIState's workspace-view subset in mobile/src/worktree/workspace-view-settings.ts:14. /** - * The host's repo catalog, narrowed to what the label maps are built from. + * The host's repo catalog, narrowed to what the host-label maps and the repo cache are built from. * - * `id` and `displayName` are required and a row without either drops: `displayName` is the key of - * four Maps and the argument `repoColor` hashes with `name.charCodeAt` — a row without one was a - * TypeError that took the whole metadata refresh with it — and `id` is the Map value the workspace - * rows resolve their host through. + * `id` and `displayName` are required and a row without either drops: `displayName` is what the + * new-workspace picker shows from the repo cache, and `id` is the Map key the workspace rows + * resolve their host through. `path` labels an older desktop's repo sections (the newer one serves + * project-sections-operations.ts instead). * * `badgeColor` sits behind main's own `||` fallback to `repoColor`, so a salvaged member draws the * same swatch. `repoIcon` is declared as the three arms MobileRepoIcon renders, and an arm this @@ -64,11 +75,9 @@ export const hostRepoCatalogSchema = z z.looseObject({ id: z.string(), displayName: z.string(), + path: salvagedOptional('path', z.string()), badgeColor: salvagedOptional('badgeColor', z.string()), - repoIcon: salvagedOptional( - 'repoIcon', - z.union([repoIconBranches.lucide, repoIconBranches.emoji, repoIconBranches.image]) - ), + repoIcon: salvagedOptional('repoIcon', mobileRepoIconSchema), connectionId: salvagedOptional('connectionId', z.string().nullable()), executionHostId: salvagedOptional('executionHostId', z.string().nullable()) }) @@ -184,6 +193,8 @@ export const hostViewSettingsSchema = z */ export const hostScreenUnreadReplySchema = z.unknown() +export type HostRepoCatalogRow = z.output[number] + /** One decoded catalog icon: the members MobileRepoIcon reads, with the rest passed through. */ export type MobileHostRepoIcon = NonNullable< z.output[number]['repoIcon'] diff --git a/mobile/src/host-screen/host-show-pinned-in-groups.test.ts b/mobile/src/host-screen/host-show-pinned-in-groups.test.ts index a225a1a48bd..ab05b44ab9a 100644 --- a/mobile/src/host-screen/host-show-pinned-in-groups.test.ts +++ b/mobile/src/host-screen/host-show-pinned-in-groups.test.ts @@ -48,6 +48,7 @@ function screenState(clientRef: ClientRef, applied: boolean[]) { [ 'setActionError', 'setCatalogError', + 'setDesktopRepos', 'setError', 'setHostLabelById', 'setHostHealthById', @@ -57,9 +58,8 @@ function screenState(clientRef: ClientRef, applied: boolean[]) { 'setLastKnownWorktrees', 'setOptimisticActiveWorktreeIdentity', 'setPinnedIds', - 'setRepoColorsByName', 'setRepoHostIdByRepoId', - 'setRepoIconsByName', + 'setServedProjectSections', 'setSleptIds', 'setWorktrees', 'setWorktreesLoaded' diff --git a/mobile/src/host-screen/host-workspace-list-action-error.test.tsx b/mobile/src/host-screen/host-workspace-list-action-error.test.tsx index ec2d7af68e4..90b5677f374 100644 --- a/mobile/src/host-screen/host-workspace-list-action-error.test.tsx +++ b/mobile/src/host-screen/host-workspace-list-action-error.test.tsx @@ -60,7 +60,7 @@ function listWith(fields: { actionError: string; setActionError: (value: string) relayRecovery: { pairingRejected: false }, routeNotice: '', router: { push: () => {} }, - sectionsResult: { rawSections: [], sections: [], uniqueRepoColors: new Map() }, + sectionsResult: { rawSections: [], sections: [], repoById: new Map() }, setDismissedNotice: () => {}, settings: { activeFilterCount: 0 }, state: { @@ -69,7 +69,6 @@ function listWith(fields: { actionError: string; setActionError: (value: string) catalogError: null, collapsedGroups: new Set(), groupMode: 'none', - repoIconsByName: new Map(), search: '', showSearch: false, worktreesLoaded: true diff --git a/mobile/src/host-screen/host-workspace-list-row-memo.test.tsx b/mobile/src/host-screen/host-workspace-list-row-memo.test.tsx index 892e782420f..3ca99182a57 100644 --- a/mobile/src/host-screen/host-workspace-list-row-memo.test.tsx +++ b/mobile/src/host-screen/host-workspace-list-row-memo.test.tsx @@ -68,7 +68,7 @@ function worktree(id: string, overrides: Partial = {}): Worktree { } const noop = () => {} -const stable = { ref: { current: null }, repoColors: new Map(), repoIcons: new Map() } +const stable = { ref: { current: null }, repoById: new Map() } function listProps(worktrees: Worktree[]) { // Each poll re-parses the payload, so rows are fresh objects even when nothing changed. @@ -91,13 +91,12 @@ function listProps(worktrees: Worktree[]) { now: 1_000, reconnectAttempts: 0, relayRecovery: { pairingRejected: false }, - sectionsResult: { rawSections: sections, sections, uniqueRepoColors: stable.repoColors }, + sectionsResult: { rawSections: sections, sections, repoById: stable.repoById }, settings: { activeFilterCount: 0, toggleWorktreeLineage: noop }, state: { actionError: '', setActionTarget: noop, groupMode: 'none', - repoIconsByName: stable.repoIcons, search: '', showSearch: false, worktreesLoaded: true diff --git a/mobile/src/host-screen/host-workspace-list.tsx b/mobile/src/host-screen/host-workspace-list.tsx index 6f47fc6415b..fc6b2180505 100644 --- a/mobile/src/host-screen/host-workspace-list.tsx +++ b/mobile/src/host-screen/host-workspace-list.tsx @@ -1,3 +1,4 @@ +import { useMemo } from 'react' import { Platform, Pressable, RefreshControl, SectionList, Text, View } from 'react-native' import { ChevronDown, ChevronRight, Pin } from 'lucide-react-native' import { AuthFailedBanner } from '../components/AuthFailedBanner' @@ -13,6 +14,7 @@ import { HostWorkspaceListStates } from '../worktree/host-workspace-list-states' import { getWorktreeStatus } from '../worktree/workspace-list-sections' import { repoColor } from '../worktree/repo-color' import { hostScreenStyles as styles } from './host-screen-styles' +import { buildRepoLookById } from './host-repo-looks' import type { HostScreenController } from './use-host-screen-controller' export function HostWorkspaceList({ controller }: { controller: HostScreenController }) { @@ -40,7 +42,8 @@ export function HostWorkspaceList({ controller }: { controller: HostScreenContro settings, state } = controller - const { rawSections, sections, uniqueRepoColors } = sectionsResult + const { rawSections, sections, repoById } = sectionsResult + const repoLookById = useMemo(() => buildRepoLookById(repoById), [repoById]) return ( <> @@ -140,10 +143,11 @@ export function HostWorkspaceList({ controller }: { controller: HostScreenContro const isCollapsed = state.collapsedGroups.has(section.key) const rawSection = rawSections.find((s) => s.key === section.key) const count = rawSection?.data.length ?? 0 - const repoSectionColor = - state.groupMode === 'repo' ? uniqueRepoColors.get(section.title) : null - const repoSectionIcon = - state.groupMode === 'repo' ? state.repoIconsByName.get(section.title) : null + // Why: the desktop's header draws its anchor repo's colour and icon. + const repoSectionLook = + state.groupMode === 'repo' && section.repo ? repoLookById.get(section.repo.id) : null + const repoSectionColor = repoSectionLook?.color ?? null + const repoSectionIcon = repoSectionLook?.icon ?? null return ( vi.fn()) + +vi.mock('../worktree/project-sections-operations', () => ({ + projectSectionsRead: { request }, + readServedProjectSections: () => ({ kind: 'answered', value: null }) +})) + +import type { RpcClient } from '../transport/rpc-client' +import { useHostProjectSections } from './use-host-project-sections' + +function clientDouble(): RpcClient { + // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the hook only compares the client by identity and hands it to the mocked read. + return {} as RpcClient +} + +describe('useHostProjectSections', () => { + it('fetches the catalog at once when the screen moves to another host client', async () => { + request.mockResolvedValue({ ok: true }) + const clientRef: { current: RpcClient | null } = { current: null } + let refresh: (options?: { force?: boolean }) => Promise = async () => {} + function Probe({ client, hostId }: { client: RpcClient; hostId: string }) { + refresh = useHostProjectSections({ + client, + connState: 'connected', + hostId, + state: { clientRef, setServedProjectSections: () => {} } + }) + return null + } + const first = clientDouble() + const second = clientDouble() + clientRef.current = first + let tree!: ReturnType + act(() => { + tree = create(createElement(Probe, { client: first, hostId: 'a' })) + }) + await act(async () => refresh()) + await act(async () => refresh()) + expect(request).toHaveBeenCalledTimes(1) + + clientRef.current = second + act(() => tree.update(createElement(Probe, { client: second, hostId: 'b' }))) + await act(async () => refresh()) + expect(request).toHaveBeenCalledTimes(2) + expect(request).toHaveBeenLastCalledWith(second) + }) +}) diff --git a/mobile/src/host-screen/use-host-project-sections.ts b/mobile/src/host-screen/use-host-project-sections.ts new file mode 100644 index 00000000000..c775009186a --- /dev/null +++ b/mobile/src/host-screen/use-host-project-sections.ts @@ -0,0 +1,56 @@ +import { useCallback, useRef } from 'react' +import type { RpcClient } from '../transport/rpc-client' +import type { ConnectionState } from '../transport/types' +import { + projectSectionsRead, + readServedProjectSections +} from '../worktree/project-sections-operations' +import type { HostScreenState } from './use-host-screen-state' + +const PROJECT_SECTIONS_REFRESH_MS = 60_000 + +/** + * The desktop sidebar's Project catalog, refreshed with repo metadata but as its own read, so a + * slow or missing answer never holds the repo labels back. + */ +export function useHostProjectSections(args: { + client: RpcClient | null + connState: ConnectionState + hostId: string | undefined + state: Pick +}): (options?: { force?: boolean }) => Promise { + const { client, connState, hostId, state } = args + const { clientRef, setServedProjectSections } = state + const inFlight = useRef(new WeakSet()) + // Per client, so a reused screen for another host fetches its catalog at once. + const fetchedAt = useRef(new WeakMap()) + + return useCallback( + async (options: { force?: boolean } = {}) => { + if (!client || connState !== 'connected' || !hostId || inFlight.current.has(client)) { + return + } + if ( + !options.force && + Date.now() - (fetchedAt.current.get(client) ?? 0) < PROJECT_SECTIONS_REFRESH_MS + ) { + return + } + inFlight.current.add(client) + try { + const reply = await projectSectionsRead.request(client).catch(() => null) + if (clientRef.current !== client) { + return + } + const sections = readServedProjectSections(reply) + if (sections.kind === 'answered') { + fetchedAt.current.set(client, Date.now()) + setServedProjectSections(sections.value) + } + } finally { + inFlight.current.delete(client) + } + }, + [client, clientRef, connState, hostId, setServedProjectSections] + ) +} diff --git a/mobile/src/host-screen/use-host-repo-metadata.ts b/mobile/src/host-screen/use-host-repo-metadata.ts index 892e90ff512..550b0eefed3 100644 --- a/mobile/src/host-screen/use-host-repo-metadata.ts +++ b/mobile/src/host-screen/use-host-repo-metadata.ts @@ -5,12 +5,13 @@ import { setCachedRepos } from '../cache/repo-cache' import type { RpcAcceptedResult } from '../transport/rpc-accepted-result' import type { RpcClient } from '../transport/rpc-client' import type { ConnectionState, RpcResponse } from '../transport/types' -import { repoColor } from '../worktree/repo-color' import { buildHostLabelById, buildRepoHostIdByRepoId } from '../worktree/worktree-host-context-labels' import { buildSshHostHealthById } from '../worktree/worktree-host-health' +import type { SectionRepo } from '../worktree/workspace-list-types' +import type { HostRepoCatalogRow } from './host-screen-reply-schema' import { hostPlatformRead, hostRepoCatalogRead, @@ -41,6 +42,18 @@ function acceptedMetadata( return verdict.accepted ? verdict.value : null } +/** A desktop repo.list row as Project sections read it, for a desktop that serves no catalog. */ +function desktopSectionRepo(repo: HostRepoCatalogRow): SectionRepo { + return { + id: repo.id, + displayName: repo.displayName, + path: repo.path ?? repo.id, + executionHostId: getRepoExecutionHostId(repo), + ...(repo.badgeColor ? { badgeColor: repo.badgeColor } : {}), + ...(repo.repoIcon ? { repoIcon: repo.repoIcon } : {}) + } +} + function readHostSettingOverrides(result: unknown): unknown { // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Preserve the established response shape at this boundary. return (result as { hostSettingOverrides?: unknown } | null)?.hostSettingOverrides @@ -60,11 +73,9 @@ export function useHostRepoMetadata(args: { repoMetadataFetchedAtRef, setHostLabelById, setHostHealthById, + setDesktopRepos, setHostPlatform, - setRepoColorsByName, - setRepoHostIdByRepoId, - setRepoIconsByName, - setRepoIdsByName + setRepoHostIdByRepoId } = state const fetchRepoMetadata = useCallback( @@ -101,22 +112,7 @@ export function useHostRepoMetadata(args: { const catalog = repos.value repoMetadataFetchedAtRef.current = Date.now() setCachedRepos(requestHostId, catalog) - setRepoColorsByName( - new Map( - catalog.map((repo) => [ - repo.displayName, - repo.badgeColor || repoColor(repo.displayName) - ]) - ) - ) - setRepoIconsByName( - new Map( - catalog.flatMap((repo) => - repo.repoIcon ? [[repo.displayName, repo.repoIcon] as const] : [] - ) - ) - ) - setRepoIdsByName(new Map(catalog.map((repo) => [repo.displayName, repo.id]))) + setDesktopRepos(catalog.map(desktopSectionRepo)) setRepoHostIdByRepoId(buildRepoHostIdByRepoId(catalog)) // Why: rows name their host when the list spans hosts or an SSH host may be unhealthy, // so a local-only catalog never pays for the label lookups. Counted over repos, not the diff --git a/mobile/src/host-screen/use-host-screen-controller.ts b/mobile/src/host-screen/use-host-screen-controller.ts index 23eb64e99b1..2e3075b5899 100644 --- a/mobile/src/host-screen/use-host-screen-controller.ts +++ b/mobile/src/host-screen/use-host-screen-controller.ts @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' import { useLocalSearchParams, usePathname } from 'expo-router' import { useRouteHandoff } from '../navigation/route-handoff' import { useSafeAreaInsets } from 'react-native-safe-area-context' @@ -16,7 +16,9 @@ import { } from '../transport/client-context-connection-metrics' import { useWorkspaceSections } from '../worktree/use-workspace-sections' import { useHostDisplayWorktrees } from './use-host-display-worktrees' -import { useHostRepoMetadata } from './use-host-repo-metadata' +import { buildProjectSectionCatalog } from '../worktree/project-section-catalog' +import { useHostProjectSections } from './use-host-project-sections' +import { useHostRepoMetadata, type FetchHostRepoMetadata } from './use-host-repo-metadata' import { useHostScreenIdentity } from './use-host-screen-identity' import { useHostScreenState } from './use-host-screen-state' import { useHostViewSettings } from './use-host-view-settings' @@ -72,7 +74,15 @@ export function useHostScreenController({ ) useHostScreenIdentity({ client, hostId, state }) - const fetchRepoMetadata = useHostRepoMetadata({ client, connState, hostId, state }) + const fetchRepoMetadataOnly = useHostRepoMetadata({ client, connState, hostId, state }) + const fetchProjectSections = useHostProjectSections({ client, connState, hostId, state }) + const fetchRepoMetadata = useCallback( + (options) => { + void fetchProjectSections(options) + return fetchRepoMetadataOnly(options) + }, + [fetchProjectSections, fetchRepoMetadataOnly] + ) const catalog = useHostWorktreeCatalog({ client, connState, @@ -104,6 +114,15 @@ export function useHostScreenController({ const showNewWorktree = resolvedRouteActionState.showNewWorktree const displayWorktrees = useHostDisplayWorktrees(connState, state, hostId) + const projectCatalog = useMemo( + () => + buildProjectSectionCatalog( + state.servedProjectSections, + state.desktopRepos, + state.visibleHostIds + ), + [state.servedProjectSections, state.desktopRepos, state.visibleHostIds] + ) const sectionsResult = useWorkspaceSections({ displayWorktrees, sortMode: state.sortMode, @@ -112,8 +131,8 @@ export function useHostScreenController({ groupMode: state.groupMode, pinnedIds: state.pinnedIds, showPinnedInGroups: state.showPinnedInGroups, - repoIdsByName: state.repoIdsByName, - repoColorsByName: state.repoColorsByName, + projectCatalog, + filterCatalogRepos: state.servedProjectSections?.repos ?? state.desktopRepos, collapsedGroups: state.collapsedGroups, workspaceStatuses: state.workspaceStatuses }) diff --git a/mobile/src/host-screen/use-host-screen-identity.ts b/mobile/src/host-screen/use-host-screen-identity.ts index 6f675b8457f..9e482ec124b 100644 --- a/mobile/src/host-screen/use-host-screen-identity.ts +++ b/mobile/src/host-screen/use-host-screen-identity.ts @@ -18,15 +18,15 @@ export function useHostScreenIdentity(args: { setCatalogError, setError, setHostLabelById, + setServedProjectSections, + setDesktopRepos, setHostHealthById, setHostName, setHostPlatform, setHostStoredDescriptor, setLastKnownWorktrees, setPinnedIds, - setRepoColorsByName, setRepoHostIdByRepoId, - setRepoIconsByName, setShowPinnedInGroups, setWorktrees, setWorktreesLoaded @@ -59,10 +59,10 @@ export function useHostScreenIdentity(args: { setHostName('') setHostStoredDescriptor(null) setError('') - setRepoColorsByName(new Map()) - setRepoIconsByName(new Map()) setRepoHostIdByRepoId(new Map()) setHostLabelById(new Map()) + setServedProjectSections(null) + setDesktopRepos([]) setHostHealthById(new Map()) setHostPlatform(null) setShowPinnedInGroups(false) diff --git a/mobile/src/host-screen/use-host-screen-state.ts b/mobile/src/host-screen/use-host-screen-state.ts index 19b7003581d..d24f4bc942d 100644 --- a/mobile/src/host-screen/use-host-screen-state.ts +++ b/mobile/src/host-screen/use-host-screen-state.ts @@ -13,7 +13,8 @@ import type { MobileViewState } from '../worktree/workspace-view-settings' import type { FilterState, Worktree } from '../worktree/workspace-list-sections' -import type { MobileHostRepoIcon } from './host-screen-reply-schema' +import type { ServedProjectSections } from '../worktree/project-sections-operations' +import type { SectionRepo } from '../worktree/workspace-list-types' import { NO_SERVER_WORKSPACES, type ServerWorkspaces } from '../worktree/server-workspaces' export function useHostScreenState(hostId: string | undefined, action: string | undefined) { @@ -39,8 +40,6 @@ export function useHostScreenState(hostId: string | undefined, action: string | const [optimisticActiveWorktreeIdentity, setOptimisticActiveWorktreeIdentity] = useState< string | null >(null) - const [repoColorsByName, setRepoColorsByName] = useState>(new Map()) - const [repoIconsByName, setRepoIconsByName] = useState>(new Map()) const [hostName, setHostName] = useState('') // The stored name identity, loaded with the name so the header can label an offline host. const [hostStoredDescriptor, setHostStoredDescriptor] = useState<{ @@ -66,8 +65,12 @@ export function useHostScreenState(hostId: string | undefined, action: string | const [workspaceStatuses, setWorkspaceStatuses] = useState( DEFAULT_MOBILE_WORKSPACE_STATUSES ) - // displayName → repo id: filters key on repo id, but section headers/rows key on displayName, so bridge the two. - const [repoIdsByName, setRepoIdsByName] = useState>(new Map()) + // The desktop sidebar's Project grouping inputs; null for a desktop that serves none. + const [servedProjectSections, setServedProjectSections] = useState( + null + ) + // The desktop's own repos from repo.list: an older desktop's rows group by these. + const [desktopRepos, setDesktopRepos] = useState([]) // Host-label inputs for rows: repo → host, SSH/override labels, and the host's own platform. const [repoHostIdByRepoId, setRepoHostIdByRepoId] = useState>( new Map() @@ -133,10 +136,9 @@ export function useHostScreenState(hostId: string | undefined, action: string | newWorktreeModalVisibleRef, optimisticActiveWorktreeIdentity, pinnedIds, - repoColorsByName, repoHostIdByRepoId, - repoIconsByName, - repoIdsByName, + servedProjectSections, + desktopRepos, repoMetadataFetchedAtRef, routeActionState, search, @@ -159,10 +161,9 @@ export function useHostScreenState(hostId: string | undefined, action: string | setLastKnownWorktrees, setOptimisticActiveWorktreeIdentity, setPinnedIds, - setRepoColorsByName, setRepoHostIdByRepoId, - setRepoIconsByName, - setRepoIdsByName, + setServedProjectSections, + setDesktopRepos, setRouteActionState, setSearch, setServerNotice, diff --git a/mobile/src/worktree/project-section-catalog.test.ts b/mobile/src/worktree/project-section-catalog.test.ts new file mode 100644 index 00000000000..b08fd27c50e --- /dev/null +++ b/mobile/src/worktree/project-section-catalog.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest' +import { buildProjectSectionCatalog } from './project-section-catalog' +import type { ServedProjectSections } from './project-sections-operations' + +const uploaded = { type: 'image', src: 'data:image/png;base64,AAAA' } + +type ServedRepo = ServedProjectSections['repos'][number] + +function repo( + id: string, + executionHostId?: ServedRepo['executionHostId'], + repoIcon?: unknown +): ServedRepo { + return { + id, + displayName: 'orca', + path: `/repos/${id}`, + badgeColor: undefined, + addedAt: undefined, + connectionId: undefined, + executionHostId, + ...(repoIcon ? { repoIcon } : {}) + } +} + +describe('buildProjectSectionCatalog', () => { + it("restores an uploaded icon from the desktop's repo.list only for the same host's repo", () => { + const served: ServedProjectSections = { + repos: [repo('r1'), repo('r1', 'runtime:m4air')], + projects: [], + setups: [], + projectOrderBy: 'manual' + } + const catalog = buildProjectSectionCatalog(served, [repo('r1', undefined, uploaded)], null) + expect(catalog.repos.map((entry) => entry.repoIcon)).toEqual([uploaded, undefined]) + }) +}) diff --git a/mobile/src/worktree/project-section-catalog.ts b/mobile/src/worktree/project-section-catalog.ts new file mode 100644 index 00000000000..79140cc3564 --- /dev/null +++ b/mobile/src/worktree/project-section-catalog.ts @@ -0,0 +1,38 @@ +import { getRepoExecutionHostId, type ExecutionHostId } from '../../../src/shared/execution-host' +import { getLogicalRepoOrderRankById } from '../../../src/shared/project-section-order' +import type { ServedProjectSections } from './project-sections-operations' +import { NO_PROJECT_SECTION_CATALOG, type ProjectSectionCatalog } from './workspace-list-sections' +import type { SectionRepo } from './workspace-list-types' + +/** + * Project grouping's inputs: the desktop sidebar's own catalog when it serves one, else (an older + * desktop) its own repos with no projects, so rows group by repo. Only shown hosts' repos open + * empty sections; the grouping itself spans every host, as the desktop's does. + */ +export function buildProjectSectionCatalog( + served: ServedProjectSections | null, + desktopRepos: readonly SectionRepo[], + visibleHostIds: ReadonlySet | null +): ProjectSectionCatalog { + const shown = (repos: readonly SectionRepo[]) => + visibleHostIds + ? repos.filter((repo) => visibleHostIds.has(getRepoExecutionHostId(repo))) + : repos + if (!served) { + return { ...NO_PROJECT_SECTION_CATALOG, repos: shown(desktopRepos) } + } + // Why: the desktop withholds uploaded image icons from the snapshot; its repo.list still carries them. + const iconKey = (repo: SectionRepo) => `${getRepoExecutionHostId(repo)}|${repo.id}` + const iconByKey = new Map(desktopRepos.map((repo) => [iconKey(repo), repo.repoIcon])) + const repos = served.repos.map((repo) => { + const icon = repo.repoIcon ? undefined : iconByKey.get(iconKey(repo)) + return icon ? { ...repo, repoIcon: icon } : repo + }) + return { + repos: shown(repos), + grouping: { projects: served.projects, projectHostSetups: served.setups }, + // Why: served repos come in the sidebar's header order, cross-host manual order included. + repoOrder: getLogicalRepoOrderRankById(served.repos.map((repo) => repo.id)), + projectOrderBy: served.projectOrderBy + } +} diff --git a/mobile/src/worktree/project-sections-operations.ts b/mobile/src/worktree/project-sections-operations.ts new file mode 100644 index 00000000000..21bbf9270a3 --- /dev/null +++ b/mobile/src/worktree/project-sections-operations.ts @@ -0,0 +1,104 @@ +import { z } from 'zod' +import { MOBILE_PROJECT_SECTIONS_METHOD } from '../../../src/shared/mobile-project-sections-contract' +import { normalizeExecutionHostId } from '../../../src/shared/execution-host' +import type { ProjectHostSetupMethod } from '../../../src/shared/project-types' +import type { RepoKind } from '../../../src/shared/repo-types' +import type { ProjectOrderBy } from '../../../src/shared/ui-chrome-types' +import { hostUnionArms, salvagedOptional, salvagingArray } from '../../../src/shared/zod-salvage' +import { bindDeferredRpcOperation, defineRpcOperation } from '../transport/rpc-operation' +import { rpcResultVariant } from '../transport/rpc-operation-result-reader' +import { isMobileMethodUnavailableError } from '../transport/mobile-method-unavailable' +import type { RpcResponse } from '../transport/types' +import type { SectionRepo } from './workspace-list-types' + +const SETUP_METHOD_ARMS = hostUnionArms({ + 'legacy-repo': true, + 'imported-existing-folder': true, + cloned: true, + provisioned: true +}) +const REPO_KIND_ARMS = hostUnionArms({ git: true, folder: true }) +const PROJECT_ORDER_BY_ARMS = hostUnionArms({ manual: true, recent: true }) + +// A host id the desktop spells; normalized, so a spelling this build cannot read drops. +const hostIdSchema = z.string().transform((value, ctx) => { + const hostId = normalizeExecutionHostId(value) + if (!hostId) { + ctx.addIssue({ code: 'custom', message: 'unknown host id' }) + return z.NEVER + } + return hostId +}) + +const repoSchema = z.object({ + id: z.string(), + displayName: z.string(), + path: z.string(), + badgeColor: salvagedOptional('badgeColor', z.string()), + addedAt: salvagedOptional('addedAt', z.number()), + connectionId: salvagedOptional('connectionId', z.string().nullable()), + executionHostId: salvagedOptional('executionHostId', hostIdSchema), + // Decoded where drawn (readMobileRepoIcon), so an arm this build lacks still keeps the row. + repoIcon: z.unknown().optional() +}) satisfies z.ZodType + +const setupSchema = z.object({ + id: z.string(), + projectId: z.string(), + hostId: hostIdSchema, + repoId: z.string(), + path: z.string(), + displayName: z.string(), + // Why the fallback: only 'provisioned' changes grouping, so a newer arm groups as a checkout. + setupMethod: z.enum(SETUP_METHOD_ARMS).catch('legacy-repo'), + kind: salvagedOptional('kind', z.enum(REPO_KIND_ARMS)), + connectionId: salvagedOptional('connectionId', z.string().nullable()), + executionHostId: salvagedOptional('executionHostId', hostIdSchema) +}) + +const catalogSchema = z.object({ + repos: salvagingArray(repoSchema), + projects: salvagingArray(z.object({ id: z.string(), displayName: z.string() })), + setups: salvagingArray(setupSchema), + projectOrderBy: z.enum(PROJECT_ORDER_BY_ARMS).catch('manual') +}) + +/** The desktop sidebar's Project grouping inputs, as this phone reads them. */ +export type ServedProjectSections = z.output + +/** The sidebar's Project grouping inputs; null until a desktop window has published them. */ +export const projectSectionsRead = bindDeferredRpcOperation( + defineRpcOperation({ + name: 'mobileRelay.projectSections-or-skip', + method: MOBILE_PROJECT_SECTIONS_METHOD, + acceptance: 'success-result-or-skip', + barrier: 'after-caller-barrier', + read: rpcResultVariant( + 'project-sections', + z.object({ catalog: catalogSchema.nullable() }).transform(({ catalog }) => catalog) + ) + }) +) + +/** + * The desktop's sidebar catalog: absent (null) on a desktop that predates the read, and left + * as it was after a transient failure, so a dropped reply never regroups the list. + */ +export function readServedProjectSections( + reply: RpcResponse | null +): { kind: 'answered'; value: ServedProjectSections | null } | { kind: 'kept' } { + if (!reply) { + return { kind: 'kept' } + } + if (!reply.ok) { + return isMobileMethodUnavailableError(reply.error.code, reply.error.message) + ? { kind: 'answered', value: null } + : { kind: 'kept' } + } + try { + const verdict = projectSectionsRead.interpret(reply) + return verdict.accepted ? { kind: 'answered', value: verdict.value } : { kind: 'kept' } + } catch { + return { kind: 'kept' } + } +} diff --git a/mobile/src/worktree/use-workspace-sections.ts b/mobile/src/worktree/use-workspace-sections.ts index 0183efb78a2..0611620f9f8 100644 --- a/mobile/src/worktree/use-workspace-sections.ts +++ b/mobile/src/worktree/use-workspace-sections.ts @@ -2,18 +2,15 @@ import { useMemo } from 'react' import type { WorkspaceStatusDefinition } from '../../../src/shared/worktree/types' import type { MobileGroupMode, MobileSortMode } from './workspace-view-settings' import { + buildSectionRepoMap, buildSections, type FilterState, + type ProjectSectionCatalog, type Section, + type SectionRepo, type Worktree } from './workspace-list-sections' -import { repoColor } from './repo-color' - -export type WorkspaceSectionRepo = { - name: string - id: string - color: string -} +import { buildFilterRepos, type WorkspaceSectionRepo } from './workspace-filter-repos' export function useWorkspaceSections(args: { displayWorktrees: Worktree[] @@ -23,15 +20,17 @@ export function useWorkspaceSections(args: { groupMode: MobileGroupMode pinnedIds: Set showPinnedInGroups: boolean - repoIdsByName: Map - repoColorsByName: Map + projectCatalog: ProjectSectionCatalog + /** Every catalog repo on every host, which is what the desktop's filter lists. */ + filterCatalogRepos: readonly SectionRepo[] collapsedGroups: Set workspaceStatuses: readonly WorkspaceStatusDefinition[] }): { sections: Section[] rawSections: Section[] uniqueRepos: WorkspaceSectionRepo[] - uniqueRepoColors: Map + /** Each row's repo by id, as the desktop's cards look theirs up for colour and icon. */ + repoById: ReadonlyMap } { const { displayWorktrees, @@ -41,28 +40,20 @@ export function useWorkspaceSections(args: { groupMode, pinnedIds, showPinnedInGroups, - repoIdsByName, - repoColorsByName, + projectCatalog, + filterCatalogRepos, collapsedGroups, workspaceStatuses } = args - const uniqueRepos = useMemo(() => { - const repos = new Map() - for (const w of displayWorktrees) { - if (!repos.has(w.repo)) { - repos.set(w.repo, { - id: repoIdsByName.get(w.repo) ?? w.repoId, - color: repoColorsByName.get(w.repo) ?? repoColor(w.repo) - }) - } - } - return [...repos.entries()].map(([name, { id, color }]) => ({ name, id, color })) - }, [displayWorktrees, repoColorsByName, repoIdsByName]) + const uniqueRepos = useMemo( + () => buildFilterRepos(filterCatalogRepos, displayWorktrees), + [filterCatalogRepos, displayWorktrees] + ) - const uniqueRepoColors = useMemo( - () => new Map(uniqueRepos.map((repo) => [repo.name, repo.color])), - [uniqueRepos] + const repoById = useMemo( + () => buildSectionRepoMap(projectCatalog.repos, displayWorktrees), + [projectCatalog.repos, displayWorktrees] ) const rawSections = useMemo( @@ -74,7 +65,7 @@ export function useWorkspaceSections(args: { search, groupMode, pinnedIds, - repoIdsByName, + projectCatalog, workspaceStatuses, collapsedGroups, showPinnedInGroups @@ -86,7 +77,7 @@ export function useWorkspaceSections(args: { search, groupMode, pinnedIds, - repoIdsByName, + projectCatalog, workspaceStatuses, collapsedGroups, showPinnedInGroups @@ -102,5 +93,5 @@ export function useWorkspaceSections(args: { [rawSections, collapsedGroups] ) - return { sections, rawSections, uniqueRepos, uniqueRepoColors } + return { sections, rawSections, uniqueRepos, repoById } } diff --git a/mobile/src/worktree/workspace-filter-repos.test.ts b/mobile/src/worktree/workspace-filter-repos.test.ts new file mode 100644 index 00000000000..83027127c24 --- /dev/null +++ b/mobile/src/worktree/workspace-filter-repos.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, it } from 'vitest' +import { buildFilterRepos } from './workspace-filter-repos' +import { filterWorktrees, type Worktree } from './workspace-list-sections' + +function row(worktreeId: string, repoId: string, hostId: Worktree['hostId']): Worktree { + return { + worktreeId, + repoId, + hostId, + repo: 'orca', + branch: 'feature', + displayName: worktreeId, + path: `/tmp/${worktreeId}`, + liveTerminalCount: 0, + hasAttachedPty: false, + unread: false, + isPinned: false, + linkedPR: null + } +} + +describe('the repo filter', () => { + it("lists each host's same-named repo separately, by id, as the desktop's filter does", () => { + const repos = buildFilterRepos( + [ + { id: 'local-orca', displayName: 'orca', path: '/Users/me/orca', badgeColor: '#111111' }, + { + id: 'm4-orca', + displayName: 'orca', + path: '/Users/m4air/orca/orca', + executionHostId: 'runtime:m4air' + }, + { id: 'assets', displayName: 'design-assets', path: '/Users/me/design-assets' } + ], + [] + ) + + expect(repos.map(({ key, id, name }) => ({ key, id, name }))).toEqual([ + { key: 'local|local-orca', id: 'local-orca', name: 'orca' }, + { key: 'runtime:m4air|m4-orca', id: 'm4-orca', name: 'orca' }, + { key: 'local|assets', id: 'assets', name: 'design-assets' } + ]) + expect(repos[0]?.color).toBe('#111111') + }) + + it('lists the repos the rows name until a catalog answers, still one per repo id', () => { + const repos = buildFilterRepos( + [], + [row('a', 'local-orca', 'local'), row('b', 'm4-orca', 'runtime:m4air')] + ) + + expect(repos.map((repo) => repo.id)).toEqual(['local-orca', 'm4-orca']) + }) + + it('filters by the picked repo id, leaving the same-named repo on another host out', () => { + const rows = [row('a', 'local-orca', 'local'), row('b', 'm4-orca', 'runtime:m4air')] + + expect( + filterWorktrees( + rows, + { filterRepoIds: new Set(['m4-orca']), hideSleeping: false, hideDefaultBranch: false }, + '' + ).map((w) => w.worktreeId) + ).toEqual(['b']) + }) +}) diff --git a/mobile/src/worktree/workspace-filter-repos.ts b/mobile/src/worktree/workspace-filter-repos.ts new file mode 100644 index 00000000000..618c4e5cb24 --- /dev/null +++ b/mobile/src/worktree/workspace-filter-repos.ts @@ -0,0 +1,39 @@ +import { getRepoExecutionHostId } from '../../../src/shared/execution-host' +import { repoColor } from './repo-color' +import { buildSectionRepoMap, type SectionRepo, type Worktree } from './workspace-list-sections' + +export type WorkspaceSectionRepo = { + /** Unique per host: two hosts can each hold a repo of the same name. */ + key: string + name: string + id: string + color: string +} + +/** A repo's badge colour, else the colour its name hashes to. */ +export function sectionRepoColor(repo: Pick): string { + return (typeof repo.badgeColor === 'string' && repo.badgeColor) || repoColor(repo.displayName) +} + +/** + * The repos the filter lists, as the desktop's project filter lists them: every catalog repo by + * id, each host's separately. Before any catalog answers, the repos the rows name. + */ +export function buildFilterRepos( + filterCatalogRepos: readonly SectionRepo[], + worktrees: readonly Worktree[] +): WorkspaceSectionRepo[] { + const repos = + filterCatalogRepos.length > 0 + ? filterCatalogRepos + : [...buildSectionRepoMap([], worktrees).values()] + const seen = new Set() + return repos.flatMap((repo) => { + const key = `${getRepoExecutionHostId(repo)}|${repo.id}` + if (seen.has(key)) { + return [] + } + seen.add(key) + return [{ key, id: repo.id, name: repo.displayName, color: sectionRepoColor(repo) }] + }) +} diff --git a/mobile/src/worktree/workspace-list-picker-options.ts b/mobile/src/worktree/workspace-list-picker-options.ts index ca597ed38cc..5a9f4886443 100644 --- a/mobile/src/worktree/workspace-list-picker-options.ts +++ b/mobile/src/worktree/workspace-list-picker-options.ts @@ -18,8 +18,8 @@ export const WORKSPACE_SORT_OPTIONS: PickerOption[] = [ ] export const WORKSPACE_GROUP_OPTIONS: PickerOption[] = [ - { value: 'none', label: 'No Grouping' }, + { value: 'none', label: 'None' }, { value: 'workspaceStatus', label: 'Status' }, - { value: 'repo', label: 'Repository' }, + { value: 'repo', label: 'Project' }, { value: 'prStatus', label: 'PR Status' } ] diff --git a/mobile/src/worktree/workspace-list-pinned-lineage.test.ts b/mobile/src/worktree/workspace-list-pinned-lineage.test.ts index 2b64872d9ac..d5d3dd58b64 100644 --- a/mobile/src/worktree/workspace-list-pinned-lineage.test.ts +++ b/mobile/src/worktree/workspace-list-pinned-lineage.test.ts @@ -1,7 +1,7 @@ import { join } from 'node:path' import { describe, expect, it } from 'vitest' import type { Worktree } from './workspace-list-sections' -import { buildSections } from './workspace-list-sections' +import { NO_PROJECT_SECTION_CATALOG, buildSections } from './workspace-list-sections' import { DEFAULT_MOBILE_WORKSPACE_STATUSES } from './mobile-workspace-statuses' function worktree(overrides: Partial = {}): Worktree { @@ -41,7 +41,7 @@ describe('a pinned parent with an unpinned child', () => { '', 'repo', new Set(), - new Map(), + NO_PROJECT_SECTION_CATALOG, DEFAULT_MOBILE_WORKSPACE_STATUSES, new Set(), showPinnedInGroups @@ -64,7 +64,7 @@ describe('a pinned parent with an unpinned child', () => { it('nests the child under its parent in both sections when the desktop setting is on', () => { expect(nesting(true)).toEqual([ { key: 'pinned', rows: nested }, - { key: 'repo:orca', rows: nested } + { key: 'repo:repo-1', rows: nested } ]) }) }) diff --git a/mobile/src/worktree/workspace-list-sections-desktop-parity.test.ts b/mobile/src/worktree/workspace-list-sections-desktop-parity.test.ts new file mode 100644 index 00000000000..f0e03d8fa89 --- /dev/null +++ b/mobile/src/worktree/workspace-list-sections-desktop-parity.test.ts @@ -0,0 +1,147 @@ +import { describe, expect, it } from 'vitest' +import { getRepoExecutionHostId } from '../../../src/shared/execution-host' +import { + PROJECT_SECTION_PARITY_HEADERS, + PROJECT_SECTION_PARITY_ROWS, + PROJECT_SECTION_PARITY_SERVED +} from '../../../src/shared/project-section-parity.test-fixture' +import { buildProjectSectionCatalog } from './project-section-catalog' +import { + projectSectionsRead, + readServedProjectSections, + type ServedProjectSections +} from './project-sections-operations' +import { buildSections, type Worktree } from './workspace-list-sections' + +// The desktop half lives in src/renderer/src/components/sidebar/ +// worktree-list-groups-mobile-parity.test.ts: buildRows renders these headers, and main serves +// PROJECT_SECTION_PARITY_SERVED for the same host answers. + +function served(projectOrderBy: 'manual' | 'recent'): ServedProjectSections | null { + const verdict = projectSectionsRead.interpret({ + id: 'sections', + ok: true, + result: { catalog: { ...PROJECT_SECTION_PARITY_SERVED, projectOrderBy } } + }) + return verdict.accepted ? verdict.value : null +} + +function rows(): Worktree[] { + const repoById = new Map(PROJECT_SECTION_PARITY_SERVED.repos.map((repo) => [repo.id, repo])) + return PROJECT_SECTION_PARITY_ROWS.flatMap((row) => { + const repo = repoById.get(row.repoId) + return repo + ? [ + { + worktreeId: row.id, + repoId: row.repoId, + hostId: getRepoExecutionHostId(repo), + repo: repo.displayName, + branch: 'feature', + displayName: row.id, + path: `${repo.path}-${row.id}`, + lastActivityAt: row.lastActivityAt, + liveTerminalCount: 0, + hasAttachedPty: false, + unread: false, + isPinned: false, + linkedPR: null + } + ] + : [] + }) +} + +function headers(catalog: ReturnType) { + return buildSections( + rows(), + 'recent', + { filterRepoIds: new Set(), hideSleeping: false, hideDefaultBranch: false }, + '', + 'repo', + new Set(), + catalog + ).map((section) => ({ key: section.key, label: section.title })) +} + +describe('Project grouping on the phone', () => { + it.each(['recent', 'manual'] as const)( + "renders the desktop sidebar's %s headers from the catalog the desktop serves", + (projectOrderBy) => { + expect(rows()).toHaveLength(PROJECT_SECTION_PARITY_ROWS.length) + expect(headers(buildProjectSectionCatalog(served(projectOrderBy), [], null))).toEqual( + PROJECT_SECTION_PARITY_HEADERS[projectOrderBy] + ) + } + ) + + it("groups by repo from an older desktop's own repos when it serves no catalog", () => { + const desktopRepos = PROJECT_SECTION_PARITY_SERVED.repos.filter( + (repo) => repo.executionHostId === 'local' + ) + + expect(headers(buildProjectSectionCatalog(null, desktopRepos, null))).toEqual([ + { key: 'repo:m4-orca-two', label: 'orca' }, + { key: 'repo:win-orca', label: 'orca' }, + { key: 'repo:local-workspace', label: 'Users/jinwoo/orca/workspaces/orca/orca' }, + { key: 'repo:local-stably', label: 'Users/jinwoo/stably/orca' }, + { key: 'repo:m4-orca', label: 'orca' }, + { key: 'repo:local-assets', label: 'design-assets' } + ]) + }) + + it('drops a served setup whose host id this build cannot read, keeping the rest', () => { + const verdict = projectSectionsRead.interpret({ + id: 'sections', + ok: true, + result: { + catalog: { + ...PROJECT_SECTION_PARITY_SERVED, + setups: [ + { ...PROJECT_SECTION_PARITY_SERVED.setups[0], hostId: 'nonsense' }, + ...PROJECT_SECTION_PARITY_SERVED.setups.slice(1) + ] + } + } + }) + + expect(verdict.accepted && verdict.value?.setups).toHaveLength( + PROJECT_SECTION_PARITY_SERVED.setups.length - 1 + ) + }) +}) + +describe('readServedProjectSections', () => { + it('reads a served catalog, and a desktop with no published window as none', () => { + expect( + readServedProjectSections({ + id: 'sections', + ok: true, + result: { catalog: PROJECT_SECTION_PARITY_SERVED } + }) + ).toMatchObject({ kind: 'answered', value: { repos: expect.any(Array) } }) + expect( + readServedProjectSections({ id: 'sections', ok: true, result: { catalog: null } }) + ).toEqual({ kind: 'answered', value: null }) + }) + + it.each(['forbidden', 'method_not_found'])( + 'reads an older desktop that answers %s as serving none', + (code) => { + expect( + readServedProjectSections({ id: 'sections', ok: false, error: { code, message: code } }) + ).toEqual({ kind: 'answered', value: null }) + } + ) + + it('keeps the last catalog after a timeout or a dropped reply', () => { + expect( + readServedProjectSections({ + id: 'sections', + ok: false, + error: { code: 'timeout', message: 'timed out' } + }) + ).toEqual({ kind: 'kept' }) + expect(readServedProjectSections(null)).toEqual({ kind: 'kept' }) + }) +}) diff --git a/mobile/src/worktree/workspace-list-sections.test.ts b/mobile/src/worktree/workspace-list-sections.test.ts index f5dfe3f52c6..1d6c155776d 100644 --- a/mobile/src/worktree/workspace-list-sections.test.ts +++ b/mobile/src/worktree/workspace-list-sections.test.ts @@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest' import type { Worktree } from './workspace-list-sections' import { CREATE_GRACE_MS, + NO_PROJECT_SECTION_CATALOG, buildSections, filterWorktrees, getWorktreeStatus, @@ -32,8 +33,16 @@ function worktree(overrides: Partial = {}): Worktree { } } +// A catalog of [displayName, id] repos with no project rows, as an old desktop answers. +function catalogOf(repos: readonly (readonly [string, string])[]) { + return { + ...NO_PROJECT_SECTION_CATALOG, + repos: repos.map(([displayName, id]) => ({ id, displayName, path: `/repos/${id}` })) + } +} + function withoutSectionListKeys(sections: ReturnType) { - return sections.map((section) => ({ + return sections.map(({ repo: _repo, ...section }) => ({ ...section, data: section.data.map( ({ @@ -293,7 +302,7 @@ describe('buildSections', () => { '', 'workspaceStatus', new Set(), - new Map(), + NO_PROJECT_SECTION_CATALOG, DEFAULT_MOBILE_WORKSPACE_STATUSES ) @@ -339,7 +348,7 @@ describe('buildSections', () => { '', 'workspaceStatus', new Set(), - new Map(), + NO_PROJECT_SECTION_CATALOG, DEFAULT_MOBILE_WORKSPACE_STATUSES ) @@ -525,7 +534,7 @@ describe('buildSections', () => { '', 'repo', new Set(), - new Map([['orca', 'repo-1']]), + catalogOf([['orca', 'repo-1']]), DEFAULT_MOBILE_WORKSPACE_STATUSES ) @@ -556,7 +565,7 @@ describe('buildSections', () => { '', 'repo', new Set(), - new Map([['orca', 'repo-1']]) + catalogOf([['orca', 'repo-1']]) ) expect(sections[0]?.data.map((item) => item.worktreeId)).toEqual(['main', 'child']) @@ -570,7 +579,7 @@ describe('buildSections', () => { '', 'repo', new Set(), - new Map([ + catalogOf([ ['orca', 'repo-1'], ['zoom-img', 'repo-missing'] ]) @@ -594,7 +603,7 @@ describe('buildSections', () => { '', 'none', new Set(), - new Map([['zoom-img', 'repo-missing']]) + catalogOf([['zoom-img', 'repo-missing']]) ) expect(withoutSectionListKeys(sections)).toEqual([]) @@ -612,7 +621,7 @@ describe('buildSections', () => { 'zoom', 'repo', new Set(), - new Map([ + catalogOf([ ['zoom-img', 'repo-matching'], ['repo', 'repo-hidden'], ['zoom-hidden', 'repo-unfiltered'] @@ -637,7 +646,7 @@ describe('buildSections', () => { '', 'repo', new Set(), - new Map([ + catalogOf([ ['sleeping-repo', 'repo-sleeping'], ['empty-repo', 'repo-empty'] ]) @@ -667,7 +676,7 @@ describe('buildSections', () => { '', 'workspaceStatus', new Set(), - new Map(), + NO_PROJECT_SECTION_CATALOG, DEFAULT_MOBILE_WORKSPACE_STATUSES ) @@ -691,7 +700,7 @@ describe('buildSections', () => { '', 'workspaceStatus', new Set(), - new Map(), + NO_PROJECT_SECTION_CATALOG, [] ) @@ -720,7 +729,7 @@ describe('buildSections', () => { '', groupMode, new Set(['local-pinned']), - new Map(), + NO_PROJECT_SECTION_CATALOG, DEFAULT_MOBILE_WORKSPACE_STATUSES, new Set(), showPinnedInGroups @@ -759,7 +768,7 @@ describe('buildSections', () => { '', 'repo', new Set(), - new Map([['orca', 'repo-1']]) + catalogOf([['orca', 'repo-1']]) ) expect(sections.map((section) => section.key)).toEqual(['pinned']) @@ -786,7 +795,7 @@ describe('buildSections', () => { '', 'none', new Set(), - new Map(), + NO_PROJECT_SECTION_CATALOG, DEFAULT_MOBILE_WORKSPACE_STATUSES ) @@ -817,7 +826,7 @@ describe('buildSections', () => { '', 'workspaceStatus', new Set(), - new Map(), + NO_PROJECT_SECTION_CATALOG, DEFAULT_MOBILE_WORKSPACE_STATUSES ) @@ -845,7 +854,7 @@ describe('buildSections', () => { '', 'workspaceStatus', new Set(), - new Map(), + NO_PROJECT_SECTION_CATALOG, DEFAULT_MOBILE_WORKSPACE_STATUSES, new Set([getMobileWorkspaceLineageGroupKey(parent)]) ) diff --git a/mobile/src/worktree/workspace-list-sections.ts b/mobile/src/worktree/workspace-list-sections.ts index 9f3b480d4b3..ed53ac550ff 100644 --- a/mobile/src/worktree/workspace-list-sections.ts +++ b/mobile/src/worktree/workspace-list-sections.ts @@ -10,12 +10,57 @@ import { getMobileWorkspaceLineageChildren } from './mobile-workspace-lineage' import { getPRGroupKey, PR_GROUP_LABELS, PR_GROUP_ORDER } from './workspace-pr-status-groups' -import type { FilterState, Section, Worktree } from './workspace-list-types' +import type { FilterState, Section, SectionRepo, Worktree } from './workspace-list-types' import type { MobileGroupMode, MobileSortMode } from './workspace-view-settings' import { sortWorktrees } from './workspace-list-ordering' import { getWorktreeRowIdentity } from './worktree-host-row-identity' +import { + buildProjectGroupingIndex, + buildProjectSections, + type ProjectSectionModel +} from '../../../src/shared/project-section-grouping' +import { withRepoSectionDisplayLabels } from '../../../src/shared/project-section-order' +import type { ProjectOrderBy } from '../../../src/shared/ui-chrome-types' -export type { FilterState, Section, Worktree } from './workspace-list-types' +export type { FilterState, Section, SectionRepo, Worktree } from './workspace-list-types' + +/** What Project grouping reads beyond the rows: the desktop's all-host catalog and header order. */ +export type ProjectSectionCatalog = { + /** Catalog repos of the hosts the list shows; each without rows gets an empty section. */ + repos: readonly SectionRepo[] + /** Null until the desktop's catalog answers; rows then group by repo, as the desktop's would. */ + grouping: ProjectSectionModel | null + /** Manual header rank; undefined keeps first-row order. */ + repoOrder: ReadonlyMap | undefined + projectOrderBy: ProjectOrderBy +} + +export const NO_PROJECT_SECTION_CATALOG: ProjectSectionCatalog = { + repos: [], + grouping: null, + repoOrder: undefined, + projectOrderBy: 'manual' +} + +/** Catalog repos by id, plus a stand-in from the row for a repo its host's catalog lacks. */ +export function buildSectionRepoMap( + catalogRepos: readonly SectionRepo[], + worktrees: readonly Worktree[] +): Map { + const repoMap = new Map(catalogRepos.map((repo) => [repo.id, repo])) + for (const w of worktrees) { + if (!repoMap.has(w.repoId)) { + repoMap.set(w.repoId, { + id: w.repoId, + displayName: w.repo || 'Unknown', + // Why: labels key on host + path, so a shared '' would let one stand-in rename another. + path: w.repoId, + executionHostId: w.hostId + }) + } + } + return repoMap +} export { CREATE_GRACE_MS, getWorktreeStatus, sortWorktrees } from './workspace-list-ordering' function makeSection( @@ -23,13 +68,14 @@ function makeSection( title: string, data: Worktree[], collapsedGroups: ReadonlySet, - icon?: 'pin' + decoration: { icon?: 'pin'; repo?: SectionRepo } = {} ): Section { const rows = applyMobileWorkspaceLineage(data, collapsedGroups) return { key, title, - ...(icon ? { icon } : {}), + ...(decoration.icon ? { icon: decoration.icon } : {}), + ...(decoration.repo ? { repo: decoration.repo } : {}), data: rows.map((worktree) => ({ ...worktree, sectionListKey: `${key}:${getWorktreeRowIdentity(worktree)}` @@ -149,7 +195,7 @@ export function buildSections( search: string, groupMode: MobileGroupMode, pinnedIds: Set, - repoIdsByName: ReadonlyMap = new Map(), + projectCatalog: ProjectSectionCatalog = NO_PROJECT_SECTION_CATALOG, workspaceStatuses: readonly WorkspaceStatusDefinition[] = DEFAULT_MOBILE_WORKSPACE_STATUSES, collapsedGroups: ReadonlySet = new Set(), showPinnedInGroups = false @@ -164,7 +210,7 @@ export function buildSections( const sections: Section[] = [] if (pinned.length > 0) { - sections.push(makeSection('pinned', 'Pinned', pinned, collapsedGroups, 'pin')) + sections.push(makeSection('pinned', 'Pinned', pinned, collapsedGroups, { icon: 'pin' })) } if (groupMode === 'none') { @@ -172,35 +218,34 @@ export function buildSections( sections.push(makeSection('all', 'All', canonicalGroupWorktrees, collapsedGroups)) } } else if (groupMode === 'repo') { - const byRepo = new Map() - for (const w of canonicalGroupWorktrees) { - const key = w.repo || 'Unknown' - const list = byRepo.get(key) - if (list) { - list.push(w) - } else { - byRepo.set(key, [w]) - } - } const representedRepoIds = new Set(worktrees.map((w) => w.repoId)) const query = search.trim().toLowerCase() - for (const [displayName, id] of repoIdsByName) { - if (representedRepoIds.has(id)) { - continue - } - if (filters.filterRepoIds.size > 0 && !filters.filterRepoIds.has(id)) { - continue - } - if (query && !displayName.toLowerCase().includes(query)) { - continue - } - if (!byRepo.has(displayName)) { - byRepo.set(displayName, []) - } - } - for (const [repo, items] of byRepo) { - const key = `repo:${repoIdsByName.get(repo) ?? repo}` - sections.push(makeSection(key, repo, orderMainWorktreeFirst(items), collapsedGroups)) + const placeholderRepoIds = projectCatalog.repos + .filter( + (repo) => + !representedRepoIds.has(repo.id) && + (filters.filterRepoIds.size === 0 || filters.filterRepoIds.has(repo.id)) && + (!query || repo.displayName.toLowerCase().includes(query)) + ) + .map((repo) => repo.id) + // Why: the desktop sidebar's own grouping, so keys, labels and order match it. + const projectSections = withRepoSectionDisplayLabels( + buildProjectSections({ + items: canonicalGroupWorktrees, + repoIdOf: (w) => w.repoId, + repoMap: buildSectionRepoMap(projectCatalog.repos, worktrees), + projectIndex: buildProjectGroupingIndex(projectCatalog.grouping ?? undefined), + placeholderRepoIds, + repoOrder: projectCatalog.repoOrder, + projectOrderBy: projectCatalog.projectOrderBy + }) + ) + for (const [key, section] of projectSections) { + sections.push( + makeSection(key, section.label, orderMainWorktreeFirst(section.items), collapsedGroups, { + repo: section.repo + }) + ) } } else if (groupMode === 'workspaceStatus') { const renderableWorkspaceStatuses = coerceMobileWorkspaceStatuses(workspaceStatuses) diff --git a/mobile/src/worktree/workspace-list-types.ts b/mobile/src/worktree/workspace-list-types.ts index 7d9061ec2ca..0f6c499792b 100644 --- a/mobile/src/worktree/workspace-list-types.ts +++ b/mobile/src/worktree/workspace-list-types.ts @@ -1,6 +1,7 @@ import type { ExecutionHostId } from '../../../src/shared/execution-host' import type { AgentWorkingMode } from '../../../src/shared/agent-status-types' import type { RuntimeWorktreeAgentRow } from '../../../src/shared/runtime-types' +import type { ProjectSectionRepo } from '../../../src/shared/project-section-grouping' export type Worktree = { sectionListKey?: string @@ -65,4 +66,15 @@ export type FilterState = { alwaysShowDefaultBranch?: boolean } -export type Section = { key: string; title: string; icon?: 'pin'; data: Worktree[] } +/** A repo as Project sections read it: the desktop's catalog record, or one a row stands in for. */ +// repoIcon stays unread wire data until drawn (readMobileRepoIcon). +export type SectionRepo = ProjectSectionRepo & { badgeColor?: string; repoIcon?: unknown } + +export type Section = { + key: string + title: string + icon?: 'pin' + /** Under Project grouping, the repo whose colour and icon the header shows, as on the desktop. */ + repo?: SectionRepo + data: Worktree[] +} diff --git a/mobile/src/worktree/worktree-list-completeness.test.ts b/mobile/src/worktree/worktree-list-completeness.test.ts index dc4d97231d1..4ffd23bcda4 100644 --- a/mobile/src/worktree/worktree-list-completeness.test.ts +++ b/mobile/src/worktree/worktree-list-completeness.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { buildSections, type Worktree } from './workspace-list-sections' +import { NO_PROJECT_SECTION_CATALOG, buildSections, type Worktree } from './workspace-list-sections' import { applyDesktopViewSettings, type MobileViewState, @@ -47,11 +47,11 @@ const base: MobileViewState = { } // Reconstruct the list the host screen renders: apply the synced desktop -// settings, then build sections exactly as the screen does. `repoIdsByName` is -// the display-name-keyed map the screen builds from repo.list. +// settings, then build sections exactly as the screen does. `catalogRepos` are +// [displayName, id] rows of the repo.list catalog the screen groups by. function visibleWorktreeIds( worktrees: Worktree[], - repoIdsByName: Map, + catalogRepos: readonly (readonly [string, string])[], desktop: WorkspaceViewSettings = {} ): string[] { const state = applyDesktopViewSettings(base, desktop) @@ -66,7 +66,10 @@ function visibleWorktreeIds( '', state.groupMode, new Set(), - repoIdsByName, + { + ...NO_PROJECT_SECTION_CATALOG, + repos: catalogRepos.map(([displayName, id]) => ({ id, displayName, path: `/repos/${id}` })) + }, state.workspaceStatuses, new Set(state.collapsedGroups) ) @@ -81,9 +84,9 @@ describe('every worktree stays visible regardless of repo.list contents', () => worktree('orca-local', 'b'), worktree('orca-runtime', 'c') ] - const repoIdsByName = new Map([['orca-runtime', 'orca-runtime']]) + const catalogRepos = [['orca-runtime', 'orca-runtime']] as const - expect(visibleWorktreeIds(worktrees, repoIdsByName)).toEqual(['a', 'b', 'c']) + expect(visibleWorktreeIds(worktrees, catalogRepos)).toEqual(['a', 'b', 'c']) }) it('shows every worktree when the same repo name maps to two host-specific ids', () => { @@ -94,29 +97,26 @@ describe('every worktree stays visible regardless of repo.list contents', () => worktree('orca-local', 'local-2', 'orca'), worktree('orca-runtime', 'runtime-1', 'orca') ] - // Name-keyed map collapses "orca" to a single id, as the host screen builds it. - const repoIdsByName = new Map([['orca', 'orca-runtime']]) + // The catalog names only one of the two "orca" repos. + const catalogRepos = [['orca', 'orca-runtime']] as const - expect(visibleWorktreeIds(worktrees, repoIdsByName)).toEqual([ - 'local-1', - 'local-2', - 'runtime-1' - ]) + expect(visibleWorktreeIds(worktrees, catalogRepos)).toEqual(['local-1', 'local-2', 'runtime-1']) }) it('shows every worktree even when the desktop carries a host scope', () => { // Host scope must never reach the mobile list either (it has no mobile UI). const worktrees = [worktree('local-repo', 'x'), worktree('runtime-repo', 'y')] - const repoIdsByName = new Map([ + const catalogRepos = [ ['local-repo', 'local-repo'], ['runtime-repo', 'runtime-repo'] - ]) + ] as const - expect( - visibleWorktreeIds(worktrees, repoIdsByName, { - workspaceHostScope: 'runtime:devbox', - visibleWorkspaceHostIds: ['runtime:devbox'] - } as unknown as WorkspaceViewSettings) - ).toEqual(['x', 'y']) + // A variable, not a literal: `workspaceHostScope` is not a member the phone reads. + const desktopWithHostScope = { + workspaceHostScope: 'runtime:devbox', + visibleWorkspaceHostIds: ['runtime:devbox'] + } + + expect(visibleWorktreeIds(worktrees, catalogRepos, desktopWithHostScope)).toEqual(['x', 'y']) }) }) diff --git a/src/main/ipc/runtime.test.ts b/src/main/ipc/runtime.test.ts index b2d0d0fbb53..6e2df76c67b 100644 --- a/src/main/ipc/runtime.test.ts +++ b/src/main/ipc/runtime.test.ts @@ -23,6 +23,8 @@ vi.mock('electron', () => ({ import { registerRuntimeHandlers } from './runtime' import { TERMINAL_FIT_RESTORE_DEADLINE_MS } from '../../shared/terminal-fit-restore-deadline' +import { PROJECT_SECTION_PARITY_SERVED } from '../../shared/project-section-parity.test-fixture' +import { readMobileProjectSections } from '../runtime/mobile-project-sections-snapshot' function runtimeCallEvent() { const mainFrame = {} @@ -72,6 +74,46 @@ describe('registerRuntimeHandlers', () => { expect(result).toEqual({ graphStatus: 'ready' }) }) + it("serves the authoritative window's sidebar catalog to paired phones, and keeps it when omitted", () => { + const runtime = { syncWindowGraph: vi.fn().mockReturnValue({ graphStatus: 'ready' }) } + registerRuntimeHandlers(runtime as never) + const handler = handleMock.mock.calls.find( + ([channel]) => channel === 'runtime:syncWindowGraph' + )![1] + const currentMainFrame = {} + const event = { sender: { mainFrame: currentMainFrame }, senderFrame: currentMainFrame } + fromWebContentsMock.mockReturnValue({ id: 17 }) + const graph = { tabs: [], leaves: [], rendererGeneration: 'renderer-1' } + + handler(event, { ...graph, projectSectionCatalog: PROJECT_SECTION_PARITY_SERVED }) + handler(event, graph) + + expect(readMobileProjectSections()).toEqual({ catalog: PROJECT_SECTION_PARITY_SERVED }) + }) + + it('does not serve a catalog from a graph the runtime refused', () => { + const refused = { ...PROJECT_SECTION_PARITY_SERVED, projectOrderBy: 'recent' as const } + const runtime = { + syncWindowGraph: vi.fn(() => { + throw new Error('Runtime graph publisher does not match the authoritative window') + }) + } + registerRuntimeHandlers(runtime as never) + const handler = handleMock.mock.calls.find( + ([channel]) => channel === 'runtime:syncWindowGraph' + )![1] + const currentMainFrame = {} + fromWebContentsMock.mockReturnValue({ id: 18 }) + + expect(() => + handler( + { sender: { mainFrame: currentMainFrame }, senderFrame: currentMainFrame }, + { tabs: [], leaves: [], rendererGeneration: 'renderer-2', projectSectionCatalog: refused } + ) + ).toThrow() + expect(readMobileProjectSections().catalog).not.toEqual(refused) + }) + it('rejects a graph publication queued by a superseded main frame', () => { const runtime = { syncWindowGraph: vi.fn(), diff --git a/src/main/ipc/runtime.ts b/src/main/ipc/runtime.ts index 1736cbc6f60..45381da0e0b 100644 --- a/src/main/ipc/runtime.ts +++ b/src/main/ipc/runtime.ts @@ -15,6 +15,7 @@ import { RpcDispatcher } from '../runtime/rpc/dispatcher' import { DESKTOP_RPC_CALLER } from '../runtime/rpc/rpc-caller-identity' import { ALL_RPC_METHODS } from '../runtime/rpc/methods' import { DesktopRuntimeSenderLifecycle } from './desktop-runtime-sender-lifecycle' +import { publishMobileProjectSections } from '../runtime/mobile-project-sections-snapshot' function boundTerminalFitRestore(pending: Promise): Promise { let timer: ReturnType | undefined @@ -49,7 +50,12 @@ export function registerRuntimeHandlers(runtime: OrcaRuntimeService): void { if (typeof graph.rendererGeneration !== 'string' || graph.rendererGeneration.length === 0) { throw new Error('Runtime graph sync requires a renderer generation') } - return runtime.syncWindowGraph(window.id, graph) + const result = runtime.syncWindowGraph(window.id, graph) + // Why after the sync: only the authoritative window's graph is accepted, so only its catalog is. + if (graph.projectSectionCatalog) { + publishMobileProjectSections(graph.projectSectionCatalog) + } + return result } ) diff --git a/src/main/runtime/mobile-project-sections-snapshot.ts b/src/main/runtime/mobile-project-sections-snapshot.ts new file mode 100644 index 00000000000..5c406f505f1 --- /dev/null +++ b/src/main/runtime/mobile-project-sections-snapshot.ts @@ -0,0 +1,16 @@ +import type { + MobileProjectSectionCatalog, + MobileProjectSectionsResult +} from '../../shared/mobile-project-sections-contract' + +// Why kept after the window closes: a stale sidebar catalog groups better than none. +let latestCatalog: MobileProjectSectionCatalog | null = null + +/** The authoritative desktop window's sidebar catalog, as its runtime graph last carried it. */ +export function publishMobileProjectSections(catalog: MobileProjectSectionCatalog): void { + latestCatalog = catalog +} + +export function readMobileProjectSections(): MobileProjectSectionsResult { + return { catalog: latestCatalog } +} diff --git a/src/main/runtime/rpc/methods/index.ts b/src/main/runtime/rpc/methods/index.ts index db3786c3e2f..f0dca1d168b 100644 --- a/src/main/runtime/rpc/methods/index.ts +++ b/src/main/runtime/rpc/methods/index.ts @@ -49,6 +49,7 @@ import { RUNTIME_CLIENT_CAPABILITY_METHODS } from './runtime-client-capabilities import { EMULATOR_METHODS } from './emulator' import { PAIRING_METHODS } from './pairing' import { MOBILE_RELAY_HOSTS_METHODS } from './mobile-relay-hosts' +import { MOBILE_PROJECT_SECTIONS_METHODS } from './mobile-project-sections' import { UPDATER_METHODS } from './updater' import { AUTOMATIC_WORK_METHODS } from './automatic-work' import { AGENT_SESSION_METHODS } from './agent-session' @@ -125,6 +126,7 @@ export const ALL_RPC_METHODS = [ ...EMULATOR_METHODS, ...PAIRING_METHODS, ...MOBILE_RELAY_HOSTS_METHODS, + ...MOBILE_PROJECT_SECTIONS_METHODS, ...UPDATER_METHODS, ...AUTOMATIC_WORK_METHODS ] diff --git a/src/main/runtime/rpc/methods/mobile-project-sections.test.ts b/src/main/runtime/rpc/methods/mobile-project-sections.test.ts new file mode 100644 index 00000000000..22835c6068a --- /dev/null +++ b/src/main/runtime/rpc/methods/mobile-project-sections.test.ts @@ -0,0 +1,47 @@ +import '../unused-default-rpc-methods.test-fixture' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { RpcDispatcher } from '../dispatcher' +import type { OrcaRuntimeService } from '../../orca-runtime' +import { MOBILE_PROJECT_SECTIONS_METHOD } from '../../../../shared/mobile-project-sections-contract' +import { PROJECT_SECTION_PARITY_SERVED } from '../../../../shared/project-section-parity.test-fixture' + +// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the method reads no runtime member; the dispatcher only stamps the runtime id. +const runtime = { getRuntimeId: () => 'test-runtime' } as unknown as OrcaRuntimeService + +// Why fresh modules: the snapshot is process state, so each test starts with nothing published. +beforeEach(() => { + vi.resetModules() +}) + +async function sectionsDispatcher(): Promise { + const { MOBILE_PROJECT_SECTIONS_METHODS } = await import('./mobile-project-sections') + return new RpcDispatcher({ runtime, methods: MOBILE_PROJECT_SECTIONS_METHODS }) +} + +describe(MOBILE_PROJECT_SECTIONS_METHOD, () => { + it('answers no catalog before a desktop window has published one', async () => { + const response = await ( + await sectionsDispatcher() + ).dispatch({ + id: 'req-1', + authToken: 'tok', + method: MOBILE_PROJECT_SECTIONS_METHOD + }) + + expect(response).toMatchObject({ ok: true, result: { catalog: null } }) + }) + + it("answers the window's last published catalog", async () => { + const dispatcher = await sectionsDispatcher() + const { publishMobileProjectSections } = await import('../../mobile-project-sections-snapshot') + publishMobileProjectSections(PROJECT_SECTION_PARITY_SERVED) + + const response = await dispatcher.dispatch({ + id: 'req-2', + authToken: 'tok', + method: MOBILE_PROJECT_SECTIONS_METHOD + }) + + expect(response).toMatchObject({ ok: true, result: { catalog: PROJECT_SECTION_PARITY_SERVED } }) + }) +}) diff --git a/src/main/runtime/rpc/methods/mobile-project-sections.ts b/src/main/runtime/rpc/methods/mobile-project-sections.ts new file mode 100644 index 00000000000..617ab54a768 --- /dev/null +++ b/src/main/runtime/rpc/methods/mobile-project-sections.ts @@ -0,0 +1,12 @@ +import { defineMethod } from '../core' +import { MOBILE_PROJECT_SECTIONS_METHOD } from '../../../../shared/mobile-project-sections-contract' +import { readMobileProjectSections } from '../../mobile-project-sections-snapshot' + +export const MOBILE_PROJECT_SECTIONS_METHODS = [ + defineMethod({ + name: MOBILE_PROJECT_SECTIONS_METHOD, + permission: 'workspace', + params: null, + handler: () => readMobileProjectSections() + }) +] diff --git a/src/main/runtime/runtime-rpc/runtime-rpc-mobile-paired-desktop-methods.ts b/src/main/runtime/runtime-rpc/runtime-rpc-mobile-paired-desktop-methods.ts index b175513b672..eab6f380498 100644 --- a/src/main/runtime/runtime-rpc/runtime-rpc-mobile-paired-desktop-methods.ts +++ b/src/main/runtime/runtime-rpc/runtime-rpc-mobile-paired-desktop-methods.ts @@ -16,6 +16,8 @@ export const PAIRED_DESKTOP_METHODS = [ // The configured servers the desktop shows, and their workspace lists as it last fetched them. 'mobileRelay.hosts.list', 'mobileRelay.hosts.worktrees', + // The sidebar's Project grouping inputs, as the desktop window last published them. + 'mobileRelay.projectSections', 'speech.dictation.cancel', 'speech.dictation.chunk', 'speech.dictation.finish', diff --git a/src/renderer/src/app-shell/use-runtime-graph-sync.ts b/src/renderer/src/app-shell/use-runtime-graph-sync.ts index 96b7b56d59c..8ee1b8f67be 100644 --- a/src/renderer/src/app-shell/use-runtime-graph-sync.ts +++ b/src/renderer/src/app-shell/use-runtime-graph-sync.ts @@ -51,6 +51,22 @@ export function useRuntimeGraphSync(): void { }) }, []) + useEffect( + () => + useAppStore.subscribe((state, previousState) => { + // Why: the graph carries the sidebar's Project catalog to paired phones; republish on change. + if ( + state.repos !== previousState.repos || + state.projects !== previousState.projects || + state.projectHostSetups !== previousState.projectHostSetups || + state.projectOrderBy !== previousState.projectOrderBy + ) { + scheduleRuntimeGraphSync() + } + }), + [] + ) + useEffect(() => { setRuntimeGraphSyncEnabled(workspaceSessionReady) return () => { diff --git a/src/renderer/src/components/cmd-j/palette-filter-options.ts b/src/renderer/src/components/cmd-j/palette-filter-options.ts index 21a06b8a24a..4cc74bf66f7 100644 --- a/src/renderer/src/components/cmd-j/palette-filter-options.ts +++ b/src/renderer/src/components/cmd-j/palette-filter-options.ts @@ -1,4 +1,7 @@ -import { getRepoDisplayLabelKey, getRepoDisplayLabelsByPath } from '@/lib/repo-display-labels' +import { + getRepoDisplayLabelKey, + getRepoDisplayLabelsByPath +} from '../../../../shared/repo-display-labels' import { getRepoExecutionHostId, getWorktreeExecutionHostId, @@ -8,8 +11,8 @@ import { import type { Project, ProjectHostSetup } from '../../../../shared/project-types' import type { Repo } from '../../../../shared/repo-types' import type { Worktree } from '../../../../shared/worktree/types' -import { getProjectHeaderRevealTarget } from '../sidebar/worktree-list/grouping/project-grouping' -import type { ProjectGroupingModel } from '../sidebar/worktree-list/grouping/project-grouping' +import { getProjectHeaderRevealTarget } from '../../../../shared/project-section-grouping' +import type { ProjectGroupingModel } from '../../../../shared/project-section-grouping' import type { SidebarHostOption } from '../sidebar/sidebar-host-options' import { expandEquivalentExecutionHostIds, diff --git a/src/renderer/src/components/cmd-j/palette-project-results.ts b/src/renderer/src/components/cmd-j/palette-project-results.ts index c403607e48f..e4581e11659 100644 --- a/src/renderer/src/components/cmd-j/palette-project-results.ts +++ b/src/renderer/src/components/cmd-j/palette-project-results.ts @@ -12,8 +12,8 @@ import type { Project, ProjectHostSetup } from '../../../../shared/project-types import type { Repo } from '../../../../shared/repo-types' import { translate } from '@/i18n/i18n' import { getProjectGroupHeaderKey } from '../sidebar/worktree-list/grouping/group-keys' -import { getProjectHeaderRevealTarget } from '../sidebar/worktree-list/grouping/project-grouping' -import type { ProjectGroupingModel } from '../sidebar/worktree-list/grouping/project-grouping' +import { getProjectHeaderRevealTarget } from '../../../../shared/project-section-grouping' +import type { ProjectGroupingModel } from '../../../../shared/project-section-grouping' export type CmdJProjectGroupResult = { id: string diff --git a/src/renderer/src/components/repo/NestedRepoChecklist.tsx b/src/renderer/src/components/repo/NestedRepoChecklist.tsx index 77bb3378900..35e6f062a47 100644 --- a/src/renderer/src/components/repo/NestedRepoChecklist.tsx +++ b/src/renderer/src/components/repo/NestedRepoChecklist.tsx @@ -2,7 +2,10 @@ import { useCallback, useMemo, type Dispatch, type SetStateAction } from 'react' import { GitBranch } from 'lucide-react' import type { NestedRepoScanResult } from '../../../../shared/project-group-types' import { cn } from '@/lib/utils' -import { getRepoDisplayLabelKey, getRepoDisplayLabelsByPath } from '@/lib/repo-display-labels' +import { + getRepoDisplayLabelKey, + getRepoDisplayLabelsByPath +} from '../../../../shared/repo-display-labels' import { translate } from '@/i18n/i18n' function NestedRepoSelectAllRow({ diff --git a/src/renderer/src/components/settings/settings-project-list.ts b/src/renderer/src/components/settings/settings-project-list.ts index 9d2bc449abc..17a2b64fdc7 100644 --- a/src/renderer/src/components/settings/settings-project-list.ts +++ b/src/renderer/src/components/settings/settings-project-list.ts @@ -12,7 +12,7 @@ import { buildProjectGroupingIndex, isCheckoutScopedProjectSetup, type ProjectGroupingModel -} from '@/components/sidebar/worktree-list/grouping/project-grouping' +} from '../../../../shared/project-section-grouping' export type SettingsProject = { projectId: string diff --git a/src/renderer/src/components/settings/settings-project-regrouping.test.ts b/src/renderer/src/components/settings/settings-project-regrouping.test.ts index b583222090b..e2557dc0779 100644 --- a/src/renderer/src/components/settings/settings-project-regrouping.test.ts +++ b/src/renderer/src/components/settings/settings-project-regrouping.test.ts @@ -5,7 +5,7 @@ import { createUIStore } from '../../store/slices/ui-slice-test-harness' import { buildProjectGroupingIndex, getProjectGroupingForRepo -} from '../sidebar/worktree-list/grouping/project-grouping' +} from '../../../../shared/project-section-grouping' import { buildSettingsProjectList, getSettingsEntryHostSelection, diff --git a/src/renderer/src/components/sidebar/project-header-drag-commit.ts b/src/renderer/src/components/sidebar/project-header-drag-commit.ts index 0a89742097b..5786391c253 100644 --- a/src/renderer/src/components/sidebar/project-header-drag-commit.ts +++ b/src/renderer/src/components/sidebar/project-header-drag-commit.ts @@ -1,6 +1,6 @@ +import { getLogicalRepoOrderRankById } from '../../../../shared/project-section-order' import { applyAllRepoInsertAt, - getLogicalRepoOrderRankById, getProjectGroupOrderForSidebarDrop, mapSidebarProjectHeaderDropIndexToSiblingInsertIndex, mapSidebarRepoDropIndexToAllRepoInsertAt diff --git a/src/renderer/src/components/sidebar/project-header-drop.test.ts b/src/renderer/src/components/sidebar/project-header-drop.test.ts index f5ee78e81e5..40e10f61e9d 100644 --- a/src/renderer/src/components/sidebar/project-header-drop.test.ts +++ b/src/renderer/src/components/sidebar/project-header-drop.test.ts @@ -1,10 +1,10 @@ // @vitest-environment happy-dom import { describe, expect, it } from 'vitest' +import { getLogicalRepoOrderRankById } from '../../../../shared/project-section-order' import { applyAllRepoInsertAt, computeProjectHeaderDropPreview, - getLogicalRepoOrderRankById, getProjectGroupOrderForSidebarDrop, getProjectHeaderDragBucketKey, getSidebarOrderedRepoHeaderIdsByBucket, diff --git a/src/renderer/src/components/sidebar/project-header-drop.ts b/src/renderer/src/components/sidebar/project-header-drop.ts index 4cc23ddd9c6..171aa8261c8 100644 --- a/src/renderer/src/components/sidebar/project-header-drop.ts +++ b/src/renderer/src/components/sidebar/project-header-drop.ts @@ -44,20 +44,6 @@ export function getSidebarOrderedRepoHeaderIdsByBucket( return buckets } -export function getLogicalRepoOrderRankById( - orderedRepoIds: readonly string[] -): Map { - const rankById = new Map() - orderedRepoIds.forEach((repoId, index) => { - // Why: paired hosts can contribute the same logical project ID; its merged - // header must anchor to the first occurrence instead of whichever host loaded last. - if (!rankById.has(repoId)) { - rankById.set(repoId, index) - } - }) - return rankById -} - export function getProjectGroupOrderForSidebarDrop(args: { siblings: readonly Repo[] dropIndex: number diff --git a/src/renderer/src/components/sidebar/rendered-sidebar-worktree-order.ts b/src/renderer/src/components/sidebar/rendered-sidebar-worktree-order.ts index 290f44e2ef6..2d7b680a019 100644 --- a/src/renderer/src/components/sidebar/rendered-sidebar-worktree-order.ts +++ b/src/renderer/src/components/sidebar/rendered-sidebar-worktree-order.ts @@ -12,7 +12,7 @@ import { getPinnedWorktreeDisplayPolicy } from './worktree-list/grouping/row-typ import { addHostSectionRows, type HostSectionRow } from './host-section-rows' import { orderHostSectionOptions } from './host-section-order' import { buildSidebarHostOptions } from './sidebar-host-options' -import { getLogicalRepoOrderRankById } from './project-header-drop' +import { getLogicalRepoOrderRankById } from '../../../../shared/project-section-order' import { getRenderedWorktreesInSidebarOrder } from './worktree-sidebar-row-preference' import { selectWorktreeListReviewCacheInputs } from './worktree-list/listing/review-cache-inputs' import { diff --git a/src/renderer/src/components/sidebar/worktree-list-groups-mobile-parity.test.ts b/src/renderer/src/components/sidebar/worktree-list-groups-mobile-parity.test.ts new file mode 100644 index 00000000000..251236173bc --- /dev/null +++ b/src/renderer/src/components/sidebar/worktree-list-groups-mobile-parity.test.ts @@ -0,0 +1,195 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { applyManualRepoOrder } from '../../../../shared/manual-repo-order' +import { mergeFetchedHostProjectCatalog } from '../../../../shared/project-compatibility-host-merge' +import type { ProjectCatalogRows } from '../../../../shared/project-compatibility-merge' +import { + buildProjectGroupingIndex, + buildProjectSections +} from '../../../../shared/project-section-grouping' +import { + getLogicalRepoOrderRankById, + withRepoSectionDisplayLabels +} from '../../../../shared/project-section-order' +import { + PROJECT_SECTION_PARITY_HEADERS, + PROJECT_SECTION_PARITY_HOSTS, + PROJECT_SECTION_PARITY_ROWS, + PROJECT_SECTION_PARITY_SERVED +} from '../../../../shared/project-section-parity.test-fixture' +import type { Repo } from '../../../../shared/repo-types' +import type { RuntimeClientTarget } from '../../../../shared/runtime-client-target' +import type { ProjectOrderBy } from '../../../../shared/ui-chrome-types' +import type { Worktree } from '../../../../shared/worktree/types' +import { + fetchRepoCatalogForTarget, + filterSetupsForPrunedRepoRows, + mergeFetchedRepoCatalog, + reconcileSupersededSshRepos +} from '../../store/repos/repo-catalog-merge' +import { getProjectHostSetupProjectionFromState } from '../../store/project-host-setup-selector' +import { buildRows } from './worktree-list/grouping/build-rows' +import { worktree as worktreeFixture } from './worktree-list-groups-test-fixtures' +import type { MobileProjectSectionCatalog } from '../../../../shared/mobile-project-sections-contract' +import { getProjectSectionCatalogFromState } from '../../runtime/sync-runtime-graph/project-section-catalog' + +// Why: the phone's Project sections must be the desktop sidebar's by construction. The desktop +// half answers the renderer's own catalog fetch and runs its merge, selector, repo order and +// buildRows; the phone half groups what main serves from the renderer's published catalog, and +// mobile/src/worktree/workspace-list-sections-desktop-parity.test.ts runs buildSections on it. + +const { rpc, assertCapability } = vi.hoisted(() => ({ rpc: vi.fn(), assertCapability: vi.fn() })) +vi.mock('../../runtime/runtime-rpc-client', () => ({ + callRuntimeRpc: rpc, + assertRuntimeEnvironmentCapability: assertCapability +})) + +function hostFor(target: RuntimeClientTarget) { + const host = PROJECT_SECTION_PARITY_HOSTS.find((entry) => + target.kind === 'local' + ? entry.target.kind === 'local' + : entry.target.kind === 'environment' && entry.target.environmentId === target.environmentId + ) + if (!host) { + throw new Error('unknown parity host') + } + return host +} + +function answerHosts(): void { + const local = hostFor({ kind: 'local' }) + vi.stubGlobal('window', { + api: { + repos: { list: async () => local.repos }, + projects: { + list: async () => local.lists?.projects ?? [], + listHostSetups: async () => local.lists?.setups ?? [] + } + } + }) + rpc.mockImplementation(async (target: RuntimeClientTarget, method: string) => { + const host = hostFor(target) + if (method === 'repo.list') { + return { repos: host.repos } + } + if (!host.lists) { + throw new Error('method_not_found') + } + return method === 'project.list' + ? { projects: host.lists.projects } + : { setups: host.lists.setups } + }) +} + +async function desktopCatalog(): Promise<{ repos: readonly Repo[]; rows: ProjectCatalogRows }> { + answerHosts() + // The all-host load's applyCatalog, host by host: local first, then each server. + let repos: readonly Repo[] = [] + let rows: ProjectCatalogRows = { projects: [], projectHostSetups: [] } + for (const host of PROJECT_SECTION_PARITY_HOSTS) { + const catalog = await fetchRepoCatalogForTarget(host.target) + const result = mergeFetchedRepoCatalog(catalog, repos) + const reconciled = reconcileSupersededSshRepos(result.repos, { pendingSshRepoReadoptions: [] }) + const finalized = applyManualRepoOrder(reconciled.repos, []) + rows = mergeFetchedHostProjectCatalog({ + previous: { + projects: rows.projects, + projectHostSetups: filterSetupsForPrunedRepoRows( + rows.projectHostSetups, + result.repos, + finalized + ) + }, + fetched: catalog.projectHostSetupCompatibility, + repos: finalized, + hostId: result.hostId + }) + repos = finalized + } + return { repos, rows } +} + +async function desktopHeaders(projectOrderBy: ProjectOrderBy) { + const { repos, rows } = await desktopCatalog() + const projection = getProjectHostSetupProjectionFromState({ repos, ...rows }) + const worktrees: Worktree[] = PROJECT_SECTION_PARITY_ROWS.map((row) => ({ + ...worktreeFixture, + id: row.id, + repoId: row.repoId, + lastActivityAt: row.lastActivityAt + })) + const withWorktrees = new Set(PROJECT_SECTION_PARITY_ROWS.map((row) => row.repoId)) + return buildRows( + 'repo', + worktrees, + new Map(repos.map((repo) => [repo.id, repo])), + null, + new Set(), + // As use-section-rows ranks it: the order of state.repos. + getLogicalRepoOrderRankById(repos.map((repo) => repo.id)), + undefined, + projectOrderBy, + {}, + undefined, + false, + undefined, + [], + new Set(repos.filter((repo) => !withWorktrees.has(repo.id)).map((repo) => repo.id)), + new Map(), + new Map(), + [], + { projects: projection.projects, projectHostSetups: projection.setups } + ).flatMap((row) => (row.type === 'header' ? [{ key: row.key, label: row.label }] : [])) +} + +/** + * What main serves the phone: the renderer's published catalog as JSON crosses the wire. Main + * stores and answers it unchanged (src/main/ipc/runtime.test.ts, mobile-project-sections.test.ts). + */ +async function servedCatalog(projectOrderBy: ProjectOrderBy): Promise { + const { repos, rows } = await desktopCatalog() + const published = getProjectSectionCatalogFromState({ repos, ...rows, projectOrderBy }) + if (!published) { + throw new Error('the renderer published no catalog') + } + return JSON.parse(JSON.stringify(published)) +} + +/** The phone's grouping over a served catalog (mobile/src/worktree/project-section-catalog.ts). */ +function phoneHeaders(served: MobileProjectSectionCatalog) { + const withRows = new Set(PROJECT_SECTION_PARITY_ROWS.map((row) => row.repoId)) + return withRepoSectionDisplayLabels( + buildProjectSections({ + items: PROJECT_SECTION_PARITY_ROWS, + repoIdOf: (row) => row.repoId, + repoMap: new Map(served.repos.map((repo) => [repo.id, repo])), + projectIndex: buildProjectGroupingIndex({ + projects: served.projects, + projectHostSetups: served.setups + }), + placeholderRepoIds: served.repos + .filter((repo) => !withRows.has(repo.id)) + .map((repo) => repo.id), + repoOrder: getLogicalRepoOrderRankById(served.repos.map((repo) => repo.id)), + projectOrderBy: served.projectOrderBy + }) + ).map(([key, section]) => ({ key, label: section.label })) +} + +afterEach(() => { + vi.unstubAllGlobals() + vi.resetAllMocks() +}) + +describe('the phone groups by Project exactly as the paired desktop sidebar', () => { + it.each(['recent', 'manual'] as const)( + 'gives checkouts of one project on local and servers the same %s headers', + async (projectOrderBy) => { + expect(await desktopHeaders(projectOrderBy)).toEqual( + PROJECT_SECTION_PARITY_HEADERS[projectOrderBy] + ) + const served = await servedCatalog(projectOrderBy) + expect(served).toEqual({ ...PROJECT_SECTION_PARITY_SERVED, projectOrderBy }) + expect(phoneHeaders(served)).toEqual(PROJECT_SECTION_PARITY_HEADERS[projectOrderBy]) + } + ) +}) diff --git a/src/renderer/src/components/sidebar/worktree-list-groups-notice-host-labels.test.ts b/src/renderer/src/components/sidebar/worktree-list-groups-notice-host-labels.test.ts index 8bbf99f9260..54d86f00fdb 100644 --- a/src/renderer/src/components/sidebar/worktree-list-groups-notice-host-labels.test.ts +++ b/src/renderer/src/components/sidebar/worktree-list-groups-notice-host-labels.test.ts @@ -12,7 +12,7 @@ import { describe, expect, it } from 'vitest' import { buildRows } from './worktree-list/grouping/build-rows' import { getNoticeHostContextLabels } from './worktree-list/grouping/host-labels' -import { buildProjectGroupingIndex } from './worktree-list/grouping/project-grouping' +import { buildProjectGroupingIndex } from '../../../../shared/project-section-grouping' import { repo, worktree, project, projectHostSetups } from './worktree-list-groups-test-fixtures' import type { ExecutionHostId } from '../../../../shared/execution-host' import type { ProjectHostSetup } from '../../../../shared/project-types' diff --git a/src/renderer/src/components/sidebar/worktree-list/grouping/build-rows.ts b/src/renderer/src/components/sidebar/worktree-list/grouping/build-rows.ts index 7a3041ce078..03a3cd4dce5 100644 --- a/src/renderer/src/components/sidebar/worktree-list/grouping/build-rows.ts +++ b/src/renderer/src/components/sidebar/worktree-list/grouping/build-rows.ts @@ -19,8 +19,8 @@ import { getMixedWorktreeHostContextLabels, getNoticeHostContextLabels } from './host-labels' -import { buildProjectGroupingIndex } from './project-grouping' -import type { ProjectGroupingModel } from './project-grouping' +import { buildProjectGroupingIndex } from '../../../../../../shared/project-section-grouping' +import type { ProjectGroupingModel } from '../../../../../../shared/project-section-grouping' import { appendProjectGroupSections } from './project-group-sections' import { getPinnedSectionWorktrees } from '../../pinned-section-worktrees' import { emitPinnedGroup } from './pinned-group-rows' @@ -42,7 +42,8 @@ import type { Row, WorktreeGroupBy } from './row-types' -import { getRenderedNaturalAnchorRepoIds, withRepoSectionDisplayLabels } from './section-order' +import { getRenderedNaturalAnchorRepoIds } from './section-order' +import { withRepoSectionDisplayLabels } from '../../../../../../shared/project-section-order' import { buildOrderedGroups } from './worktree-grouping' export function buildRows( diff --git a/src/renderer/src/components/sidebar/worktree-list/grouping/group-sections.ts b/src/renderer/src/components/sidebar/worktree-list/grouping/group-sections.ts index bbba41b609b..607ab0e593a 100644 --- a/src/renderer/src/components/sidebar/worktree-list/grouping/group-sections.ts +++ b/src/renderer/src/components/sidebar/worktree-list/grouping/group-sections.ts @@ -14,7 +14,8 @@ import { getLaneHostWorktreeIds, getMixedHostContextLabels } from './host-labels' -import type { OrderedGroupEntry, ProjectGroupingIndex } from './project-grouping' +import type { OrderedGroupEntry } from './worktree-group-entry' +import type { ProjectGroupingIndex } from '../../../../../../shared/project-section-grouping' import { appendWorktreeRows, buildFolderWorkspaceRow, diff --git a/src/renderer/src/components/sidebar/worktree-list/grouping/host-labels.ts b/src/renderer/src/components/sidebar/worktree-list/grouping/host-labels.ts index 925b04eed41..9479d5243b7 100644 --- a/src/renderer/src/components/sidebar/worktree-list/grouping/host-labels.ts +++ b/src/renderer/src/components/sidebar/worktree-list/grouping/host-labels.ts @@ -12,9 +12,9 @@ import { import { getWorktreeHostIdentity } from '../../../../../../shared/worktree/host-qualified-identity' import { getProjectGroupingForRepo, - type ProjectGroupingIndex, - type WorktreeGroupEntry -} from './project-grouping' + type ProjectGroupingIndex +} from '../../../../../../shared/project-section-grouping' +import type { WorktreeGroupEntry } from './worktree-group-entry' import { getFolderWorkspaceHostId } from '../../folder-workspace-host-id' import type { RenderableFolderWorkspace } from './folder-workspace-lanes' diff --git a/src/renderer/src/components/sidebar/worktree-list/grouping/project-group-sections.ts b/src/renderer/src/components/sidebar/worktree-list/grouping/project-group-sections.ts index 6a3b01a9ff9..2993d40561f 100644 --- a/src/renderer/src/components/sidebar/worktree-list/grouping/project-group-sections.ts +++ b/src/renderer/src/components/sidebar/worktree-list/grouping/project-group-sections.ts @@ -8,12 +8,12 @@ import { getEffectiveProjectGroupManualRank } from '../../../../../../shared/pro import { PROJECT_GROUP_META, getProjectGroupHeaderKey } from './group-keys' import { appendOrderedGroups } from './group-sections' import type { SectionAppendContext } from './group-sections' -import type { OrderedGroupEntry } from './project-grouping' +import type { OrderedGroupEntry } from './worktree-group-entry' import { compareRecentRank, createRecentRankLookup, withRepoSectionDisplayLabels -} from './section-order' +} from '../../../../../../shared/project-section-order' import { buildFolderWorkspaceRow } from './row-builders' export function appendProjectGroupSections( diff --git a/src/renderer/src/components/sidebar/worktree-list/grouping/project-grouping.ts b/src/renderer/src/components/sidebar/worktree-list/grouping/project-grouping.ts deleted file mode 100644 index c0554bef73c..00000000000 --- a/src/renderer/src/components/sidebar/worktree-list/grouping/project-grouping.ts +++ /dev/null @@ -1,177 +0,0 @@ -import type { Project, ProjectHostSetup } from '../../../../../../shared/project-types' -import type { Repo } from '../../../../../../shared/repo-types' -import type { Worktree } from '../../../../../../shared/worktree/types' -import type { RenderableFolderWorkspace } from './folder-workspace-lanes' -import { toSshExecutionHostId } from '../../../../../../shared/execution-host' -import { parseWslUncPath } from '../../../../../../shared/wsl-paths' -import { - isWindowsAbsolutePathLike, - normalizeRuntimePathForComparison -} from '../../../../../../shared/cross-platform-path' - -export type OrderedGroupEntry = [string, WorktreeGroupEntry] - -export type ProjectGroupingModel = { - projects: readonly Project[] - projectHostSetups: readonly ProjectHostSetup[] -} - -export type WorktreeGroupEntry = { - label: string - items: Worktree[] - repo?: Repo - repoIds: Set - /** Folder workspaces bucketed into this lane under non-repo grouping. Carries - * the owning group because FolderWorkspaceRow requires a non-optional one. */ - folderWorkspaces?: RenderableFolderWorkspace[] -} - -export type ProjectGroupingIndex = { - projectById: Map - setupByRepoId: Map - surfaceKeysRequiringSetupGroups: Set -} - -const projectGroupingIndexCache = new WeakMap() - -// Why: provisioned and folder setups are not independent Git checkouts. -function isDistinctUserCheckout(setup: ProjectHostSetup): boolean { - return setup.setupMethod !== 'provisioned' && setup.kind !== 'folder' -} - -// Why: execution target and filesystem namespace independently identify a surface. -function getProjectSetupSurfaceKey(setup: ProjectHostSetup): string { - return `${setup.projectId}::${setup.hostId}::${getExecutionSurface(setup)}::${getPathSurface(setup)}` -} - -function getExecutionSurface(setup: ProjectHostSetup): string { - const connectionId = setup.connectionId?.trim() - if (connectionId) { - return toSshExecutionHostId(connectionId) - } - return setup.executionHostId?.trim() || setup.hostId -} - -// Why: projection twins differ by row identity, not checkout directory. -function getCheckoutIdentity(setup: ProjectHostSetup): string { - return normalizeRuntimePathForComparison(setup.path.trim()) || setup.repoId || setup.id -} - -function getPathSurface(setup: ProjectHostSetup): string { - const wslPath = parseWslUncPath(setup.path) - if (wslPath) { - return `wsl:${wslPath.distro.toLowerCase()}` - } - if (isWindowsAbsolutePathLike(setup.path)) { - return 'windows-host' - } - return 'default' -} - -export function buildProjectGroupingIndex( - model?: ProjectGroupingModel -): ProjectGroupingIndex | null { - if (!model) { - return null - } - const cached = projectGroupingIndexCache.get(model) - if (cached !== undefined) { - return cached - } - const projects = model.projects ?? [] - const projectHostSetups = model.projectHostSetups ?? [] - if (projects.length === 0 || projectHostSetups.length === 0) { - projectGroupingIndexCache.set(model, null) - return null - } - const checkoutsByProjectSurface = new Map>() - for (const setup of projectHostSetups) { - if (!isDistinctUserCheckout(setup)) { - continue - } - const key = getProjectSetupSurfaceKey(setup) - const existing = checkoutsByProjectSurface.get(key) - if (existing) { - existing.add(getCheckoutIdentity(setup)) - } else { - checkoutsByProjectSurface.set(key, new Set([getCheckoutIdentity(setup)])) - } - } - const surfaceKeysRequiringSetupGroups = new Set() - for (const [surfaceKey, checkouts] of checkoutsByProjectSurface) { - if (checkouts.size > 1) { - surfaceKeysRequiringSetupGroups.add(surfaceKey) - } - } - const index = { - projectById: new Map(projects.map((project) => [project.id, project])), - setupByRepoId: new Map(projectHostSetups.map((setup) => [setup.repoId, setup])), - surfaceKeysRequiringSetupGroups - } - projectGroupingIndexCache.set(model, index) - return index -} - -// Why: Settings splits projects by this same rule so it matches the sidebar headers. -export function isCheckoutScopedProjectSetup( - setup: ProjectHostSetup, - projectIndex: ProjectGroupingIndex -): boolean { - return ( - projectIndex.surfaceKeysRequiringSetupGroups.has(getProjectSetupSurfaceKey(setup)) && - isDistinctUserCheckout(setup) - ) -} - -export type ProjectHeaderRevealTarget = { - key: string - label: string - repo?: Repo - projectId?: string -} - -export function getProjectGroupingForRepo( - repoId: string, - repoMap: Map, - projectIndex: ProjectGroupingIndex | null -): ProjectHeaderRevealTarget { - const repo = repoMap.get(repoId) - const setup = projectIndex?.setupByRepoId.get(repoId) - const project = setup ? projectIndex?.projectById.get(setup.projectId) : undefined - if (!setup || !project) { - return { - key: `repo:${repoId}`, - label: repo?.displayName ?? 'Unknown', - repo - } - } - if (projectIndex && isCheckoutScopedProjectSetup(setup, projectIndex)) { - // Why: only the ambiguous surface needs checkout-specific headers. - return { - key: `project:${project.id}::setup:${repoId}`, - label: repo?.displayName ?? setup.displayName, - repo, - projectId: project.id - } - } - // Why: provisioned runtime copies and non-ambiguous checkouts follow project - // identity rather than path-scoped setup identity, so they stay in one project. - return { - key: `project:${project.id}`, - label: project.displayName, - repo, - projectId: project.id - } -} - -export function getProjectHeaderRevealTarget( - repoId: string, - repoMap: Map, - projectGrouping?: ProjectGroupingModel -): ProjectHeaderRevealTarget { - return getProjectGroupingForRepo(repoId, repoMap, buildProjectGroupingIndex(projectGrouping)) -} - -export function addRepoIdToGroup(group: WorktreeGroupEntry, repoId: string): void { - group.repoIds.add(repoId) -} diff --git a/src/renderer/src/components/sidebar/worktree-list/grouping/recent-project-rank-scan-budget.test.ts b/src/renderer/src/components/sidebar/worktree-list/grouping/recent-project-rank-scan-budget.test.ts index 397134f5f53..8d9a042b857 100644 --- a/src/renderer/src/components/sidebar/worktree-list/grouping/recent-project-rank-scan-budget.test.ts +++ b/src/renderer/src/components/sidebar/worktree-list/grouping/recent-project-rank-scan-budget.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest' -import { sortProjectEntries } from './section-order' +import { sortProjectEntries } from '../../../../../../shared/project-section-order' import { appendProjectGroupSections } from './project-group-sections' import type { SectionAppendContext } from './group-sections' import { PROJECT_GROUP_META } from './group-keys' -import type { OrderedGroupEntry } from './project-grouping' +import type { OrderedGroupEntry } from './worktree-group-entry' import { repo, worktree } from '../../worktree-list-groups-test-fixtures' function entry( diff --git a/src/renderer/src/components/sidebar/worktree-list/grouping/section-order.ts b/src/renderer/src/components/sidebar/worktree-list/grouping/section-order.ts index 4e21be7ac04..b087da6f75b 100644 --- a/src/renderer/src/components/sidebar/worktree-list/grouping/section-order.ts +++ b/src/renderer/src/components/sidebar/worktree-list/grouping/section-order.ts @@ -1,14 +1,8 @@ import type { Repo } from '../../../../../../shared/repo-types' -import type { ProjectOrderBy } from '../../../../../../shared/ui-chrome-types' import type { WorkspaceStatusDefinition, Worktree } from '../../../../../../shared/worktree/types' import type { AppState } from '../../../../store/types' -import { getRepoDisplayLabelKey, getRepoDisplayLabelsByPath } from '@/lib/repo-display-labels' +import type { ProjectGroupingModel } from '../../../../../../shared/project-section-grouping' import { ALL_GROUP_KEY } from './group-keys' -import type { - OrderedGroupEntry, - ProjectGroupingModel, - WorktreeGroupEntry -} from './project-grouping' import type { WorktreeGroupBy } from './row-types' import { getGroupKeyForWorktree } from './worktree-group-keys' @@ -73,158 +67,6 @@ export function orderMainWorktreeFirst(worktrees: Worktree[]): Worktree[] { return [...mainWorktrees, ...worktrees.filter((worktree) => !worktree.isMainWorktree)] } -// Why: disambiguate a section's *own* label, not its anchor checkout's repo -// name. Substituting repo.displayName made a project header read the project -// name while it was the only repo-backed section and the checkout name as soon -// as a second one rendered, so filtering workspaces renamed the project -// (#16127). Path suffixes still resolve genuinely identical labels. -export function withRepoSectionDisplayLabels( - entries: readonly OrderedGroupEntry[] -): OrderedGroupEntry[] { - const labelItems = entries.flatMap(([, group]) => - group.repo ? [{ ...group.repo, displayName: group.label }] : [] - ) - if (labelItems.length === 0) { - return [...entries] - } - const labelsByPath = getRepoDisplayLabelsByPath(labelItems) - return entries.map(([key, group]) => [ - key, - group.repo - ? { ...group, label: labelsByPath.get(getRepoDisplayLabelKey(group.repo)) ?? group.label } - : group - ]) -} - -/** - * Recent rank for a project header. `hasActivity` projects (at least one - * visible worktree) always sort before fallback projects, regardless of the - * numeric values — a placeholder's `addedAt` must never outrank real activity. - * Within each tier, higher timestamps come first. - */ -type RecentRank = { hasActivity: boolean; ts: number } - -export function recentRankForEntry(entry: OrderedGroupEntry): RecentRank { - let max = Number.NEGATIVE_INFINITY - for (const worktree of entry[1].items) { - if (worktree.lastActivityAt > max) { - max = worktree.lastActivityAt - } - } - if (max !== Number.NEGATIVE_INFINITY) { - // Why: Recent must be timestamp-based, not encounter order — the incoming - // array is no longer pre-sorted by recency once decoupled from sortBy. - return { hasActivity: true, ts: max } - } - const addedAt = entry[1].repo?.addedAt - return { - hasActivity: false, - ts: typeof addedAt === 'number' ? addedAt : Number.NEGATIVE_INFINITY - } -} - -export function createRecentRankLookup(): (entry: OrderedGroupEntry) => RecentRank { - const ranks = new Map() - return (entry) => { - const existing = ranks.get(entry) - if (existing) { - return existing - } - const rank = recentRankForEntry(entry) - ranks.set(entry, rank) - return rank - } -} - -export function compareRecentRank(a: RecentRank, b: RecentRank): number { - if (a.hasActivity !== b.hasActivity) { - return a.hasActivity ? -1 : 1 - } - return b.ts - a.ts -} - -function manualRankForEntry( - entry: OrderedGroupEntry, - repoOrder: Map | undefined -): number { - const key = entry[0] - const repoIds = - entry[1].repoIds.size > 0 - ? [...entry[1].repoIds] - : [key.startsWith('repo:') ? key.slice('repo:'.length) : key] - let rank = Number.POSITIVE_INFINITY - for (const repoId of repoIds) { - const repoRank = repoOrder?.get(repoId) - if (repoRank !== undefined && repoRank < rank) { - rank = repoRank - } - } - return rank -} - -export function getManualOrderAnchorRepo( - group: WorktreeGroupEntry, - repoMap: Map, - repoOrder: Map | undefined -): Repo | undefined { - let anchor = group.repo - let anchorRank = anchor ? (repoOrder?.get(anchor.id) ?? Number.POSITIVE_INFINITY) : undefined - for (const repoId of group.repoIds) { - const repo = repoMap.get(repoId) - if (!repo) { - continue - } - const rank = repoOrder?.get(repoId) ?? Number.POSITIVE_INFINITY - if (!anchor || rank < (anchorRank ?? Number.POSITIVE_INFINITY)) { - anchor = repo - anchorRank = rank - } - } - return anchor -} - -/** - * Order project header entries by the user's project-order preference. Manual - * follows the canonical repoOrder; Recent follows each project's most recent - * visible workspace activity (descending), with empty/imported-only projects - * sorting after active ones, then by manual rank, then label. - */ -export function sortProjectEntries( - entries: OrderedGroupEntry[], - projectOrderBy: ProjectOrderBy, - repoOrder: Map | undefined -): OrderedGroupEntry[] { - if (projectOrderBy === 'recent') { - if (entries.length < 2) { - return [...entries] - } - const getRecentRank = createRecentRankLookup() - return [...entries].sort((a, b) => { - const byRecent = compareRecentRank(getRecentRank(a), getRecentRank(b)) - if (byRecent !== 0) { - return byRecent - } - const ma = manualRankForEntry(a, repoOrder) - const mb = manualRankForEntry(b, repoOrder) - if (ma !== mb) { - return ma - mb - } - return a[1].label.localeCompare(b[1].label) - }) - } - if (!repoOrder) { - return entries - } - return [...entries].sort((a, b) => { - const ra = manualRankForEntry(a, repoOrder) - const rb = manualRankForEntry(b, repoOrder) - if (ra !== rb) { - return ra - rb - } - return a[1].label.localeCompare(b[1].label) - }) -} - /** * Build the flat row list consumed by the virtualizer. * Extracted here to keep WorktreeList.tsx under the line-count lint limit. diff --git a/src/renderer/src/components/sidebar/worktree-list/grouping/worktree-group-entry.ts b/src/renderer/src/components/sidebar/worktree-list/grouping/worktree-group-entry.ts new file mode 100644 index 00000000000..6795308a40a --- /dev/null +++ b/src/renderer/src/components/sidebar/worktree-list/grouping/worktree-group-entry.ts @@ -0,0 +1,11 @@ +import type { ProjectSectionEntry } from '../../../../../../shared/project-section-grouping' +import type { Worktree } from '../../../../../../shared/worktree/types' +import type { RenderableFolderWorkspace } from './folder-workspace-lanes' + +export type OrderedGroupEntry = [string, WorktreeGroupEntry] + +export type WorktreeGroupEntry = ProjectSectionEntry & { + /** Folder workspaces bucketed into this lane under non-repo grouping. Carries + * the owning group because FolderWorkspaceRow requires a non-optional one. */ + folderWorkspaces?: RenderableFolderWorkspace[] +} diff --git a/src/renderer/src/components/sidebar/worktree-list/grouping/worktree-group-keys.ts b/src/renderer/src/components/sidebar/worktree-list/grouping/worktree-group-keys.ts index 807e41e97f1..5fe9b0633d2 100644 --- a/src/renderer/src/components/sidebar/worktree-list/grouping/worktree-group-keys.ts +++ b/src/renderer/src/components/sidebar/worktree-list/grouping/worktree-group-keys.ts @@ -5,8 +5,11 @@ import { getWorkspaceStatus, getWorkspaceStatusGroupKey } from '../../workspace- import { cloneDefaultWorkspaceStatuses } from '../../../../../../shared/workspace-statuses' import type { AppState } from '../../../../store/types' import { ALL_GROUP_KEY, getPRGroupKey, getProjectGroupHeaderKey } from './group-keys' -import { buildProjectGroupingIndex, getProjectGroupingForRepo } from './project-grouping' -import type { ProjectGroupingModel } from './project-grouping' +import { + buildProjectGroupingIndex, + getProjectGroupingForRepo +} from '../../../../../../shared/project-section-grouping' +import type { ProjectGroupingModel } from '../../../../../../shared/project-section-grouping' import type { WorktreeGroupBy } from './row-types' export function getGroupKeyForWorktree( diff --git a/src/renderer/src/components/sidebar/worktree-list/grouping/worktree-grouping.ts b/src/renderer/src/components/sidebar/worktree-list/grouping/worktree-grouping.ts index b6449acd2a1..d4259c0f1fe 100644 --- a/src/renderer/src/components/sidebar/worktree-list/grouping/worktree-grouping.ts +++ b/src/renderer/src/components/sidebar/worktree-list/grouping/worktree-grouping.ts @@ -14,19 +14,18 @@ import { } from './folder-workspace-lanes' import { PR_GROUP_META, PR_GROUP_ORDER, getPRGroupKey, getPRLaneKey } from './group-keys' import type { PRGroupKey } from './group-keys' -import { addRepoIdToGroup, getProjectGroupingForRepo } from './project-grouping' -import type { - OrderedGroupEntry, - ProjectGroupingIndex, - WorktreeGroupEntry -} from './project-grouping' +import type { OrderedGroupEntry, WorktreeGroupEntry } from './worktree-group-entry' +import { + buildProjectSections, + type ProjectGroupingIndex +} from '../../../../../../shared/project-section-grouping' +import { orderProjectSections } from '../../../../../../shared/project-section-order' import type { ImportedWorktreesCardCandidate, NewExternalWorktreesInboxCandidate, PendingCreationRef, WorktreeGroupBy } from './row-types' -import { getManualOrderAnchorRepo, sortProjectEntries } from './section-order' /** Lane label for a lane a folder workspace opened before any worktree did. */ function getLaneLabelForKey( @@ -78,17 +77,33 @@ export function buildOrderedGroups(args: { folderWorkspaces = [] } = args + if (groupBy === 'repo') { + // Why shared: the paired phone builds its Project sections with this same function. + return buildProjectSections({ + items: naturalWorktrees, + repoIdOf: (worktree) => worktree.repoId, + repoMap, + projectIndex, + placeholderRepoIds, + noticeRepos: [ + ...[...importedWorktreesByRepo].map( + ([repoId, candidate]) => [repoId, candidate.repo] as const + ), + ...[...newExternalWorktreesInboxByRepo].map( + ([repoId, candidate]) => [repoId, candidate.repo] as const + ) + ], + pendingRepoIds: pendingByRepo.keys(), + repoOrder, + projectOrderBy + }) + } + const grouped = new Map() for (const w of naturalWorktrees) { let key: string let label: string - let repo: Repo | undefined - if (groupBy === 'repo') { - const grouping = getProjectGroupingForRepo(w.repoId, repoMap, projectIndex) - key = grouping.key - label = grouping.label - repo = grouping.repo - } else if (groupBy === 'workspace-status') { + if (groupBy === 'workspace-status') { const workspaceStatus = getWorkspaceStatus(w, workspaceStatuses) key = getWorkspaceStatusGroupKey(workspaceStatus) label = @@ -99,111 +114,34 @@ export function buildOrderedGroups(args: { label = PR_GROUP_META[prGroup].label } if (!grouped.has(key)) { - grouped.set(key, { label, items: [], repo, repoIds: new Set() }) + grouped.set(key, { label, items: [], repo: undefined, repoIds: new Set() }) } const group = grouped.get(key)! group.items.push(w) - addRepoIdToGroup(group, w.repoId) + group.repoIds.add(w.repoId) } // Why: folder workspaces are not worktrees, so they never appear in the loop // above. Bucketing them here — and creating the lane when no worktree opened // one — is what lets a folder workspace be the sole occupant of a lane (#15362). - if (groupBy !== 'repo') { - for (const pair of folderWorkspaces) { - const key = getFolderWorkspaceLaneKey(pair, groupBy, workspaceStatuses) - if (!grouped.has(key)) { - grouped.set(key, { - label: getLaneLabelForKey(key, groupBy, workspaceStatuses), - items: [], - repo: undefined, - repoIds: new Set() - }) - } - const group = grouped.get(key)! - group.folderWorkspaces ??= [] - group.folderWorkspaces.push(pair) - } - for (const group of grouped.values()) { - group.folderWorkspaces?.sort((left, right) => - compareFolderWorkspacesForDisplay(left.folderWorkspace, right.folderWorkspace) - ) + for (const pair of folderWorkspaces) { + const key = getFolderWorkspaceLaneKey(pair, groupBy, workspaceStatuses) + if (!grouped.has(key)) { + grouped.set(key, { + label: getLaneLabelForKey(key, groupBy, workspaceStatuses), + items: [], + repo: undefined, + repoIds: new Set() + }) } + const group = grouped.get(key)! + group.folderWorkspaces ??= [] + group.folderWorkspaces.push(pair) } - if (groupBy === 'repo') { - for (const repoId of placeholderRepoIds) { - const grouping = getProjectGroupingForRepo(repoId, repoMap, projectIndex) - if (!grouping.repo) { - continue - } - const key = grouping.key - if (!grouped.has(key)) { - // Why: repos can arrive before worktree scans, but stale IDs passed by - // older snapshots must not render an "Unknown" project header. - grouped.set(key, { - label: grouping.label, - items: [], - repo: grouping.repo, - repoIds: new Set([repoId]) - }) - } else { - addRepoIdToGroup(grouped.get(key)!, repoId) - } - } + for (const group of grouped.values()) { + group.folderWorkspaces?.sort((left, right) => + compareFolderWorkspacesForDisplay(left.folderWorkspace, right.folderWorkspace) + ) } - if (groupBy === 'repo') { - for (const [repoId, candidate] of importedWorktreesByRepo) { - const grouping = getProjectGroupingForRepo(repoId, repoMap, projectIndex) - const key = grouping.key - if (!grouped.has(key)) { - grouped.set(key, { - label: grouping.label, - items: [], - repo: grouping.repo ?? candidate.repo, - repoIds: new Set([repoId]) - }) - } else if (grouped.has(key)) { - addRepoIdToGroup(grouped.get(key)!, repoId) - } - } - } - if (groupBy === 'repo') { - for (const [repoId, candidate] of newExternalWorktreesInboxByRepo) { - const grouping = getProjectGroupingForRepo(repoId, repoMap, projectIndex) - const key = grouping.key - if (!grouped.has(key)) { - // Why: the default policy removes pinned worktrees from natural groups, - // but actionable inbox rows still need a project section to render in. - grouped.set(key, { - label: grouping.label, - items: [], - repo: grouping.repo ?? candidate.repo, - repoIds: new Set([repoId]) - }) - } else if (grouped.has(key)) { - addRepoIdToGroup(grouped.get(key)!, repoId) - } - } - } - if (groupBy === 'repo') { - for (const repoId of pendingByRepo.keys()) { - const grouping = getProjectGroupingForRepo(repoId, repoMap, projectIndex) - const key = grouping.key - if (!grouped.has(key)) { - // Why: creating the first worktree in a repo leaves it with no group yet; - // ensure one so the in-progress row nests under its repo instead of being - // dropped. - grouped.set(key, { - label: grouping.label, - items: [], - repo: grouping.repo, - repoIds: new Set([repoId]) - }) - } else { - addRepoIdToGroup(grouped.get(key)!, repoId) - } - } - } - const orderedGroups: OrderedGroupEntry[] = [] if (groupBy === 'pr-status') { for (const prGroup of PR_GROUP_ORDER) { @@ -224,16 +162,7 @@ export function buildOrderedGroups(args: { } } } else { - for (const group of grouped.values()) { - // Why: logical project headers can contain multiple host setup repos. - // Use the repo that anchors manual order so drag and actions target the - // same persisted order source the row sorter reads. - group.repo = getManualOrderAnchorRepo(group, repoMap, repoOrder) - } - // Why: project header order is its own user choice (projectOrderBy), - // decoupled from workspace sortBy. Manual uses the canonical repoOrder so - // header drag has a stable source of truth; Recent follows activity. - const entries = sortProjectEntries(Array.from(grouped.entries()), projectOrderBy, repoOrder) + const entries = orderProjectSections(grouped, repoMap, repoOrder, projectOrderBy) // Why: large imported repo sets can have one group per repo; spreading // those entries into push can exceed V8's argument limit. for (const entry of entries) { diff --git a/src/renderer/src/components/sidebar/worktree-list/listing/use-collapsed-groups.ts b/src/renderer/src/components/sidebar/worktree-list/listing/use-collapsed-groups.ts index 1993416b091..3f35069dbf0 100644 --- a/src/renderer/src/components/sidebar/worktree-list/listing/use-collapsed-groups.ts +++ b/src/renderer/src/components/sidebar/worktree-list/listing/use-collapsed-groups.ts @@ -6,7 +6,7 @@ import type { Repo } from '../../../../../../shared/repo-types' import type { WorkspaceStatusDefinition, Worktree } from '../../../../../../shared/worktree/types' import type { WorktreeLineage } from '../../../../../../shared/worktree/lineage-types' import type { PinnedWorktreeDisplayPolicy, WorktreeGroupBy } from '../grouping/row-types' -import type { ProjectGroupingModel } from '../grouping/project-grouping' +import type { ProjectGroupingModel } from '../../../../../../shared/project-section-grouping' import { getFolderWorkspaceRevealGroupKeys } from '../navigation/folder-reveal' import type { FolderWorkspace } from '../../../../../../shared/folder-workspace-types' import type { ExecutionHostId } from '../../../../../../shared/execution-host' diff --git a/src/renderer/src/components/sidebar/worktree-list/listing/use-section-rows.ts b/src/renderer/src/components/sidebar/worktree-list/listing/use-section-rows.ts index 08499686972..99d1a04e012 100644 --- a/src/renderer/src/components/sidebar/worktree-list/listing/use-section-rows.ts +++ b/src/renderer/src/components/sidebar/worktree-list/listing/use-section-rows.ts @@ -13,9 +13,9 @@ import { folderWorkspaceKey } from '../../../../../../shared/workspace-scope' import { getFolderWorkspaceHostIdentity } from '../../../../../../shared/folder-workspace-worktree' import { getHostDisplayLabelOverrides } from '../../../../../../shared/host-setting-overrides' import { buildRows } from '../grouping/build-rows' -import type { ProjectGroupingModel } from '../grouping/project-grouping' +import type { ProjectGroupingModel } from '../../../../../../shared/project-section-grouping' import type { PinnedWorktreeDisplayPolicy, Row, WorktreeGroupBy } from '../grouping/row-types' -import { getLogicalRepoOrderRankById } from '../../project-header-drop' +import { getLogicalRepoOrderRankById } from '../../../../../../shared/project-section-order' import { getEmptyProjectPlaceholderRepoIds } from '../../empty-project-placeholder-repos' import { deferHostSectionCollapse, scopeHostSectionCollapse } from '../../host-section-collapse' import { addHostSectionRows } from '../../host-section-rows' diff --git a/src/renderer/src/components/sidebar/worktree-list/navigation/pending-reveal-inputs.ts b/src/renderer/src/components/sidebar/worktree-list/navigation/pending-reveal-inputs.ts index 465e6928d9d..42594527709 100644 --- a/src/renderer/src/components/sidebar/worktree-list/navigation/pending-reveal-inputs.ts +++ b/src/renderer/src/components/sidebar/worktree-list/navigation/pending-reveal-inputs.ts @@ -9,7 +9,7 @@ import type { WorkspaceStatusDefinition, Worktree } from '../../../../../../shar import type { WorktreeLineage } from '../../../../../../shared/worktree/lineage-types' import type { ExecutionHostId } from '../../../../../../shared/execution-host' import type { RenderRow } from '../listing/render-row' -import type { ProjectGroupingModel } from '../grouping/project-grouping' +import type { ProjectGroupingModel } from '../../../../../../shared/project-section-grouping' import type { PinnedWorktreeDisplayPolicy, WorktreeGroupBy } from '../grouping/row-types' import { getFolderWorkspaceRevealGroupKeys } from './folder-reveal' import { getWorktreeRevealCollapsedGroupKeys } from './worktree-reveal-group-keys' diff --git a/src/renderer/src/components/sidebar/worktree-list/navigation/reveal-ancestors.ts b/src/renderer/src/components/sidebar/worktree-list/navigation/reveal-ancestors.ts index 563387d0b93..44774d453ba 100644 --- a/src/renderer/src/components/sidebar/worktree-list/navigation/reveal-ancestors.ts +++ b/src/renderer/src/components/sidebar/worktree-list/navigation/reveal-ancestors.ts @@ -1,7 +1,7 @@ import type { ProjectGroup } from '../../../../../../shared/project-group-types' import type { Repo } from '../../../../../../shared/repo-types' import { getProjectGroupHeaderKey } from '../grouping/group-keys' -import type { ProjectGroupingModel } from '../grouping/project-grouping' +import type { ProjectGroupingModel } from '../../../../../../shared/project-section-grouping' function getProjectIdFromHeaderRowKey(rowKey: string): string | null { if (!rowKey.startsWith('project:')) { diff --git a/src/renderer/src/components/sidebar/worktree-list/navigation/worktree-reveal-group-keys.ts b/src/renderer/src/components/sidebar/worktree-list/navigation/worktree-reveal-group-keys.ts index 3fe1a15b004..dc5082a8c39 100644 --- a/src/renderer/src/components/sidebar/worktree-list/navigation/worktree-reveal-group-keys.ts +++ b/src/renderer/src/components/sidebar/worktree-list/navigation/worktree-reveal-group-keys.ts @@ -14,7 +14,7 @@ import { getWorktreeLineageAncestors } from '../../worktree-lineage-projection' import { getWorktreeLineageGroupKey, PINNED_GROUP_KEY } from '../grouping/group-keys' -import type { ProjectGroupingModel } from '../grouping/project-grouping' +import type { ProjectGroupingModel } from '../../../../../../shared/project-section-grouping' import type { PinnedWorktreeDisplayPolicy, WorktreeGroupBy } from '../grouping/row-types' import { getGroupKeysForWorktree } from '../grouping/worktree-group-keys' diff --git a/src/renderer/src/components/sidebar/worktree-list/viewport/viewport-props.ts b/src/renderer/src/components/sidebar/worktree-list/viewport/viewport-props.ts index 8fbe8aef935..feb68d74262 100644 --- a/src/renderer/src/components/sidebar/worktree-list/viewport/viewport-props.ts +++ b/src/renderer/src/components/sidebar/worktree-list/viewport/viewport-props.ts @@ -21,7 +21,7 @@ import type { ImportedWorktreeCardActionState } from '../../imported-worktrees-c import type { NewExternalWorktreesInboxActionState } from '../../new-external-worktrees-inbox-actions' import type { WorktreeDragGroup } from '../../worktree-manual-order' import type { WorktreeStatusDropAtIndexArgs } from '../drag/drop-commit-context' -import type { ProjectGroupingModel } from '../grouping/project-grouping' +import type { ProjectGroupingModel } from '../../../../../../shared/project-section-grouping' import type { PinnedWorktreeDisplayPolicy, WorktreeGroupBy } from '../grouping/row-types' export const EMPTY_PROJECT_GROUPS: readonly ProjectGroup[] = [] diff --git a/src/renderer/src/hooks/settings-navigation-build-options.ts b/src/renderer/src/hooks/settings-navigation-build-options.ts index 606485eb91f..fbac15468a9 100644 --- a/src/renderer/src/hooks/settings-navigation-build-options.ts +++ b/src/renderer/src/hooks/settings-navigation-build-options.ts @@ -1,4 +1,4 @@ -import type { ProjectGroupingModel } from '@/components/sidebar/worktree-list/grouping/project-grouping' +import type { ProjectGroupingModel } from '../../../shared/project-section-grouping' import type { Repo } from '../../../shared/repo-types' export type SettingsNavigationBuildOptions = { diff --git a/src/renderer/src/hooks/useSettingsNavigationMetadata.ts b/src/renderer/src/hooks/useSettingsNavigationMetadata.ts index 647a90e87fa..821f7eab3a3 100644 --- a/src/renderer/src/hooks/useSettingsNavigationMetadata.ts +++ b/src/renderer/src/hooks/useSettingsNavigationMetadata.ts @@ -22,7 +22,7 @@ import { defaultScopeHost } from '@/lib/default-creation-host' import { useAppStore } from '@/store' import { useProjectHostSetupProjection } from '@/store/selectors' import { useStructuredAgentSessionHostQueuesMessages } from '@/runtime/structured-agent-session-host-capability' -import type { ProjectGroupingModel } from '@/components/sidebar/worktree-list/grouping/project-grouping' +import type { ProjectGroupingModel } from '../../../shared/project-section-grouping' import type { Repo } from '../../../shared/repo-types' import { buildCapabilitySettingsSections, diff --git a/src/renderer/src/runtime/sync-runtime-graph-project-sections.test.ts b/src/renderer/src/runtime/sync-runtime-graph-project-sections.test.ts new file mode 100644 index 00000000000..4c8b812c789 --- /dev/null +++ b/src/renderer/src/runtime/sync-runtime-graph-project-sections.test.ts @@ -0,0 +1,110 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { + scheduleRuntimeGraphSync, + setRuntimeGraphStoreStateGetter, + setRuntimeGraphSyncEnabled +} from './sync-runtime-graph' +import type { AppState } from '../store/types' +import type { Repo } from '../../../shared/repo-types' +import type { RuntimeSyncWindowGraph } from '../../../shared/runtime-types' + +vi.mock('./sync-runtime-graph/terminal-group-grid-sizes', () => ({ + measureTerminalGroupGridSizes: () => undefined +})) + +const repo: Repo = { + id: 'repo-1', + path: '/Users/me/orca', + displayName: 'orca', + badgeColor: '#737373', + addedAt: 1, + hookSettings: { mode: 'auto', scripts: { setup: 'secret-setup', archive: '' } }, + gitUsername: 'me', + repoIcon: { type: 'image', src: 'data:image/png;base64,AAAA', source: 'upload' } +} + +const BASE_STATE = { + tabsByWorktree: {}, + terminalLayoutsByTabId: {}, + runtimePaneTitlesByTabId: {}, + groupsByWorktree: {}, + activeGroupIdByWorktree: {}, + layoutByWorktree: {}, + unifiedTabsByWorktree: {}, + tabBarOrderByWorktree: {}, + activeFileId: null, + activeFileIdByWorktree: {}, + openFiles: [], + editorDrafts: {}, + activeTabId: null, + agentStatusByPaneKey: {}, + browserTabsByWorktree: {}, + repos: [repo], + projects: [], + projectHostSetups: [], + projectOrderBy: 'recent' +} + +async function flushSync(): Promise { + await vi.advanceTimersByTimeAsync(20) + await Promise.resolve() + await Promise.resolve() +} + +afterEach(() => { + setRuntimeGraphSyncEnabled(false) + setRuntimeGraphStoreStateGetter(null) + vi.useRealTimers() + vi.unstubAllGlobals() +}) + +describe("the sidebar's Project catalog in the window graph", () => { + it('carries it once per change, without repo hooks, credentials or uploaded icons', async () => { + const syncWindowGraph = vi + .fn<(graph: RuntimeSyncWindowGraph) => Promise>() + .mockResolvedValue({}) + vi.useFakeTimers() + vi.stubGlobal('window', { api: { runtime: { syncWindowGraph } } }) + vi.stubGlobal('HTMLElement', class HTMLElement {}) + let state = BASE_STATE + // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: graph sync reads only these members of the store. + setRuntimeGraphStoreStateGetter(() => state as unknown as AppState) + setRuntimeGraphSyncEnabled(true) + await flushSync() + + expect(syncWindowGraph.mock.calls[0]?.[0].projectSectionCatalog).toEqual({ + repos: [ + { + id: 'repo-1', + displayName: 'orca', + path: '/Users/me/orca', + badgeColor: '#737373', + addedAt: 1 + } + ], + projects: [{ id: 'repo:repo-1', displayName: 'orca' }], + setups: [ + { + id: 'repo-1', + projectId: 'repo:repo-1', + hostId: 'local', + repoId: 'repo-1', + path: '/Users/me/orca', + displayName: 'orca', + setupMethod: 'legacy-repo' + } + ], + projectOrderBy: 'recent' + }) + + scheduleRuntimeGraphSync() + await flushSync() + expect(syncWindowGraph).toHaveBeenCalledTimes(2) + expect(syncWindowGraph.mock.calls[1]?.[0]).not.toHaveProperty('projectSectionCatalog') + + state = { ...BASE_STATE, projectOrderBy: 'manual' } + scheduleRuntimeGraphSync() + await flushSync() + expect(syncWindowGraph.mock.calls[2]?.[0].projectSectionCatalog?.projectOrderBy).toBe('manual') + }) +}) diff --git a/src/renderer/src/runtime/sync-runtime-graph/graph-publication.ts b/src/renderer/src/runtime/sync-runtime-graph/graph-publication.ts index cf058f35e37..4b1f9d9bf15 100644 --- a/src/renderer/src/runtime/sync-runtime-graph/graph-publication.ts +++ b/src/renderer/src/runtime/sync-runtime-graph/graph-publication.ts @@ -18,6 +18,7 @@ import { buildMobileSessionTabSnapshots } from './mobile-session-snapshots' import { isWebOnlyMirroredTerminalTab } from './mobile-session-surfaces' import { resolveRuntimeTerminalTitle } from './sync-projections' import { measureTerminalGroupGridSizes } from './terminal-group-grid-sizes' +import { getProjectSectionCatalogFromState } from './project-section-catalog' import { collectAmbiguousTerminalTabIds, graphState, @@ -60,6 +61,7 @@ export async function syncRuntimeGraph(): Promise { ) const publication = partitionMobileSessionPublication(mobileSessionTabs) const gridSizes = measureTerminalGroupGridSizes(state.settings?.terminalFontSize ?? 14) + const projectSectionCatalog = getProjectSectionCatalogFromState(state) const graph: RuntimeRendererSyncWindowGraph = { tabs: [], leaves: [], @@ -69,6 +71,9 @@ export async function syncRuntimeGraph(): Promise { ...(gridSizes ? { terminalGroupGridSizes: gridSizes.groups } : {}), ...(gridSizes?.singleGroupWorkspace ? { singleGroupWorkspaceGridSize: gridSizes.singleGroupWorkspace } + : {}), + ...(projectSectionCatalog && projectSectionCatalog !== graphState.publishedProjectSectionCatalog + ? { projectSectionCatalog } : {}) } @@ -198,6 +203,8 @@ export async function syncRuntimeGraph(): Promise { try { const result = await window.api.runtime.syncWindowGraph(graph) graphState.publishedTerminalGroupGridSizesKey = JSON.stringify(gridSizes ?? null) + graphState.publishedProjectSectionCatalog = + projectSectionCatalog ?? graphState.publishedProjectSectionCatalog commitMobileSessionPublication(mobileSessionTabs, result?.mobileSessionResyncWorktrees) const currentState = graphState.getStoreState() currentState?.setRuntimeAgentOrchestrationByPaneKey?.(result?.agentOrchestrationByPaneKey ?? {}) diff --git a/src/renderer/src/runtime/sync-runtime-graph/graph-state.ts b/src/renderer/src/runtime/sync-runtime-graph/graph-state.ts index a7cd35bad39..2e370f156cc 100644 --- a/src/renderer/src/runtime/sync-runtime-graph/graph-state.ts +++ b/src/renderer/src/runtime/sync-runtime-graph/graph-state.ts @@ -1,3 +1,4 @@ +import type { MobileProjectSectionCatalog } from '../../../../shared/mobile-project-sections-contract' import { createBrowserUuid } from '@/lib/browser-uuid' import type { AppState } from '@/store/types' import type { @@ -31,6 +32,10 @@ export const EMPTY_WORKTREE_OPEN_FILE_IDS: readonly string[] = [] export const mobilePublicationEpoch = `renderer:${createBrowserUuid()}` +function noPublishedProjectSectionCatalog(): MobileProjectSectionCatalog | null { + return null +} + export const graphState = { syncScheduled: false, syncInFlight: false, @@ -56,7 +61,9 @@ export const graphState = { cachedEditorDraftHashes: null as EditorDraftHashCache | null, mobileSessionSnapshotVersion: 0, /** Last group grid sizes main accepted, so a refit republishes only when one changed. */ - publishedTerminalGroupGridSizesKey: '' + publishedTerminalGroupGridSizesKey: '', + /** Last sidebar Project catalog main accepted, so the graph carries it only when it changed. */ + publishedProjectSectionCatalog: noPublishedProjectSectionCatalog() } /** IDs occurring more than once cannot address the legacy tab-keyed runtime maps safely. */ diff --git a/src/renderer/src/runtime/sync-runtime-graph/project-section-catalog.ts b/src/renderer/src/runtime/sync-runtime-graph/project-section-catalog.ts new file mode 100644 index 00000000000..e375e04d9ea --- /dev/null +++ b/src/renderer/src/runtime/sync-runtime-graph/project-section-catalog.ts @@ -0,0 +1,44 @@ +import { + buildMobileProjectSectionCatalog, + type MobileProjectSectionCatalog +} from '../../../../shared/mobile-project-sections-contract' +import type { ProjectHostSetupProjection } from '../../../../shared/project-host-setup-projection' +import { getProjectHostSetupProjectionFromState } from '@/store/project-host-setup-selector' +import type { AppState } from '@/store/types' + +type CatalogSource = { + repos: AppState['repos'] + projection: ProjectHostSetupProjection + projectOrderBy: AppState['projectOrderBy'] + catalog: MobileProjectSectionCatalog +} + +let cachedCatalogSource: CatalogSource | null = null + +/** What the sidebar groups Project headers by: state.repos in its order and the selector's projection. */ +export function getProjectSectionCatalogFromState( + state: Pick +): MobileProjectSectionCatalog | null { + // Why: graph publication must not fail on a store slice that holds no repo catalog. + if (!Array.isArray(state.repos)) { + return null + } + const projection = getProjectHostSetupProjectionFromState(state) + const cached = cachedCatalogSource + if ( + cached && + cached.repos === state.repos && + cached.projection === projection && + cached.projectOrderBy === state.projectOrderBy + ) { + return cached.catalog + } + const catalog = buildMobileProjectSectionCatalog(state.repos, projection, state.projectOrderBy) + cachedCatalogSource = { + repos: state.repos, + projection, + projectOrderBy: state.projectOrderBy, + catalog + } + return catalog +} diff --git a/src/renderer/src/store/adopt-from-endpoint.ts b/src/renderer/src/store/adopt-from-endpoint.ts index 07b8ec6978d..2bdb56874c4 100644 --- a/src/renderer/src/store/adopt-from-endpoint.ts +++ b/src/renderer/src/store/adopt-from-endpoint.ts @@ -8,12 +8,14 @@ import type { ProjectGroup } from '../../../shared/project-group-types' import type { ProjectHostSetup } from '../../../shared/project-types' import type { Repo } from '../../../shared/repo-types' import { - getRepoExecutionHostId, LOCAL_EXECUTION_HOST_ID, toSshExecutionHostId, type ExecutionHostId } from '../../../shared/execution-host' -import { normalizeProjectHostSetupRow } from '../../../shared/project-catalog-row-normalization' +import { + adoptEndpointProjectHostSetup, + adoptEndpointRepo +} from '../../../shared/endpoint-catalog-adoption' import type { RuntimeClientTarget } from '../../../shared/runtime-client-target' import { getRuntimeTargetHostId } from '../runtime/runtime-client-target' @@ -81,11 +83,11 @@ export function adoptFromEndpoint( ): Repo | ProjectGroup | ProjectHostSetup | FolderWorkspace | OwnedWorktreeEvent | ExecutionHostId { switch (fetched.kind) { case 'repo': - return adoptRepo(target, fetched.row) + return adoptEndpointRepo(target, fetched.row) case 'projectGroup': return adoptProjectGroup(target, fetched.row) case 'projectHostSetup': - return adoptProjectHostSetup(target, fetched.row) + return adoptEndpointProjectHostSetup(target, fetched.row) case 'folderWorkspace': return { ...fetched.row, @@ -110,17 +112,6 @@ export function adoptFromEndpoint( } } -function adoptRepo(target: RuntimeClientTarget, repo: Repo): Repo { - if (target.kind === 'environment') { - // `connectionId` survives: on a server's row it names that server's nested SSH target. - return { ...repo, executionHostId: getRuntimeTargetHostId(target) } - } - if (repo.connectionId) { - return { ...repo, executionHostId: getRepoExecutionHostId(repo) } - } - return repo.executionHostId ? repo : { ...repo, executionHostId: LOCAL_EXECUTION_HOST_ID } -} - function adoptProjectGroup(target: RuntimeClientTarget, projectGroup: ProjectGroup): ProjectGroup { if (target.kind === 'environment') { return { ...projectGroup, executionHostId: getRuntimeTargetHostId(target) } @@ -130,25 +121,3 @@ function adoptProjectGroup(target: RuntimeClientTarget, projectGroup: ProjectGro } return { ...projectGroup, executionHostId: LOCAL_EXECUTION_HOST_ID } } - -function adoptProjectHostSetup( - target: RuntimeClientTarget, - setup: ProjectHostSetup -): ProjectHostSetup { - // Why here: every setup row crossing IPC or RPC into the renderer passes through this - // adoption step, so it is where the declared field types stop being aspirational. - const adopted = normalizeProjectHostSetupRow(setup) - if (target.kind !== 'environment') { - return adopted - } - const hostId = getRuntimeTargetHostId(target) - const executionHostId = adopted.executionHostId ?? adopted.hostId - return { - ...adopted, - hostId, - executionHostId: executionHostId === LOCAL_EXECUTION_HOST_ID ? hostId : executionHostId, - runtimeOwnerEnvironmentId: target.environmentId, - // Why: paired clients route through the HUB and must not treat its private SSH target as client-local configuration. - connectionId: null - } -} diff --git a/src/renderer/src/store/github/work-item-fetch-actions.ts b/src/renderer/src/store/github/work-item-fetch-actions.ts index 26904d761ce..70ac75ef212 100644 --- a/src/renderer/src/store/github/work-item-fetch-actions.ts +++ b/src/renderer/src/store/github/work-item-fetch-actions.ts @@ -6,7 +6,7 @@ import type { WorkItemsCacheError } from './cache-model' import { isGitHubWorkItemsSshRemoteRequiredError } from '../../../../shared/work-items' import { getTaskSourceCacheScope } from '../../../../shared/task-source-context' import { isGitHubWorkItemsQueryTooLarge } from '../slices/github-work-items-query-bounds' -import { reconcileCatalogRows } from '../slices/repo-identity-reconcile' +import { reconcileCatalogRows } from '../../../../shared/catalog-row-reconcile' import { structuralValuesEqual } from '../../../../shared/structural-value-equality' import { workItemsCacheKey, workItemsInflightRequestKey } from './cache-identity' import { isFresh, withBoundedCacheEntry, WORK_ITEMS_CACHE_TTL } from './cache-policy' diff --git a/src/renderer/src/store/project-host-setup-selector-normalization.ts b/src/renderer/src/store/project-host-setup-selector-normalization.ts deleted file mode 100644 index 154ee4e5ea0..00000000000 --- a/src/renderer/src/store/project-host-setup-selector-normalization.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { Project, ProjectHostSetup } from '../../../shared/project-types' -import type { Repo } from '../../../shared/repo-types' -import { - getProjectIdentityKey, - type ProjectHostSetupProjection -} from '../../../shared/project-host-setup-projection' - -export type NormalizedProjectHostSetupProjection = ProjectHostSetupProjection & { - changed: boolean -} - -export function normalizeHydratedProjectHostSetupProjection( - repos: readonly Repo[], - projects: readonly Project[], - setups: readonly ProjectHostSetup[], - derived: ProjectHostSetupProjection -): NormalizedProjectHostSetupProjection { - const repoById = new Map(repos.map((repo) => [repo.id, repo])) - const derivedProjectIds = new Set(derived.projects.map((project) => project.id)) - const projectIdByHydratedProjectId = new Map() - let changed = false - const normalizedSetups = setups.map((setup) => { - const repo = repoById.get(setup.repoId) ?? repoById.get(setup.id) - if (!repo) { - return setup - } - const projectId = getProjectIdentityKey(repo) - if (projectId === setup.projectId || projectId === `repo:${repo.id}`) { - return setup - } - changed = true - projectIdByHydratedProjectId.set(setup.projectId, projectId) - return { ...setup, projectId } - }) - const normalizedProjects = projects.flatMap((project) => { - const projectId = projectIdByHydratedProjectId.get(project.id) - if (!projectId || projectId === project.id) { - return [project] - } - // Why: runtime-hosted copies of the same Git repo may hydrate path-scoped - // project ids. If the repo-derived project already exists, keep that bucket - // authoritative so VM copies group under the user's single project. - if (derivedProjectIds.has(projectId)) { - changed = true - return [] - } - changed = true - return [{ ...project, id: projectId }] - }) - return { projects: normalizedProjects, setups: normalizedSetups, changed } -} diff --git a/src/renderer/src/store/project-host-setup-selector.ts b/src/renderer/src/store/project-host-setup-selector.ts index f56b3a6e0b3..ca51a218695 100644 --- a/src/renderer/src/store/project-host-setup-selector.ts +++ b/src/renderer/src/store/project-host-setup-selector.ts @@ -3,226 +3,55 @@ import { projectHostSetupProjectionFromRepos, type ProjectHostSetupProjection } from '../../../shared/project-host-setup-projection' +import { resolveProjectHostSetupProjection } from '../../../shared/project-host-setup-hydration' import type { AppState } from './types' -import { normalizeHydratedProjectHostSetupProjection } from './project-host-setup-selector-normalization' -const projectHostSetupProjectionCache = new WeakMap() -const providedProjectHostSetupProjectionCache = new WeakMap< - Project[], - WeakMap ->() -const mergedProjectHostSetupProjectionCache = new WeakMap< - AppState['repos'], - WeakMap> ->() -const normalizedProjectHostSetupProjectionCache = new WeakMap< - AppState['repos'], - WeakMap> ->() +const derivedProjectionByRepos = new WeakMap() // Catalog writers replace these readonly arrays; their identities are the invalidation boundary. -type StateProjectHostSetupProjectionCache = { - repos: AppState['repos'] - projects: AppState['projects'] - setups: AppState['projectHostSetups'] - projection: ProjectHostSetupProjection -} +const resolvedProjectionByCatalog = new WeakMap< + AppState['repos'], + WeakMap> +>() -let stateProjectHostSetupProjectionCache: StateProjectHostSetupProjectionCache | null = null - -function getCachedStateProjectHostSetupProjection( - repos: AppState['repos'], - projects: AppState['projects'], - setups: AppState['projectHostSetups'] -): ProjectHostSetupProjection | undefined { - const cached = stateProjectHostSetupProjectionCache - return cached && - cached.repos === repos && - cached.projects === projects && - cached.setups === setups - ? cached.projection - : undefined -} - -function cacheStateProjectHostSetupProjection( - repos: AppState['repos'], - projects: AppState['projects'], - setups: AppState['projectHostSetups'], - projection: ProjectHostSetupProjection -): void { - stateProjectHostSetupProjectionCache = { repos, projects, setups, projection } -} - -function getCachedProjectHostSetupProjection(repos: AppState['repos']): ProjectHostSetupProjection { - const cachedProjection = projectHostSetupProjectionCache.get(repos) - if (cachedProjection) { - return cachedProjection +function getDerivedProjection(repos: AppState['repos']): ProjectHostSetupProjection { + const cached = derivedProjectionByRepos.get(repos) + if (cached) { + return cached } - const projection = projectHostSetupProjectionFromRepos(repos) - projectHostSetupProjectionCache.set(repos, projection) - return projection -} - -function getCachedProvidedProjectHostSetupProjection( - projects: Project[], - setups: ProjectHostSetup[] -): ProjectHostSetupProjection { - const cachedBySetups = providedProjectHostSetupProjectionCache.get(projects) - const cachedProjection = cachedBySetups?.get(setups) - if (cachedProjection) { - return cachedProjection - } - - const projection = { projects, setups } - const nextCachedBySetups = - cachedBySetups ?? new WeakMap() - nextCachedBySetups.set(setups, projection) - if (!cachedBySetups) { - providedProjectHostSetupProjectionCache.set(projects, nextCachedBySetups) - } - return projection -} - -function mergeById(base: readonly T[], overlay: readonly T[]): T[] { - const merged = [...base] - const indexById = new Map(merged.map((entry, index) => [entry.id, index])) - for (const entry of overlay) { - const index = indexById.get(entry.id) - if (index === undefined) { - indexById.set(entry.id, merged.length) - merged.push(entry) - } else { - merged[index] = entry - } - } - return merged -} - -function mergeProjectHostSetupProjection( - repos: AppState['repos'], - projects: Project[], - setups: ProjectHostSetup[] -): ProjectHostSetupProjection { - const cachedByProjects = mergedProjectHostSetupProjectionCache.get(repos) - const cachedBySetups = cachedByProjects?.get(projects) - const cachedProjection = cachedBySetups?.get(setups) - if (cachedProjection) { - return cachedProjection - } - const derived = getCachedProjectHostSetupProjection(repos) - const normalized = normalizeHydratedProjectHostSetupProjection(repos, projects, setups, derived) - // Why: older runtimes/profiles may hydrate empty or partial project/setup arrays - // beside legacy repos. Keep repo-backed compatibility rows visible in that case. - const projection = { - projects: mergeById(derived.projects, normalized.projects), - setups: mergeById(derived.setups, normalized.setups) - } - const nextCachedByProjects = - cachedByProjects ?? - new WeakMap>() - const nextCachedBySetups = - cachedBySetups ?? new WeakMap() - nextCachedBySetups.set(setups, projection) - if (!cachedBySetups) { - nextCachedByProjects.set(projects, nextCachedBySetups) - } - if (!cachedByProjects) { - mergedProjectHostSetupProjectionCache.set(repos, nextCachedByProjects) - } - return projection -} - -function getCachedNormalizedProjectHostSetupProjection( - repos: AppState['repos'], - projects: Project[], - setups: ProjectHostSetup[], - derived: ProjectHostSetupProjection, - normalized: ProjectHostSetupProjection -): ProjectHostSetupProjection { - const cachedByProjects = normalizedProjectHostSetupProjectionCache.get(repos) - const cachedBySetups = cachedByProjects?.get(projects) - const cachedProjection = cachedBySetups?.get(setups) - if (cachedProjection) { - return cachedProjection - } - const projection = { - projects: mergeById(derived.projects, normalized.projects), - setups: mergeById(derived.setups, normalized.setups) - } - const nextCachedByProjects = - cachedByProjects ?? - new WeakMap>() - const nextCachedBySetups = - cachedBySetups ?? new WeakMap() - nextCachedBySetups.set(setups, projection) - if (!cachedBySetups) { - nextCachedByProjects.set(projects, nextCachedBySetups) - } - if (!cachedByProjects) { - normalizedProjectHostSetupProjectionCache.set(repos, nextCachedByProjects) - } + derivedProjectionByRepos.set(repos, projection) return projection } +// Why: this is a zustand selector compared with Object.is, so the result must be +// reference-stable per (repos, projects, setups) input or every render re-renders. export function getProjectHostSetupProjectionFromState( state: Pick & Partial> ): ProjectHostSetupProjection { - const projects = state.projects - const projectHostSetups = state.projectHostSetups - if (projects && projectHostSetups) { - const cachedProjection = getCachedStateProjectHostSetupProjection( - state.repos, - projects, - projectHostSetups - ) - if (cachedProjection) { - return cachedProjection - } - - const repoIds = new Set(state.repos.map((repo) => repo.id)) - const coveredRepoIds = new Set() - for (const setup of projectHostSetups) { - const repoId = typeof setup.repoId === 'string' ? setup.repoId : '' - if (repoIds.has(repoId)) { - coveredRepoIds.add(repoId) - } - if (repoIds.has(setup.id)) { - coveredRepoIds.add(setup.id) - } - } - let projection: ProjectHostSetupProjection - if (state.repos.length > 0 && coveredRepoIds.size < repoIds.size) { - projection = mergeProjectHostSetupProjection( - state.repos, - projects as Project[], - projectHostSetups as ProjectHostSetup[] - ) - } else { - const derived = getCachedProjectHostSetupProjection(state.repos) - const normalized = normalizeHydratedProjectHostSetupProjection( - state.repos, - projects as Project[], - projectHostSetups as ProjectHostSetup[], - derived - ) - // Why: this is a zustand selector compared with Object.is, so the merged - // result must be reference-stable per (repos, projects, setups) input or - // every render returns a fresh object and triggers a re-render storm. - projection = normalized.changed - ? getCachedNormalizedProjectHostSetupProjection( - state.repos, - projects as Project[], - projectHostSetups as ProjectHostSetup[], - derived, - normalized - ) - : getCachedProvidedProjectHostSetupProjection( - projects as Project[], - projectHostSetups as ProjectHostSetup[] - ) - } - cacheStateProjectHostSetupProjection(state.repos, projects, projectHostSetups, projection) - return projection + const { repos, projects, projectHostSetups: setups } = state + if (!projects || !setups) { + return getDerivedProjection(repos) } - return getCachedProjectHostSetupProjection(state.repos) + let byProjects = resolvedProjectionByCatalog.get(repos) + if (!byProjects) { + byProjects = new WeakMap() + resolvedProjectionByCatalog.set(repos, byProjects) + } + let bySetups = byProjects.get(projects) + if (!bySetups) { + bySetups = new WeakMap() + byProjects.set(projects, bySetups) + } + const cached = bySetups.get(setups) + if (cached) { + return cached + } + const projection = resolveProjectHostSetupProjection( + repos, + projects, + setups, + getDerivedProjection(repos) + ) + bySetups.set(setups, projection) + return projection } diff --git a/src/renderer/src/store/projects/project-host-routing.ts b/src/renderer/src/store/projects/project-host-routing.ts index ef51b1a86c5..2cac33a7503 100644 --- a/src/renderer/src/store/projects/project-host-routing.ts +++ b/src/renderer/src/store/projects/project-host-routing.ts @@ -8,10 +8,6 @@ import { projectHostSetupProjectionFromRepos, type ProjectHostSetupProjection } from '../../../../shared/project-host-setup-projection' -import { - normalizeProjectHostSetupRows, - normalizeProjectRows -} from '../../../../shared/project-catalog-row-normalization' import { PROJECT_HOST_SETUP_RUNTIME_CAPABILITY, WORKSPACE_RUN_CONTEXT_RUNTIME_CAPABILITY @@ -22,7 +18,7 @@ import { callRuntimeRpc } from '../../runtime/runtime-rpc-client' import type { RuntimeClientTarget } from '../../../../shared/runtime-client-target' -import { adoptFromEndpoint } from '../adopt-from-endpoint' +import { adoptEndpointProjectCatalog } from '../../../../shared/endpoint-catalog-adoption' export function getProjectSetupRuntimeTarget( hostId: ProjectHostSetupExistingFolderArgs['hostId'] @@ -33,15 +29,6 @@ export function getProjectSetupRuntimeTarget( : { kind: 'local' } } -function normalizeProjectCatalogProjection( - projection: ProjectHostSetupProjection -): ProjectHostSetupProjection { - return { - projects: normalizeProjectRows([...projection.projects]), - setups: normalizeProjectHostSetupRows([...projection.setups]) - } -} - async function assertProjectHostSetupRuntimeCapability(target: RuntimeClientTarget): Promise { if (target.kind !== 'environment') { return @@ -71,10 +58,11 @@ export async function fetchProjectHostSetupCompatibility( if (!projectsApi?.list || !projectsApi.listHostSetups) { throw new Error('projects_api_unavailable') } - return normalizeProjectCatalogProjection({ - projects: await projectsApi.list(), - setups: await projectsApi.listHostSetups() - }) + return adoptEndpointProjectCatalog( + target, + await projectsApi.list(), + await projectsApi.listHostSetups() + ) } await assertProjectHostSetupRuntimeCapability(target) const [projectResponse, setupResponse] = await Promise.all([ @@ -85,14 +73,7 @@ export async function fetchProjectHostSetupCompatibility( timeoutMs: 15_000 }) ]) - return { - // Why projects too: the same wire response carries them, and a remote host on another - // Orca version can publish a row whose declared field types do not hold. - projects: normalizeProjectRows([...projectResponse.projects]), - setups: setupResponse.setups.map((setup) => - adoptFromEndpoint(target, { kind: 'projectHostSetup', row: setup }) - ) - } + return adoptEndpointProjectCatalog(target, projectResponse.projects, setupResponse.setups) } catch { // Why: newer clients must hydrate against older runtimes that only know repo.list; derive the transitional model locally. return projectHostSetupProjectionFromRepos(repos) diff --git a/src/renderer/src/store/projects/project-update.ts b/src/renderer/src/store/projects/project-update.ts index 7bdb858efd7..8855c8cfeb9 100644 --- a/src/renderer/src/store/projects/project-update.ts +++ b/src/renderer/src/store/projects/project-update.ts @@ -5,7 +5,7 @@ import { callRuntimeRpc } from '../../runtime/runtime-rpc-client' import { notifyInstalledAgentSkillsChanged } from '@/hooks/installed-agent-skill-discovery' import type { RepoSlice } from '../repos/repo-state' import { getProjectSetupRuntimeTarget } from './project-host-routing' -import { mergeUpdatedProjectCompatibilityProject } from './project-compatibility-core' +import { mergeUpdatedProjectCompatibilityProject } from '../../../../shared/project-compatibility-merge' import { normalizeProjectRow } from '../../../../shared/project-catalog-row-normalization' export function createProjectUpdateActions( diff --git a/src/renderer/src/store/repos/all-host-repo-catalog-actions.ts b/src/renderer/src/store/repos/all-host-repo-catalog-actions.ts index f9b8989cf3e..90ee84f35ab 100644 --- a/src/renderer/src/store/repos/all-host-repo-catalog-actions.ts +++ b/src/renderer/src/store/repos/all-host-repo-catalog-actions.ts @@ -27,12 +27,11 @@ import { filterSetupsForPrunedRepoRows, filterTrustedOrcaHooksToValidRepos, mergeFetchedRepoCatalog, - projectCompatibilityForReconciledRepos, reconcileReadoptedSshWorktreeState, reconcileSupersededSshRepos } from './repo-catalog-merge' import { getRuntimeTargetHostId } from '../../runtime/runtime-client-target' -import { mergeFetchedProjectCompatibilityForHost } from '../projects/project-compatibility-host-merge' +import { mergeFetchedHostProjectCatalog } from '../../../../shared/project-compatibility-host-merge' import { scheduleSafeAutoForkSync } from './safe-auto-fork-sync' export function createAllHostRepoCatalogActions( @@ -67,11 +66,7 @@ export function createAllHostRepoCatalogActions( const result = mergeFetchedRepoCatalog(catalog, s.repos) const reconciliation = reconcileSupersededSshRepos(result.repos, s) const finalizedRepos = applyManualRepoOrder(reconciliation.repos, s.manualRepoOrder) - const projectCompatibility = projectCompatibilityForReconciledRepos( - finalizedRepos, - catalog.projectHostSetupCompatibility - ) - const mergedProjectCompatibility = mergeFetchedProjectCompatibilityForHost({ + const mergedProjectCompatibility = mergeFetchedHostProjectCatalog({ previous: { projects: s.projects, projectHostSetups: filterSetupsForPrunedRepoRows( @@ -80,7 +75,7 @@ export function createAllHostRepoCatalogActions( finalizedRepos ) }, - fetched: projectCompatibility, + fetched: catalog.projectHostSetupCompatibility, repos: finalizedRepos, hostId: result.hostId }) diff --git a/src/renderer/src/store/repos/repo-catalog-actions.ts b/src/renderer/src/store/repos/repo-catalog-actions.ts index 75c304b978f..10b65166d71 100644 --- a/src/renderer/src/store/repos/repo-catalog-actions.ts +++ b/src/renderer/src/store/repos/repo-catalog-actions.ts @@ -2,7 +2,7 @@ import type { StateCreator } from 'zustand' import type { AppState } from '../types' import type { Repo } from '../../../../shared/repo-types' import { applyManualRepoOrder } from '../../../../shared/manual-repo-order' -import { reconcileCatalogRows } from '../slices/repo-identity-reconcile' +import { reconcileCatalogRows } from '../../../../shared/catalog-row-reconcile' import { retainValidFilterRepoIds } from '../slices/repo-filter-selection' import { mergeSshRepoReadoptions, @@ -25,7 +25,6 @@ import { fetchRepoCatalogForTarget, filterSetupsForPrunedRepoRows, mergeFetchedRepoCatalog, - projectCompatibilityForReconciledRepos, reconcileReadoptedSshWorktreeState, reconcileSupersededSshRepos } from './repo-catalog-merge' @@ -33,8 +32,8 @@ import { getProjectHostSetupOwnerKey, mergeProjectHostSetupCompatibility, projectCompatibilityFromRepos -} from '../projects/project-compatibility-core' -import { mergeFetchedProjectCompatibilityForHost } from '../projects/project-compatibility-host-merge' +} from '../../../../shared/project-compatibility-merge' +import { mergeFetchedHostProjectCatalog } from '../../../../shared/project-compatibility-host-merge' import { scheduleSafeAutoForkSync } from './safe-auto-fork-sync' export function createRepoCatalogActions( @@ -65,7 +64,7 @@ export function createRepoCatalogActions( ) // Why: mergeProjectHostSetupCompatibility always allocates; a no-op readoption // must not churn catalog identity. This write is all-repos, not host-scoped, - // so it cannot go through mergeFetchedProjectCompatibilityForHost. Reconcile + // so it cannot go through mergeFetchedHostProjectCatalog. Reconcile // hands the store arrays straight back when nothing moved, so writing them // unconditionally still leaves identity-keyed selectors untouched. const projects = reconcileCatalogRows( @@ -113,11 +112,7 @@ export function createRepoCatalogActions( const prunedRepos = applyManualRepoOrder(reconciliation.repos, s.manualRepoOrder) const validRepoIds = new Set(prunedRepos.map((repo) => repo.id)) const validRepoHostIdentities = new Set(prunedRepos.map(getRepoHostIdentity)) - const projectCompatibility = projectCompatibilityForReconciledRepos( - prunedRepos, - catalog.projectHostSetupCompatibility - ) - const mergedProjectCompatibility = mergeFetchedProjectCompatibilityForHost({ + const mergedProjectCompatibility = mergeFetchedHostProjectCatalog({ previous: { projects: s.projects, projectHostSetups: filterSetupsForPrunedRepoRows( @@ -126,7 +121,7 @@ export function createRepoCatalogActions( prunedRepos ) }, - fetched: projectCompatibility, + fetched: catalog.projectHostSetupCompatibility, repos: prunedRepos, hostId: result.hostId }) diff --git a/src/renderer/src/store/repos/repo-catalog-identity.ts b/src/renderer/src/store/repos/repo-catalog-identity.ts index a5c838f630a..ee94fc5a537 100644 --- a/src/renderer/src/store/repos/repo-catalog-identity.ts +++ b/src/renderer/src/store/repos/repo-catalog-identity.ts @@ -4,8 +4,8 @@ import { reconcileFetchedRepos } from '../slices/repo-identity-reconcile' import { getRepoHostIdentity } from '../slices/repo-host-identity' import { getRepoExecutionHostId } from '../../../../shared/execution-host' import type { RepoSlice } from './repo-state' -import { mergeFetchedProjectCompatibilityForHost } from '../projects/project-compatibility-host-merge' -import { projectCompatibilityFromRepos } from '../projects/project-compatibility-core' +import { mergeFetchedProjectCompatibilityForHost } from '../../../../shared/project-compatibility-host-merge' +import { projectCompatibilityFromRepos } from '../../../../shared/project-compatibility-merge' import { mergeByIdentity } from '../catalog-identity' export function mergeFetchedReposForHost( diff --git a/src/renderer/src/store/repos/repo-catalog-merge.ts b/src/renderer/src/store/repos/repo-catalog-merge.ts index ce6b6134b58..6f1a2fa7431 100644 --- a/src/renderer/src/store/repos/repo-catalog-merge.ts +++ b/src/renderer/src/store/repos/repo-catalog-merge.ts @@ -9,15 +9,10 @@ import { reconcileReadoptedSshWorktreesByRepo } from '../slices/readopted-ssh-wo import { callRuntimeRpc } from '../../runtime/runtime-rpc-client' import type { RuntimeClientTarget } from '../../../../shared/runtime-client-target' import { getRepoExecutionHostId } from '../../../../shared/execution-host' -import type { RepoSlice } from './repo-state' import { adoptFromEndpoint } from '../adopt-from-endpoint' import { getRuntimeTargetHostId } from '../../runtime/runtime-client-target' import { fetchProjectHostSetupCompatibility } from '../projects/project-host-routing' import { mergeFetchedReposForHost } from './repo-catalog-identity' -import { - mergeProjectHostSetupCompatibility, - projectCompatibilityFromRepos -} from '../projects/project-compatibility-core' export type FetchedRepoCatalog = { repos: readonly Repo[] @@ -120,13 +115,6 @@ export function reconcileReadoptedSshWorktreeState( } } -export function projectCompatibilityForReconciledRepos( - repos: readonly Repo[], - fetched: ProjectHostSetupProjection -): Pick { - return mergeProjectHostSetupCompatibility(projectCompatibilityFromRepos(repos), fetched) -} - export function filterTrustedOrcaHooksToValidRepos( trust: AppState['trustedOrcaHooks'], validRepoIds: Set diff --git a/src/renderer/src/store/repos/runtime-repo-catalog-actions.ts b/src/renderer/src/store/repos/runtime-repo-catalog-actions.ts index 5b2ce12be0f..ed9ea109d46 100644 --- a/src/renderer/src/store/repos/runtime-repo-catalog-actions.ts +++ b/src/renderer/src/store/repos/runtime-repo-catalog-actions.ts @@ -15,12 +15,11 @@ import { fetchRepoCatalogForTarget, filterSetupsForPrunedRepoRows, mergeFetchedRepoCatalog, - projectCompatibilityForReconciledRepos, reconcileReadoptedSshWorktreeState, reconcileSupersededSshRepos } from './repo-catalog-merge' import { getRuntimeTargetHostId } from '../../runtime/runtime-client-target' -import { mergeFetchedProjectCompatibilityForHost } from '../projects/project-compatibility-host-merge' +import { mergeFetchedHostProjectCatalog } from '../../../../shared/project-compatibility-host-merge' import { scheduleSafeAutoForkSync } from './safe-auto-fork-sync' export function createRuntimeRepoCatalogActions( @@ -56,11 +55,7 @@ export function createRuntimeRepoCatalogActions( const finalizedRepos = applyManualRepoOrder(reconciliation.repos, s.manualRepoOrder) const validRepoIds = new Set(finalizedRepos.map((repo) => repo.id)) const validRepoHostIdentities = new Set(finalizedRepos.map(getRepoHostIdentity)) - const projectCompatibility = projectCompatibilityForReconciledRepos( - finalizedRepos, - catalog.projectHostSetupCompatibility - ) - const mergedProjectCompatibility = mergeFetchedProjectCompatibilityForHost({ + const mergedProjectCompatibility = mergeFetchedHostProjectCatalog({ previous: { projects: s.projects, projectHostSetups: filterSetupsForPrunedRepoRows( @@ -69,7 +64,7 @@ export function createRuntimeRepoCatalogActions( finalizedRepos ) }, - fetched: projectCompatibility, + fetched: catalog.projectHostSetupCompatibility, repos: finalizedRepos, hostId: result.hostId }) diff --git a/src/renderer/src/store/slices/repo-identity-reconcile.ts b/src/renderer/src/store/slices/repo-identity-reconcile.ts index 0e2b4aa1147..a5c564482aa 100644 --- a/src/renderer/src/store/slices/repo-identity-reconcile.ts +++ b/src/renderer/src/store/slices/repo-identity-reconcile.ts @@ -1,6 +1,7 @@ import type { Repo } from '../../../../shared/repo-types' import { structuralValuesEqual } from '../../../../shared/structural-value-equality' import { getRepoHostIdentity } from './repo-host-identity' +import { reconcileCatalogRows } from '../../../../shared/catalog-row-reconcile' // Why: after a drag-reorder we optimistically set `repos`, persist, and main // broadcasts `repos:changed`. The renderer's own echo handler refetches, which @@ -13,32 +14,6 @@ import { getRepoHostIdentity } from './repo-host-identity' // gitRemoteIdentity, repoIcon, path arrays) that structured-clone and main's hydrateRepo rebuild // every fetch, so a reference compare would report every repo as changed. -/** - * Reuses equal rows from `previous` — and the whole array when nothing moved — so a refetch that - * changed nothing leaves identity-keyed memos and store subscribers untouched. `getIdentity` must - * be the key the producing merge already dedups by, so it is unique within `next`. - */ -export function reconcileCatalogRows( - previous: readonly T[], - next: readonly T[], - getIdentity: (row: T) => string -): readonly T[] { - const previousByIdentity = new Map(previous.map((row) => [getIdentity(row), row])) - let identical = next.length === previous.length - const reconciled = next.map((row, index) => { - const existing = previousByIdentity.get(getIdentity(row)) - if (existing !== undefined && structuralValuesEqual(existing, row)) { - if (existing !== previous[index]) { - identical = false - } - return existing - } - identical = false - return row - }) - return identical ? previous : reconciled -} - export function reconcileFetchedRepos( previous: readonly Repo[], next: readonly Repo[] diff --git a/src/renderer/src/store/slices/runtime-status.ts b/src/renderer/src/store/slices/runtime-status.ts index 25804801467..48563a8ab90 100644 --- a/src/renderer/src/store/slices/runtime-status.ts +++ b/src/renderer/src/store/slices/runtime-status.ts @@ -15,7 +15,7 @@ import { dismissRuntimeDisconnectedToast, showRuntimeDisconnectedToast } from './runtime-environment-disconnect-toast' -import { reconcileCatalogRows } from './repo-identity-reconcile' +import { reconcileCatalogRows } from '../../../../shared/catalog-row-reconcile' import { createRuntimeStatusHydration } from './runtime-status-hydration' import { refreshRuntimeEnvironmentStatus } from './runtime-status-refresh' import * as runtimeStatusConnectionGeneration from './runtime-status-connection-generation' diff --git a/src/shared/catalog-row-reconcile.ts b/src/shared/catalog-row-reconcile.ts new file mode 100644 index 00000000000..262a913e738 --- /dev/null +++ b/src/shared/catalog-row-reconcile.ts @@ -0,0 +1,27 @@ +import { structuralValuesEqual } from './structural-value-equality' + +/** + * Reuses equal rows from `previous` — and the whole array when nothing moved — so a refetch that + * changed nothing leaves identity-keyed memos and store subscribers untouched. `getIdentity` must + * be the key the producing merge already dedups by, so it is unique within `next`. + */ +export function reconcileCatalogRows( + previous: readonly T[], + next: readonly T[], + getIdentity: (row: T) => string +): readonly T[] { + const previousByIdentity = new Map(previous.map((row) => [getIdentity(row), row])) + let identical = next.length === previous.length + const reconciled = next.map((row, index) => { + const existing = previousByIdentity.get(getIdentity(row)) + if (existing !== undefined && structuralValuesEqual(existing, row)) { + if (existing !== previous[index]) { + identical = false + } + return existing + } + identical = false + return row + }) + return identical ? previous : reconciled +} diff --git a/src/shared/endpoint-catalog-adoption.ts b/src/shared/endpoint-catalog-adoption.ts new file mode 100644 index 00000000000..d6c4ed4df55 --- /dev/null +++ b/src/shared/endpoint-catalog-adoption.ts @@ -0,0 +1,68 @@ +// The host stamp rows take entering a client's all-host catalog; shared so the phone matches the desktop. +import { + getRepoExecutionHostId, + LOCAL_EXECUTION_HOST_ID, + toRuntimeExecutionHostId +} from './execution-host' +import { + normalizeProjectHostSetupRow, + normalizeProjectHostSetupRows, + normalizeProjectRows +} from './project-catalog-row-normalization' +import type { ProjectHostSetupProjection } from './project-host-setup-projection' +import type { Project, ProjectHostSetup } from './project-types' +import type { Repo } from './repo-types' +import type { RuntimeClientTarget } from './runtime-client-target' + +export function adoptEndpointRepo(target: RuntimeClientTarget, repo: Repo): Repo { + if (target.kind === 'environment') { + // `connectionId` survives: on a server's row it names that server's nested SSH target. + return { ...repo, executionHostId: toRuntimeExecutionHostId(target.environmentId) } + } + if (repo.connectionId) { + return { ...repo, executionHostId: getRepoExecutionHostId(repo) } + } + return repo.executionHostId ? repo : { ...repo, executionHostId: LOCAL_EXECUTION_HOST_ID } +} + +export function adoptEndpointProjectHostSetup( + target: RuntimeClientTarget, + setup: ProjectHostSetup +): ProjectHostSetup { + // Why here: every setup row crossing IPC or RPC into a client passes through this + // adoption step, so it is where the declared field types stop being aspirational. + const adopted = normalizeProjectHostSetupRow(setup) + if (target.kind !== 'environment') { + return adopted + } + const hostId = toRuntimeExecutionHostId(target.environmentId) + const executionHostId = adopted.executionHostId ?? adopted.hostId + return { + ...adopted, + hostId, + executionHostId: executionHostId === LOCAL_EXECUTION_HOST_ID ? hostId : executionHostId, + runtimeOwnerEnvironmentId: target.environmentId, + // Why: paired clients route through the HUB and must not treat its private SSH target as client-local configuration. + connectionId: null + } +} + +/** An endpoint's `project.list` and `projectHostSetup.list` answers as a client's projection. */ +export function adoptEndpointProjectCatalog( + target: RuntimeClientTarget, + projects: readonly Project[], + setups: readonly ProjectHostSetup[] +): ProjectHostSetupProjection { + if (target.kind === 'local') { + return { + projects: normalizeProjectRows([...projects]), + setups: normalizeProjectHostSetupRows([...setups]) + } + } + return { + // Why projects too: the same wire response carries them, and a remote host on another + // Orca version can publish a row whose declared field types do not hold. + projects: normalizeProjectRows([...projects]), + setups: setups.map((setup) => adoptEndpointProjectHostSetup(target, setup)) + } +} diff --git a/src/shared/mobile-project-sections-contract.ts b/src/shared/mobile-project-sections-contract.ts new file mode 100644 index 00000000000..b24bb6a7304 --- /dev/null +++ b/src/shared/mobile-project-sections-contract.ts @@ -0,0 +1,64 @@ +import type { ProjectHostSetupProjection } from './project-host-setup-projection' +import type { ProjectSectionProject, ProjectSectionSetup } from './project-section-grouping' +import type { RepoIcon } from './repo-icon' +import type { Repo } from './repo-types' +import type { ProjectOrderBy } from './ui-chrome-types' + +// Why the desktop answers it: its sidebar's own grouping inputs, so the phone's Project sections match. +export const MOBILE_PROJECT_SECTIONS_METHOD = 'mobileRelay.projectSections' + +/** A sidebar repo as Project sections read it; never its hooks, credentials or AI settings. */ +export type MobileProjectSectionRepo = Pick & + Partial> & { repoIcon?: RepoIcon } + +export type MobileProjectSectionCatalog = { + /** Every repo the sidebar holds, across hosts, in its header order. */ + repos: MobileProjectSectionRepo[] + projects: ProjectSectionProject[] + setups: ProjectSectionSetup[] + projectOrderBy: ProjectOrderBy +} + +/** Null until a desktop window has published its sidebar catalog. */ +export type MobileProjectSectionsResult = { catalog: MobileProjectSectionCatalog | null } + +function sectionRepo(repo: Repo): MobileProjectSectionRepo { + return { + id: repo.id, + displayName: repo.displayName, + path: repo.path, + badgeColor: repo.badgeColor, + addedAt: repo.addedAt, + ...(repo.connectionId !== undefined ? { connectionId: repo.connectionId } : {}), + ...(repo.executionHostId ? { executionHostId: repo.executionHostId } : {}), + // Why not uploaded icons: a data URL can be hundreds of KB per repo on every read. + ...(repo.repoIcon && !(repo.repoIcon.type === 'image' && repo.repoIcon.src.startsWith('data:')) + ? { repoIcon: repo.repoIcon } + : {}) + } +} + +/** The sidebar's repos, in its order, and the projection its project-header selector produced. */ +export function buildMobileProjectSectionCatalog( + repos: readonly Repo[], + projection: ProjectHostSetupProjection, + projectOrderBy: ProjectOrderBy +): MobileProjectSectionCatalog { + return { + repos: repos.map(sectionRepo), + projects: projection.projects.map(({ id, displayName }) => ({ id, displayName })), + setups: projection.setups.map((setup) => ({ + id: setup.id, + projectId: setup.projectId, + hostId: setup.hostId, + repoId: setup.repoId, + path: setup.path, + displayName: setup.displayName, + setupMethod: setup.setupMethod, + ...(setup.kind ? { kind: setup.kind } : {}), + ...(setup.connectionId !== undefined ? { connectionId: setup.connectionId } : {}), + ...(setup.executionHostId ? { executionHostId: setup.executionHostId } : {}) + })), + projectOrderBy + } +} diff --git a/src/renderer/src/store/projects/project-compatibility-host-merge.ts b/src/shared/project-compatibility-host-merge.ts similarity index 87% rename from src/renderer/src/store/projects/project-compatibility-host-merge.ts rename to src/shared/project-compatibility-host-merge.ts index 8c51cbe9a4b..e37b7104eea 100644 --- a/src/renderer/src/store/projects/project-compatibility-host-merge.ts +++ b/src/shared/project-compatibility-host-merge.ts @@ -1,21 +1,24 @@ -import type { Project, ProjectHostSetup } from '../../../../shared/project-types' -import type { Repo } from '../../../../shared/repo-types' -import { reconcileCatalogRows } from '../slices/repo-identity-reconcile' +import type { Project, ProjectHostSetup } from './project-types' +import type { Repo } from './repo-types' +import { reconcileCatalogRows } from './catalog-row-reconcile' import { getRepoExecutionHostId, LOCAL_EXECUTION_HOST_ID, parseExecutionHostId -} from '../../../../shared/execution-host' -import type { RepoSlice } from '../repos/repo-state' +} from './execution-host' import { + type ProjectCatalogRows, getProjectHostSetupOwnerKey, getReposById, getSourceRepoIdsOutsideHost, mergePreviousProjectMetadata, mergeProjectCompatibilityProjects, + mergeProjectHostSetupCompatibility, mergeProjectHostSetupsByOwner, + projectCompatibilityFromRepos, projectWithCurrentSourceRepoIds -} from './project-compatibility-core' +} from './project-compatibility-merge' +import type { ProjectHostSetupProjection } from './project-host-setup-projection' export function getProjectHostIds( project: Project, @@ -130,11 +133,11 @@ export function mergeFetchedProjectCompatibilityForHost({ repos, hostId }: { - previous: Pick - fetched: Pick + previous: ProjectCatalogRows + fetched: ProjectCatalogRows repos: readonly Repo[] hostId: string -}): Pick { +}): ProjectCatalogRows { const setupBelongsToFetchedCatalog = (setup: ProjectHostSetup): boolean => { if (hostId !== LOCAL_EXECUTION_HOST_ID) { return setup.hostId === hostId @@ -224,3 +227,23 @@ export function mergeFetchedProjectCompatibilityForHost({ ) } } + +/** One host's fetched catalog folded into the all-host project rows, as each host refresh does. */ +export function mergeFetchedHostProjectCatalog({ + previous, + fetched, + repos, + hostId +}: { + previous: ProjectCatalogRows + fetched: ProjectHostSetupProjection + repos: readonly Repo[] + hostId: string +}): ProjectCatalogRows { + return mergeFetchedProjectCompatibilityForHost({ + previous, + fetched: mergeProjectHostSetupCompatibility(projectCompatibilityFromRepos(repos), fetched), + repos, + hostId + }) +} diff --git a/src/renderer/src/store/projects/project-compatibility-core.ts b/src/shared/project-compatibility-merge.ts similarity index 92% rename from src/renderer/src/store/projects/project-compatibility-core.ts rename to src/shared/project-compatibility-merge.ts index c0882c8e683..7cf7887e0c9 100644 --- a/src/renderer/src/store/projects/project-compatibility-core.ts +++ b/src/shared/project-compatibility-merge.ts @@ -1,17 +1,20 @@ -import type { Project, ProjectHostSetup } from '../../../../shared/project-types' -import type { Repo } from '../../../../shared/repo-types' +import type { Project, ProjectHostSetup, ProjectUpdateArgs } from './project-types' +import type { Repo } from './repo-types' import { mergeCatalogCreatedAt, mergeCatalogUpdatedAt, projectHostSetupProjectionFromRepos -} from '../../../../shared/project-host-setup-projection' -import type { ProjectHostSetupProjection } from '../../../../shared/project-host-setup-projection' -import { getRepoExecutionHostId, LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host' -import type { ProjectUpdate, RepoSlice } from '../repos/repo-state' +} from './project-host-setup-projection' +import type { ProjectHostSetupProjection } from './project-host-setup-projection' +import { getRepoExecutionHostId, LOCAL_EXECUTION_HOST_ID } from './execution-host' -export function projectCompatibilityFromRepos( - repos: readonly Repo[] -): Pick { +/** A catalog's project rows and the host setups that place them. */ +export type ProjectCatalogRows = { + projects: readonly Project[] + projectHostSetups: readonly ProjectHostSetup[] +} + +export function projectCompatibilityFromRepos(repos: readonly Repo[]): ProjectCatalogRows { const projection = projectHostSetupProjectionFromRepos(repos) return { projects: projection.projects, @@ -63,7 +66,7 @@ export function mergeProjectCompatibilityProjects( export function mergeUpdatedProjectCompatibilityProject( base: Project, updated: Project, - updates: ProjectUpdate + updates: ProjectUpdateArgs['updates'] ): Project { const project = mergeProjectCompatibilityProject(base, updated) if ('localWindowsRuntimePreference' in updates) { @@ -189,9 +192,9 @@ export function mergePreviousProjectMetadata( } export function mergeProjectHostSetupCompatibility( - derived: Pick, + derived: ProjectCatalogRows, fetched: ProjectHostSetupProjection -): Pick { +): ProjectCatalogRows { const fetchedRepoSetupKeys = new Set(fetched.setups.map(getRepoDerivedSetupKey)) const derivedSetups = derived.projectHostSetups.filter( (setup) => !fetchedRepoSetupKeys.has(getRepoDerivedSetupKey(setup)) diff --git a/src/shared/project-host-setup-hydration.ts b/src/shared/project-host-setup-hydration.ts new file mode 100644 index 00000000000..f1c8cf1bb11 --- /dev/null +++ b/src/shared/project-host-setup-hydration.ts @@ -0,0 +1,120 @@ +import type { Project, ProjectHostSetup } from './project-types' +import type { Repo } from './repo-types' +import { + getProjectIdentityKey, + projectHostSetupProjectionFromRepos, + type ProjectHostSetupProjection +} from './project-host-setup-projection' + +type NormalizedProjectHostSetupProjection = ProjectHostSetupProjection & { + changed: boolean +} + +function normalizeHydratedProjectHostSetupProjection( + repos: readonly Repo[], + projects: readonly Project[], + setups: readonly ProjectHostSetup[], + derived: ProjectHostSetupProjection +): NormalizedProjectHostSetupProjection { + const repoById = new Map(repos.map((repo) => [repo.id, repo])) + const derivedProjectIds = new Set(derived.projects.map((project) => project.id)) + const projectIdByHydratedProjectId = new Map() + let changed = false + const normalizedSetups = setups.map((setup) => { + const repo = repoById.get(setup.repoId) ?? repoById.get(setup.id) + if (!repo) { + return setup + } + const projectId = getProjectIdentityKey(repo) + if (projectId === setup.projectId || projectId === `repo:${repo.id}`) { + return setup + } + changed = true + projectIdByHydratedProjectId.set(setup.projectId, projectId) + return { ...setup, projectId } + }) + const normalizedProjects = projects.flatMap((project) => { + const projectId = projectIdByHydratedProjectId.get(project.id) + if (!projectId || projectId === project.id) { + return [project] + } + // Why: runtime-hosted copies of the same Git repo may hydrate path-scoped + // project ids. If the repo-derived project already exists, keep that bucket + // authoritative so VM copies group under the user's single project. + if (derivedProjectIds.has(projectId)) { + changed = true + return [] + } + changed = true + return [{ ...project, id: projectId }] + }) + return { projects: normalizedProjects, setups: normalizedSetups, changed } +} + +/** `overlay` rows replace `base` rows of the same id; new ids append in overlay order. */ +function mergeProjectionRowsById( + base: readonly T[], + overlay: readonly T[] +): T[] { + const merged = [...base] + const indexById = new Map(merged.map((entry, index) => [entry.id, index])) + for (const entry of overlay) { + const index = indexById.get(entry.id) + if (index === undefined) { + indexById.set(entry.id, merged.length) + merged.push(entry) + } else { + merged[index] = entry + } + } + return merged +} + +/** Whether some repo has no host setup naming it, by setup repo id or setup id. */ +function hasReposWithoutProjectHostSetup( + repos: readonly Pick[], + setups: readonly ProjectHostSetup[] +): boolean { + const repoIds = new Set(repos.map((repo) => repo.id)) + const coveredRepoIds = new Set() + for (const setup of setups) { + const repoId = typeof setup.repoId === 'string' ? setup.repoId : '' + if (repoIds.has(repoId)) { + coveredRepoIds.add(repoId) + } + if (repoIds.has(setup.id)) { + coveredRepoIds.add(setup.id) + } + } + return repos.length > 0 && coveredRepoIds.size < repoIds.size +} + +/** The repo-derived rows overlaid with the hydrated ones, so a repo without a setup still groups. */ +function mergeDerivedProjectHostSetupProjection( + derived: ProjectHostSetupProjection, + normalized: ProjectHostSetupProjection +): ProjectHostSetupProjection { + return { + projects: mergeProjectionRowsById(derived.projects, normalized.projects), + setups: mergeProjectionRowsById(derived.setups, normalized.setups) + } +} + +/** + * The projects and setups a catalog groups by: the hydrated rows, normalized onto the repos' + * identities, and backed by the repo-derived rows when any repo has no setup. The renderer's + * selector is a memo over this. + */ +export function resolveProjectHostSetupProjection( + repos: readonly Repo[], + projects: readonly Project[], + setups: readonly ProjectHostSetup[], + derived: ProjectHostSetupProjection = projectHostSetupProjectionFromRepos(repos) +): ProjectHostSetupProjection { + const normalized = normalizeHydratedProjectHostSetupProjection(repos, projects, setups, derived) + // Why: older runtimes/profiles may hydrate empty or partial project/setup arrays + // beside legacy repos. Keep repo-backed compatibility rows visible in that case. + return hasReposWithoutProjectHostSetup(repos, setups) || normalized.changed + ? mergeDerivedProjectHostSetupProjection(derived, normalized) + : { projects, setups } +} diff --git a/src/shared/project-section-grouping.ts b/src/shared/project-section-grouping.ts new file mode 100644 index 00000000000..b6132332a4e --- /dev/null +++ b/src/shared/project-section-grouping.ts @@ -0,0 +1,282 @@ +// The desktop sidebar's Project sections; the paired phone calls these same functions. +import type { ExecutionHostId } from './execution-host' +import { orderProjectSections, type ActivityItem } from './project-section-order' +import type { ProjectOrderBy } from './ui-chrome-types' +import { toSshExecutionHostId } from './execution-host' +import type { Project, ProjectHostSetup } from './project-types' +import type { Repo } from './repo-types' +import { parseWslUncPath } from './wsl-paths' +import { isWindowsAbsolutePathLike, normalizeRuntimePathForComparison } from './cross-platform-path' + +/** The project fields grouping reads. */ +export type ProjectSectionProject = Pick + +/** The host-setup fields grouping and host labels read. */ +export type ProjectSectionSetup = Pick< + ProjectHostSetup, + 'id' | 'projectId' | 'hostId' | 'repoId' | 'path' | 'displayName' | 'setupMethod' +> & + Partial> + +export type ProjectGroupingModel = { + projects: readonly Project[] + projectHostSetups: readonly ProjectHostSetup[] +} + +/** What grouping reads of a model; a paired phone receives only this much. */ +export type ProjectSectionModel = { + projects: readonly ProjectSectionProject[] + projectHostSetups: readonly ProjectSectionSetup[] +} + +export type ProjectGroupingIndex = { + projectById: Map + setupByRepoId: Map + surfaceKeysRequiringSetupGroups: Set +} + +/** The repo fields section grouping, labelling and ordering read. */ +export type ProjectSectionRepo = { + id: string + displayName: string + path: string + addedAt?: number + connectionId?: string | null + executionHostId?: ExecutionHostId | null +} + +/** One project section: its rows, the repo that anchors its header, and every repo it holds. */ +export type ProjectSectionEntry = { + label: string + items: Item[] + repo?: R + repoIds: Set +} + +const projectGroupingIndexCache = new WeakMap() + +// Why: provisioned and folder setups are not independent Git checkouts. +function isDistinctUserCheckout(setup: ProjectSectionSetup): boolean { + return setup.setupMethod !== 'provisioned' && setup.kind !== 'folder' +} + +// Why: execution target and filesystem namespace independently identify a surface. +function getProjectSetupSurfaceKey(setup: ProjectSectionSetup): string { + return `${setup.projectId}::${setup.hostId}::${getExecutionSurface(setup)}::${getPathSurface(setup)}` +} + +function getExecutionSurface(setup: ProjectSectionSetup): string { + const connectionId = setup.connectionId?.trim() + if (connectionId) { + return toSshExecutionHostId(connectionId) + } + return setup.executionHostId?.trim() || setup.hostId +} + +// Why: projection twins differ by row identity, not checkout directory. +function getCheckoutIdentity(setup: ProjectSectionSetup): string { + return normalizeRuntimePathForComparison(setup.path.trim()) || setup.repoId || setup.id +} + +function getPathSurface(setup: ProjectSectionSetup): string { + const wslPath = parseWslUncPath(setup.path) + if (wslPath) { + return `wsl:${wslPath.distro.toLowerCase()}` + } + if (isWindowsAbsolutePathLike(setup.path)) { + return 'windows-host' + } + return 'default' +} + +export function buildProjectGroupingIndex( + model?: ProjectSectionModel +): ProjectGroupingIndex | null { + if (!model) { + return null + } + const cached = projectGroupingIndexCache.get(model) + if (cached !== undefined) { + return cached + } + const projects = model.projects ?? [] + const projectHostSetups = model.projectHostSetups ?? [] + if (projects.length === 0 || projectHostSetups.length === 0) { + projectGroupingIndexCache.set(model, null) + return null + } + const checkoutsByProjectSurface = new Map>() + for (const setup of projectHostSetups) { + if (!isDistinctUserCheckout(setup)) { + continue + } + const key = getProjectSetupSurfaceKey(setup) + const existing = checkoutsByProjectSurface.get(key) + if (existing) { + existing.add(getCheckoutIdentity(setup)) + } else { + checkoutsByProjectSurface.set(key, new Set([getCheckoutIdentity(setup)])) + } + } + const surfaceKeysRequiringSetupGroups = new Set() + for (const [surfaceKey, checkouts] of checkoutsByProjectSurface) { + if (checkouts.size > 1) { + surfaceKeysRequiringSetupGroups.add(surfaceKey) + } + } + const index = { + projectById: new Map(projects.map((project) => [project.id, project])), + setupByRepoId: new Map(projectHostSetups.map((setup) => [setup.repoId, setup])), + surfaceKeysRequiringSetupGroups + } + projectGroupingIndexCache.set(model, index) + return index +} + +// Why: Settings splits projects by this same rule so it matches the sidebar headers. +export function isCheckoutScopedProjectSetup( + setup: ProjectSectionSetup, + projectIndex: ProjectGroupingIndex +): boolean { + return ( + projectIndex.surfaceKeysRequiringSetupGroups.has(getProjectSetupSurfaceKey(setup)) && + isDistinctUserCheckout(setup) + ) +} + +export type ProjectHeaderRevealTarget = Repo> = { + key: string + label: string + repo?: R + projectId?: string +} + +export function getProjectGroupingForRepo>( + repoId: string, + repoMap: ReadonlyMap, + projectIndex: ProjectGroupingIndex | null +): ProjectHeaderRevealTarget { + const repo = repoMap.get(repoId) + const setup = projectIndex?.setupByRepoId.get(repoId) + const project = setup ? projectIndex?.projectById.get(setup.projectId) : undefined + if (!setup || !project) { + return { + key: `repo:${repoId}`, + label: repo?.displayName ?? 'Unknown', + repo + } + } + if (projectIndex && isCheckoutScopedProjectSetup(setup, projectIndex)) { + // Why: only the ambiguous surface needs checkout-specific headers. + return { + key: `project:${project.id}::setup:${repoId}`, + label: repo?.displayName ?? setup.displayName, + repo, + projectId: project.id + } + } + // Why: provisioned runtime copies and non-ambiguous checkouts follow project + // identity rather than path-scoped setup identity, so they stay in one project. + return { + key: `project:${project.id}`, + label: project.displayName, + repo, + projectId: project.id + } +} + +export function getProjectHeaderRevealTarget( + repoId: string, + repoMap: Map, + projectGrouping?: ProjectGroupingModel +): ProjectHeaderRevealTarget { + return getProjectGroupingForRepo(repoId, repoMap, buildProjectGroupingIndex(projectGrouping)) +} + +/** Files `item` under its repo's project section, opening the section on first sight. */ +function addItemToProjectSection( + sections: Map>, + item: Item, + repoId: string, + repoMap: ReadonlyMap, + projectIndex: ProjectGroupingIndex | null +): void { + const grouping = getProjectGroupingForRepo(repoId, repoMap, projectIndex) + let section = sections.get(grouping.key) + if (!section) { + section = { label: grouping.label, items: [], repo: grouping.repo, repoIds: new Set() } + sections.set(grouping.key, section) + } + section.items.push(item) + section.repoIds.add(repoId) +} + +/** + * Opens the project section for a repo that has no rows to file, or joins the repo to the open one. + * `requireRepo` skips repo ids missing from the catalog, which would otherwise read "Unknown". + */ +function ensureProjectSectionForRepo( + sections: Map>, + repoId: string, + repoMap: ReadonlyMap, + projectIndex: ProjectGroupingIndex | null, + options: { requireRepo?: boolean; fallbackRepo?: R } = {} +): void { + const grouping = getProjectGroupingForRepo(repoId, repoMap, projectIndex) + if (options.requireRepo && !grouping.repo) { + return + } + const section = sections.get(grouping.key) + if (section) { + section.repoIds.add(repoId) + return + } + sections.set(grouping.key, { + label: grouping.label, + items: [], + repo: grouping.repo ?? options.fallbackRepo, + repoIds: new Set([repoId]) + }) +} + +/** + * Project sections in header order, unlabelled (see withRepoSectionDisplayLabels): rows first, then + * empty-project placeholders, notice rows' repos (opened with the notice's own repo if the catalog + * lacks it), and repos with a workspace being created. + */ +export function buildProjectSections< + Item extends ActivityItem, + R extends ProjectSectionRepo +>(args: { + items: readonly Item[] + repoIdOf: (item: Item) => string + repoMap: ReadonlyMap + projectIndex: ProjectGroupingIndex | null + placeholderRepoIds: Iterable + noticeRepos?: Iterable + pendingRepoIds?: Iterable + repoOrder: ReadonlyMap | undefined + projectOrderBy: ProjectOrderBy +}): [string, ProjectSectionEntry][] { + const { repoMap, projectIndex } = args + const sections = new Map>() + for (const item of args.items) { + addItemToProjectSection(sections, item, args.repoIdOf(item), repoMap, projectIndex) + } + // Why: repos can arrive before worktree scans, but stale IDs passed by + // older snapshots must not render an "Unknown" project header. + for (const repoId of args.placeholderRepoIds) { + ensureProjectSectionForRepo(sections, repoId, repoMap, projectIndex, { requireRepo: true }) + } + // Why: the default policy removes pinned worktrees from natural groups, + // but actionable notice rows still need a project section to render in. + for (const [repoId, fallbackRepo] of args.noticeRepos ?? []) { + ensureProjectSectionForRepo(sections, repoId, repoMap, projectIndex, { fallbackRepo }) + } + // Why: creating the first worktree in a repo leaves it with no group yet; + // ensure one so the in-progress row nests under its repo instead of being dropped. + for (const repoId of args.pendingRepoIds ?? []) { + ensureProjectSectionForRepo(sections, repoId, repoMap, projectIndex) + } + return orderProjectSections(sections, repoMap, args.repoOrder, args.projectOrderBy) +} diff --git a/src/shared/project-section-order.ts b/src/shared/project-section-order.ts new file mode 100644 index 00000000000..e164bcc73db --- /dev/null +++ b/src/shared/project-section-order.ts @@ -0,0 +1,201 @@ +// Labels and order of the desktop sidebar's project sections, shared so the paired phone matches. +import type { ProjectSectionEntry, ProjectSectionRepo } from './project-section-grouping' +import { getRepoDisplayLabelKey, getRepoDisplayLabelsByPath } from './repo-display-labels' +import type { ProjectOrderBy } from './ui-chrome-types' + +export type ActivityItem = { lastActivityAt?: number } + +// Why: disambiguate a section's *own* label, not its anchor checkout's repo +// name. Substituting repo.displayName made a project header read the project +// name while it was the only repo-backed section and the checkout name as soon +// as a second one rendered, so filtering workspaces renamed the project +// (#16127). Path suffixes still resolve genuinely identical labels. +export function withRepoSectionDisplayLabels< + E extends ProjectSectionEntry +>(entries: readonly [string, E][]): [string, E][] { + const labelItems = entries.flatMap(([, group]) => + group.repo ? [{ ...group.repo, displayName: group.label }] : [] + ) + if (labelItems.length === 0) { + return [...entries] + } + const labelsByPath = getRepoDisplayLabelsByPath(labelItems) + return entries.map(([key, group]) => [ + key, + group.repo + ? { ...group, label: labelsByPath.get(getRepoDisplayLabelKey(group.repo)) ?? group.label } + : group + ]) +} + +/** + * Recent rank for a project header. `hasActivity` projects (at least one + * visible worktree) always sort before fallback projects, regardless of the + * numeric values — a placeholder's `addedAt` must never outrank real activity. + * Within each tier, higher timestamps come first. + */ +type RecentRank = { hasActivity: boolean; ts: number } + +export function recentRankForEntry( + entry: readonly [string, ProjectSectionEntry] +): RecentRank { + let max = Number.NEGATIVE_INFINITY + for (const worktree of entry[1].items) { + // Why: phone rows from older hosts may omit the timestamp; the desktop's always carry one. + if ((worktree.lastActivityAt ?? Number.NEGATIVE_INFINITY) > max) { + max = worktree.lastActivityAt ?? max + } + } + if (max !== Number.NEGATIVE_INFINITY) { + // Why: Recent must be timestamp-based, not encounter order — the incoming + // array is no longer pre-sorted by recency once decoupled from sortBy. + return { hasActivity: true, ts: max } + } + const addedAt = entry[1].repo?.addedAt + return { + hasActivity: false, + ts: typeof addedAt === 'number' ? addedAt : Number.NEGATIVE_INFINITY + } +} + +export function createRecentRankLookup< + T extends readonly [string, ProjectSectionEntry] +>(): (entry: T) => RecentRank { + const ranks = new Map() + return (entry) => { + const existing = ranks.get(entry) + if (existing) { + return existing + } + const rank = recentRankForEntry(entry) + ranks.set(entry, rank) + return rank + } +} + +export function compareRecentRank(a: RecentRank, b: RecentRank): number { + if (a.hasActivity !== b.hasActivity) { + return a.hasActivity ? -1 : 1 + } + return b.ts - a.ts +} + +function manualRankForEntry( + entry: readonly [string, ProjectSectionEntry], + repoOrder: ReadonlyMap | undefined +): number { + const key = entry[0] + const repoIds = + entry[1].repoIds.size > 0 + ? [...entry[1].repoIds] + : [key.startsWith('repo:') ? key.slice('repo:'.length) : key] + let rank = Number.POSITIVE_INFINITY + for (const repoId of repoIds) { + const repoRank = repoOrder?.get(repoId) + if (repoRank !== undefined && repoRank < rank) { + rank = repoRank + } + } + return rank +} + +export function getManualOrderAnchorRepo( + group: ProjectSectionEntry, + repoMap: ReadonlyMap, + repoOrder: ReadonlyMap | undefined +): R | undefined { + let anchor = group.repo + let anchorRank = anchor ? (repoOrder?.get(anchor.id) ?? Number.POSITIVE_INFINITY) : undefined + for (const repoId of group.repoIds) { + const repo = repoMap.get(repoId) + if (!repo) { + continue + } + const rank = repoOrder?.get(repoId) ?? Number.POSITIVE_INFINITY + if (!anchor || rank < (anchorRank ?? Number.POSITIVE_INFINITY)) { + anchor = repo + anchorRank = rank + } + } + return anchor +} + +/** + * Order project header entries by the user's project-order preference. Manual + * follows the canonical repoOrder; Recent follows each project's most recent + * visible workspace activity (descending), with empty/imported-only projects + * sorting after active ones, then by manual rank, then label. + */ +export function sortProjectEntries< + T extends readonly [string, ProjectSectionEntry] +>( + entries: T[], + projectOrderBy: ProjectOrderBy, + repoOrder: ReadonlyMap | undefined +): T[] { + if (projectOrderBy === 'recent') { + if (entries.length < 2) { + return [...entries] + } + const getRecentRank = createRecentRankLookup() + return [...entries].sort((a, b) => { + const byRecent = compareRecentRank(getRecentRank(a), getRecentRank(b)) + if (byRecent !== 0) { + return byRecent + } + const ma = manualRankForEntry(a, repoOrder) + const mb = manualRankForEntry(b, repoOrder) + if (ma !== mb) { + return ma - mb + } + return a[1].label.localeCompare(b[1].label) + }) + } + if (!repoOrder) { + return entries + } + return [...entries].sort((a, b) => { + const ra = manualRankForEntry(a, repoOrder) + const rb = manualRankForEntry(b, repoOrder) + if (ra !== rb) { + return ra - rb + } + return a[1].label.localeCompare(b[1].label) + }) +} + +/** + * Project sections in header order. Each header anchors on the repo that leads manual order, so + * drag and actions target the same persisted order source the row sorter reads. + */ +export function orderProjectSections< + R extends ProjectSectionRepo, + E extends ProjectSectionEntry +>( + sections: ReadonlyMap, + repoMap: ReadonlyMap, + repoOrder: ReadonlyMap | undefined, + projectOrderBy: ProjectOrderBy +): [string, E][] { + for (const section of sections.values()) { + section.repo = getManualOrderAnchorRepo(section, repoMap, repoOrder) + } + // Why: project header order is its own user choice (projectOrderBy), decoupled from workspace + // sortBy. Manual uses the canonical repoOrder; Recent follows activity. + return sortProjectEntries(Array.from(sections.entries()), projectOrderBy, repoOrder) +} + +/** Each repo id's rank in catalog order; the first occurrence wins across hosts. */ +export function getLogicalRepoOrderRankById( + orderedRepoIds: readonly string[] +): Map { + const rankById = new Map() + orderedRepoIds.forEach((repoId, index) => { + // Why: paired hosts can contribute the same logical project ID; its merged + // header must anchor to the first occurrence instead of whichever host loaded last. + if (!rankById.has(repoId)) { + rankById.set(repoId, index) + } + }) + return rankById +} diff --git a/src/shared/project-section-parity.test-fixture.ts b/src/shared/project-section-parity.test-fixture.ts new file mode 100644 index 00000000000..d7295e1460d --- /dev/null +++ b/src/shared/project-section-parity.test-fixture.ts @@ -0,0 +1,248 @@ +// Host answers both the desktop sidebar's and the phone's Project grouping tests run on. +import type { MobileProjectSectionCatalog } from './mobile-project-sections-contract' +import type { ExecutionHostId } from './execution-host' +import { + projectHostSetupProjectionFromRepos, + type ProjectHostSetupProjection +} from './project-host-setup-projection' +import type { Repo } from './repo-types' +import type { RuntimeClientTarget } from './runtime-client-target' + +const orcaRemote = { + canonicalKey: 'github.com/stablyai/orca', + remoteName: 'origin', + remoteUrl: 'git@github.com:stablyai/orca.git' +} + +function hostRepo(id: string, path: string, displayName = 'orca'): Repo { + return { + id, + path, + displayName, + badgeColor: '#737373', + addedAt: 1, + ...(displayName === 'orca' ? { gitRemoteIdentity: orcaRemote } : {}) + } +} + +export type ParityHostAnswer = { + target: RuntimeClientTarget + hostId: ExecutionHostId + /** In the host's own repo.list order. */ + repos: Repo[] + /** The host's project.list/projectHostSetup.list rows; null for a host that predates them. */ + lists: ProjectHostSetupProjection | null +} + +const localRepos = [ + hostRepo('local-stably', '/Users/jinwoo/stably/orca'), + hostRepo('local-assets', '/Users/jinwoo/design-assets', 'design-assets'), + hostRepo('local-workspace', '/Users/jinwoo/orca/workspaces/orca/orca') +] +const windowsRepo = hostRepo('win-orca', 'C:/Users/jinwo/orca/orca') + +// Jinwoo's shape: several local checkouts of stablyai/orca, plus the project on two servers. +export const PROJECT_SECTION_PARITY_HOSTS: readonly ParityHostAnswer[] = [ + { + target: { kind: 'local' }, + hostId: 'local', + repos: localRepos, + lists: projectHostSetupProjectionFromRepos(localRepos) + }, + { + target: { kind: 'environment', environmentId: 'windows2' }, + hostId: 'runtime:windows2', + repos: [windowsRepo], + // A server's own catalog rows: its project name and an imported setup on its local host. + lists: { + projects: [ + { + id: 'github:stablyai/orca', + displayName: 'Orca', + badgeColor: '#737373', + providerIdentity: { provider: 'github', owner: 'stablyai', repo: 'orca' }, + sourceRepoIds: [windowsRepo.id], + createdAt: 5, + updatedAt: 5 + } + ], + setups: [ + { + id: 'win-setup', + projectId: 'github:stablyai/orca', + hostId: 'local', + repoId: windowsRepo.id, + path: windowsRepo.path, + displayName: 'orca', + setupState: 'ready', + setupMethod: 'imported-existing-folder', + createdAt: 5, + updatedAt: 5 + } + ] + } + }, + { + target: { kind: 'environment', environmentId: 'm4air' }, + hostId: 'runtime:m4air', + repos: [ + hostRepo('m4-orca', '/Users/m4air/orca/orca'), + hostRepo('m4-orca-two', '/Users/m4air/orca-two/orca') + ], + lists: null + } +] + +/** Workspace rows; activity order differs from catalog order, so Recent and Manual disagree. */ +export const PROJECT_SECTION_PARITY_ROWS: readonly { + id: string + repoId: string + lastActivityAt: number +}[] = [ + { id: 'wt-m4-two', repoId: 'm4-orca-two', lastActivityAt: 90 }, + { id: 'wt-win', repoId: 'win-orca', lastActivityAt: 70 }, + { id: 'wt-workspace', repoId: 'local-workspace', lastActivityAt: 50 }, + { id: 'wt-stably', repoId: 'local-stably', lastActivityAt: 30 }, + { id: 'wt-m4', repoId: 'm4-orca', lastActivityAt: 10 } +] + +const STABLY = { + key: 'project:github:stablyai/orca::setup:local-stably', + label: 'jinwoo/stably/orca' +} +const WORKSPACE = { + key: 'project:github:stablyai/orca::setup:local-workspace', + label: 'workspaces/orca/orca' +} +// Why 'orca', not the server's 'Orca': the later host's derived project name wins the merge. +const WINDOWS = { key: 'project:github:stablyai/orca', label: 'jinwo/orca/orca' } +const M4 = { key: 'project:github:stablyai/orca::setup:m4-orca', label: 'm4air/orca/orca' } +const M4_TWO = { + key: 'project:github:stablyai/orca::setup:m4-orca-two', + label: 'm4air/orca-two/orca' +} +const ASSETS = { key: 'project:repo:local-assets', label: 'design-assets' } + +export const PROJECT_SECTION_PARITY_HEADERS = { + recent: [M4_TWO, WINDOWS, WORKSPACE, STABLY, M4, ASSETS], + manual: [STABLY, ASSETS, WORKSPACE, WINDOWS, M4, M4_TWO] +} as const + +/** What main serves the phone for these answers (the renderer's published catalog). */ +export const PROJECT_SECTION_PARITY_SERVED: MobileProjectSectionCatalog = { + repos: [ + { + id: 'local-stably', + displayName: 'orca', + path: '/Users/jinwoo/stably/orca', + badgeColor: '#737373', + addedAt: 1, + executionHostId: 'local' + }, + { + id: 'local-assets', + displayName: 'design-assets', + path: '/Users/jinwoo/design-assets', + badgeColor: '#737373', + addedAt: 1, + executionHostId: 'local' + }, + { + id: 'local-workspace', + displayName: 'orca', + path: '/Users/jinwoo/orca/workspaces/orca/orca', + badgeColor: '#737373', + addedAt: 1, + executionHostId: 'local' + }, + { + id: 'win-orca', + displayName: 'orca', + path: 'C:/Users/jinwo/orca/orca', + badgeColor: '#737373', + addedAt: 1, + executionHostId: 'runtime:windows2' + }, + { + id: 'm4-orca', + displayName: 'orca', + path: '/Users/m4air/orca/orca', + badgeColor: '#737373', + addedAt: 1, + executionHostId: 'runtime:m4air' + }, + { + id: 'm4-orca-two', + displayName: 'orca', + path: '/Users/m4air/orca-two/orca', + badgeColor: '#737373', + addedAt: 1, + executionHostId: 'runtime:m4air' + } + ], + projects: [ + { id: 'repo:local-assets', displayName: 'design-assets' }, + { id: 'github:stablyai/orca', displayName: 'orca' } + ], + setups: [ + { + id: 'local-stably', + projectId: 'github:stablyai/orca', + hostId: 'local', + repoId: 'local-stably', + path: '/Users/jinwoo/stably/orca', + displayName: 'orca', + setupMethod: 'legacy-repo' + }, + { + id: 'local-assets', + projectId: 'repo:local-assets', + hostId: 'local', + repoId: 'local-assets', + path: '/Users/jinwoo/design-assets', + displayName: 'design-assets', + setupMethod: 'legacy-repo' + }, + { + id: 'local-workspace', + projectId: 'github:stablyai/orca', + hostId: 'local', + repoId: 'local-workspace', + path: '/Users/jinwoo/orca/workspaces/orca/orca', + displayName: 'orca', + setupMethod: 'legacy-repo' + }, + { + id: 'win-setup', + projectId: 'github:stablyai/orca', + hostId: 'runtime:windows2', + repoId: 'win-orca', + path: 'C:/Users/jinwo/orca/orca', + displayName: 'orca', + setupMethod: 'imported-existing-folder', + connectionId: null, + executionHostId: 'runtime:windows2' + }, + { + id: 'm4-orca', + projectId: 'github:stablyai/orca', + hostId: 'runtime:m4air', + repoId: 'm4-orca', + path: '/Users/m4air/orca/orca', + displayName: 'orca', + setupMethod: 'legacy-repo', + executionHostId: 'runtime:m4air' + }, + { + id: 'm4-orca-two', + projectId: 'github:stablyai/orca', + hostId: 'runtime:m4air', + repoId: 'm4-orca-two', + path: '/Users/m4air/orca-two/orca', + displayName: 'orca', + setupMethod: 'legacy-repo', + executionHostId: 'runtime:m4air' + } + ], + projectOrderBy: 'manual' +} diff --git a/src/renderer/src/lib/repo-display-labels.test.ts b/src/shared/repo-display-labels.test.ts similarity index 100% rename from src/renderer/src/lib/repo-display-labels.test.ts rename to src/shared/repo-display-labels.test.ts diff --git a/src/renderer/src/lib/repo-display-labels.ts b/src/shared/repo-display-labels.ts similarity index 96% rename from src/renderer/src/lib/repo-display-labels.ts rename to src/shared/repo-display-labels.ts index 89b7beeaeb6..b6c16ffb923 100644 --- a/src/renderer/src/lib/repo-display-labels.ts +++ b/src/shared/repo-display-labels.ts @@ -1,4 +1,4 @@ -import { getRepoExecutionHostId, type ExecutionHostId } from '../../../shared/execution-host' +import { getRepoExecutionHostId, type ExecutionHostId } from './execution-host' type RepoDisplayLabelItem = { path: string diff --git a/src/shared/rpc-contract/rpc-params-catalog.generated.ts b/src/shared/rpc-contract/rpc-params-catalog.generated.ts index 3c8d830db0e..6126664d89e 100644 --- a/src/shared/rpc-contract/rpc-params-catalog.generated.ts +++ b/src/shared/rpc-contract/rpc-params-catalog.generated.ts @@ -1077,6 +1077,7 @@ export const RPC_PARAMS_BY_METHOD = { 'markdown.saveTab': SaveMarkdownTab, 'mobileRelay.hosts.list': null, 'mobileRelay.hosts.worktrees': MobileRelayHostWorktreesParamsSchema, + 'mobileRelay.projectSections': null, 'mobileWeb.bundle.chunk': MobileWebBundleChunkParamsSchema, 'mobileWeb.bundle.manifest': null, 'mobileWeb.bundle.range': MobileWebBundleChunkParamsSchema, diff --git a/src/shared/runtime-session-contracts.ts b/src/shared/runtime-session-contracts.ts index 736ad2ecf21..9c3ecf43e7b 100644 --- a/src/shared/runtime-session-contracts.ts +++ b/src/shared/runtime-session-contracts.ts @@ -1,3 +1,4 @@ +import type { MobileProjectSectionCatalog } from './mobile-project-sections-contract' import type { AgentStatusOrchestrationContext } from './agent-status-types' import type { RemoteServerUpdateSupport } from './remote-server-update' import type { RemoteRuntimeSharedConnectionDiagnostics } from './remote-runtime-shared-control-types' @@ -166,6 +167,8 @@ export type RuntimeSyncWindowGraph = { terminalGroupGridSizes?: Record /** The grid a workspace with a single tab group gets, for one the window is not showing. */ singleGroupWorkspaceGridSize?: { cols: number; rows: number } + /** The sidebar's Project grouping inputs for paired phones; sent only when they changed. */ + projectSectionCatalog?: MobileProjectSectionCatalog } export type RuntimeRendererSyncWindowGraph = RuntimeSyncWindowGraph & { diff --git a/tests/e2e/paired-mobile-desktop-mirror-workspace-visibility.spec.ts b/tests/e2e/paired-mobile-desktop-mirror-workspace-visibility.spec.ts index 946782d2e15..c8ad8b552fe 100644 --- a/tests/e2e/paired-mobile-desktop-mirror-workspace-visibility.spec.ts +++ b/tests/e2e/paired-mobile-desktop-mirror-workspace-visibility.spec.ts @@ -34,6 +34,13 @@ const ReposSchema = z.object({ repos: z.array(z.looseObject({ id: z.string(), path: z.string() })) }) const HostsSchema = z.object({ hosts: z.array(z.looseObject({ hostId: z.string() })) }) +const ProjectSectionsSchema = z.object({ + catalog: z + .object({ + repos: z.array(z.looseObject({ path: z.string(), executionHostId: z.string().optional() })) + }) + .nullable() +}) const TabsSchema = z.looseObject({ tabs: z.array(z.looseObject({ id: z.string() })) }) let requestId = 0 @@ -77,6 +84,18 @@ test("phone lists a server's workspaces exactly as the desktop sidebar filters t const { repos } = ReposSchema.parse(await result(socket, 'repo.list', {}, serverHostId)) const repo = `id:${repos.find((entry) => entry.path === serverRepo)?.id ?? ''}` const create = { setupDecision: 'skip' } + // The desktop window publishes the catalog its sidebar groups Project sections with. + await expect + .poll( + async () => + ProjectSectionsSchema.parse( + await result(socket, 'mobileRelay.projectSections', {}) + ).catalog?.repos.some( + (entry) => entry.path === serverRepo && entry.executionHostId === serverHostId + ) ?? false, + { timeout: 30_000 } + ) + .toBe(true) // One workspace each: created on the server itself, by the desktop, and by the phone through it. await host.client.call('worktree.create', { repo, name: 'from-server', ...create })