feat(mobile): rebuild Home around what needs you, with actions pinned at the bottom (#36520)

This commit is contained in:
Kelvin Amoaba
2026-10-31 02:47:49 +00:00
committed by GitHub
parent 5e6c0494b3
commit 97582248e7
94 changed files with 1750 additions and 10755 deletions
-2
View File
@@ -751,8 +751,6 @@
3 test-no-wall-clock:config/scripts/reclaim-dev-electron-bundles.test.ts
1 test-no-wall-clock:config/scripts/reclaim-release-test-caches.test.ts
1 test-no-wall-clock:mobile/scripts/native-build-cache.test.ts
1 test-no-wall-clock:mobile/src/components/MobileHostCard-truthful-status.test.tsx
1 test-no-wall-clock:mobile/src/components/MobileHostCard.test.ts
1 test-no-wall-clock:mobile/src/components/pr-sidebar/pr-comment-card-web-avatar.test.tsx
1 test-no-wall-clock:mobile/src/session/mobile-structured-send-new-action.test.ts
2 test-no-wall-clock:mobile/src/session/native-chat/composer/use-mobile-native-chat-message-send-draft-lifetime.test.ts
@@ -1208,15 +1208,6 @@
"auto.components.mobile.PhoneCarousel.93217b41c1": {
"ko": "워크트리 목록"
},
"auto.components.mobile.slides.HomeSlide.0bad5b07c8": {
"ko": "GitHub 및 Linear"
},
"auto.components.mobile.slides.HomeSlide.0bc1881bc4": {
"ko": "연결됨 · 워크트리 40개 · 활성 5개"
},
"auto.components.mobile.slides.HomeSlide.c0e2e9dcd9": {
"ko": "다시 오신 것을 환영합니다"
},
"auto.components.mobile.slides.TerminalSlide.2defc05141": {
"ko": "dev@mac"
},
@@ -1,224 +0,0 @@
{
"goldenFormatVersion": 6,
"operation": "home.host-accounts",
"family": "home.host-accounts",
"namedDeltas": [],
"values": {
"16b4c187b90c": {
"name": "accounts.list#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "accounts.list"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"claude": {
"accounts": [
{
"email": "claude@example.test",
"id": "claude-1",
"updatedAt": 1700000000000
}
],
"activeAccountId": "claude-1"
},
"codex": {
"accounts": [],
"activeAccountId": {
"$rpc": "null"
}
},
"rateLimits": {
"claude": {
"$rpc": "null"
},
"codex": {
"$rpc": "null"
},
"inactiveClaudeAccounts": [],
"inactiveCodexAccounts": []
}
}
}
}
},
"2525c654127c": {
"host-1": {
"claude": {
"accounts": [
{
"email": "claude@example.test",
"id": "claude-1",
"updatedAt": 1700000000000
}
],
"activeAccountId": "claude-1"
},
"codex": {
"accounts": [],
"activeAccountId": {
"$rpc": "null"
}
},
"rateLimits": {
"claude": {
"$rpc": "null"
},
"claudeTarget": {
"runtime": "host",
"wslDistro": {
"$rpc": "null"
}
},
"codex": {
"$rpc": "null"
},
"codexTarget": {
"runtime": "host",
"wslDistro": {
"$rpc": "null"
}
},
"inactiveClaudeAccounts": [],
"inactiveCodexAccounts": []
}
}
},
"3bf8cb601ec3": {
"name": "accounts.list#1",
"ordinal": 2,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"accounts.list\"}"
},
"44136fa355b3": {},
"78574a24b32d": {
"name": "accounts",
"ordinal": 3,
"value": {
"host-1": {
"claude": {
"accounts": [
{
"email": "claude@example.test",
"id": "claude-1",
"updatedAt": 1700000000000
}
],
"activeAccountId": "claude-1"
},
"codex": {
"accounts": [],
"activeAccountId": {
"$rpc": "null"
}
},
"rateLimits": {
"claude": {
"$rpc": "null"
},
"claudeTarget": {
"runtime": "host",
"wslDistro": {
"$rpc": "null"
}
},
"codex": {
"$rpc": "null"
},
"codexTarget": {
"runtime": "host",
"wslDistro": {
"$rpc": "null"
}
},
"inactiveClaudeAccounts": [],
"inactiveCodexAccounts": []
}
}
}
},
"884d6f264bfd": {
"name": "accounts.list#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "accounts.list"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"eb79a9b3682a": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"$rpc": "undefined"
}
}
},
"recording": {
"scenario": "home-host-accounts",
"checkpoints": [
{
"id": "accounts-pending",
"observation": {
"sender": ["884d6f264bfd"],
"payloads": ["3bf8cb601ec3"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "accounts-published",
"observation": {
"sender": ["16b4c187b90c"],
"payloads": ["3bf8cb601ec3"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "2525c654127c",
"effects": ["78574a24b32d"]
}
}
]
}
}
@@ -1,128 +0,0 @@
{
"goldenFormatVersion": 6,
"operation": "home.host-stats",
"family": "home.host-stats",
"namedDeltas": [],
"values": {
"44136fa355b3": {},
"94616379becc": {
"name": "stats",
"ordinal": 3,
"value": {
"host-1": {
"activeWorktrees": 1,
"totalWorktrees": 3
}
}
},
"9698bf4d8a23": {
"name": "stats.summary#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "stats.summary"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"activeWorktrees": 1,
"totalWorktrees": 3
}
}
}
},
"9a84a7559023": {
"host-1": {
"activeWorktrees": 1,
"totalWorktrees": 3
}
},
"cf7f6575cf73": {
"name": "stats.summary#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "stats.summary"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"d66672644411": {
"name": "stats.summary#1",
"ordinal": 2,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"stats.summary\"}"
},
"eb79a9b3682a": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"$rpc": "undefined"
}
}
},
"recording": {
"scenario": "home-host-stats",
"checkpoints": [
{
"id": "stats-pending",
"observation": {
"sender": ["cf7f6575cf73"],
"payloads": ["d66672644411"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "settled",
"observation": {
"sender": ["9698bf4d8a23"],
"payloads": ["d66672644411"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "9a84a7559023",
"effects": ["94616379becc"]
}
}
]
}
}
@@ -1,684 +0,0 @@
{
"goldenFormatVersion": 6,
"operation": "home.host-accounts",
"family": "home.host-accounts",
"namedDeltas": [],
"values": {
"11f6bbd8152f": {
"name": "accounts.list#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "accounts.list"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"error": {
"message": "inner refused"
},
"ok": false
}
}
}
},
"16b4c187b90c": {
"name": "accounts.list#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "accounts.list"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"claude": {
"accounts": [
{
"email": "claude@example.test",
"id": "claude-1",
"updatedAt": 1700000000000
}
],
"activeAccountId": "claude-1"
},
"codex": {
"accounts": [],
"activeAccountId": {
"$rpc": "null"
}
},
"rateLimits": {
"claude": {
"$rpc": "null"
},
"codex": {
"$rpc": "null"
},
"inactiveClaudeAccounts": [],
"inactiveCodexAccounts": []
}
}
}
}
},
"2525c654127c": {
"host-1": {
"claude": {
"accounts": [
{
"email": "claude@example.test",
"id": "claude-1",
"updatedAt": 1700000000000
}
],
"activeAccountId": "claude-1"
},
"codex": {
"accounts": [],
"activeAccountId": {
"$rpc": "null"
}
},
"rateLimits": {
"claude": {
"$rpc": "null"
},
"claudeTarget": {
"runtime": "host",
"wslDistro": {
"$rpc": "null"
}
},
"codex": {
"$rpc": "null"
},
"codexTarget": {
"runtime": "host",
"wslDistro": {
"$rpc": "null"
}
},
"inactiveClaudeAccounts": [],
"inactiveCodexAccounts": []
}
}
},
"2812f0b1ec77": {
"name": "accounts.list#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "accounts.list"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "method_not_found",
"message": "Unknown method"
},
"id": "frame-1",
"ok": false
}
}
},
"3439bc68151d": {
"name": "accounts.list#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "accounts.list"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "transport failure",
"isRpcDeliveryUnknown": true
}
}
},
"3bf8cb601ec3": {
"name": "accounts.list#1",
"ordinal": 2,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"accounts.list\"}"
},
"44136fa355b3": {},
"4a1c397d346b": {
"name": "accounts.list#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "accounts.list"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"error": "inner refused",
"ok": false
}
}
}
},
"564b115f0a17": {
"name": "accounts.list#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "accounts.list"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "",
"isRpcDeliveryUnknown": true
}
}
},
"6d54d83570d0": {
"name": "accounts.list#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "accounts.list"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true
}
}
},
"71d054262f17": {
"name": "accounts.list#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "accounts.list"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "refused",
"message": "outer refused"
},
"id": "frame-1",
"ok": false
}
}
},
"78574a24b32d": {
"name": "accounts",
"ordinal": 3,
"value": {
"host-1": {
"claude": {
"accounts": [
{
"email": "claude@example.test",
"id": "claude-1",
"updatedAt": 1700000000000
}
],
"activeAccountId": "claude-1"
},
"codex": {
"accounts": [],
"activeAccountId": {
"$rpc": "null"
}
},
"rateLimits": {
"claude": {
"$rpc": "null"
},
"claudeTarget": {
"runtime": "host",
"wslDistro": {
"$rpc": "null"
}
},
"codex": {
"$rpc": "null"
},
"codexTarget": {
"runtime": "host",
"wslDistro": {
"$rpc": "null"
}
},
"inactiveClaudeAccounts": [],
"inactiveCodexAccounts": []
}
}
}
},
"884d6f264bfd": {
"name": "accounts.list#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "accounts.list"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"a2761753b29a": {
"name": "accounts.list#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "accounts.list"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "refused",
"message": ""
},
"id": "frame-1",
"ok": false
}
}
},
"d308707eb973": {
"name": "accounts.list#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "accounts.list"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"error": "refused"
}
}
}
},
"d953c94f3173": {
"name": "accounts.list#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "accounts.list"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"$rpc": "null"
}
}
}
},
"eb79a9b3682a": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"$rpc": "undefined"
}
}
},
"recording": {
"scenario": "matrix-home.host-accounts-accounts.list-1",
"checkpoints": [
{
"id": "home-host-accounts.prelude:accounts-pending",
"observation": {
"sender": ["884d6f264bfd"],
"payloads": ["3bf8cb601ec3"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "home-host-accounts.normal:accounts-published",
"observation": {
"sender": ["16b4c187b90c"],
"payloads": ["3bf8cb601ec3"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "2525c654127c",
"effects": ["78574a24b32d"]
}
},
{
"id": "home-host-accounts.result-absent:accounts-published",
"observation": {
"sender": ["6d54d83570d0"],
"payloads": ["3bf8cb601ec3"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "home-host-accounts.result-null:accounts-published",
"observation": {
"sender": ["d953c94f3173"],
"payloads": ["3bf8cb601ec3"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "home-host-accounts.inner-ok-missing:accounts-published",
"observation": {
"sender": ["d308707eb973"],
"payloads": ["3bf8cb601ec3"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "home-host-accounts.inner-false-string-error:accounts-published",
"observation": {
"sender": ["4a1c397d346b"],
"payloads": ["3bf8cb601ec3"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "home-host-accounts.inner-false-object-error:accounts-published",
"observation": {
"sender": ["11f6bbd8152f"],
"payloads": ["3bf8cb601ec3"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "home-host-accounts.outer-refused:accounts-published",
"observation": {
"sender": ["71d054262f17"],
"payloads": ["3bf8cb601ec3"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "home-host-accounts.outer-refused-no-message:accounts-published",
"observation": {
"sender": ["a2761753b29a"],
"payloads": ["3bf8cb601ec3"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "home-host-accounts.method-not-found:accounts-published",
"observation": {
"sender": ["2812f0b1ec77"],
"payloads": ["3bf8cb601ec3"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "home-host-accounts.transport-rejection:accounts-published",
"observation": {
"sender": ["3439bc68151d"],
"payloads": ["3bf8cb601ec3"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "home-host-accounts.transport-rejection-no-message:accounts-published",
"observation": {
"sender": ["564b115f0a17"],
"payloads": ["3bf8cb601ec3"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
}
]
}
}
@@ -1,666 +0,0 @@
{
"goldenFormatVersion": 6,
"operation": "home.host-stats",
"family": "home.host-stats",
"namedDeltas": [],
"values": {
"1ac8bfa526f1": {
"name": "stats.summary#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "stats.summary"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "",
"isRpcDeliveryUnknown": true
}
}
},
"4211139e96bd": {
"name": "stats.summary#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "stats.summary"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"error": {
"message": "inner refused"
},
"ok": false
}
}
}
},
"44136fa355b3": {},
"556edbbc8712": {
"host-1": {
"$rpc": "null"
}
},
"5691d0e49e5a": {
"name": "stats.summary#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "stats.summary"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"error": "refused"
}
}
}
},
"570b0ab475a2": {
"name": "stats",
"ordinal": 3,
"value": {
"host-1": {
"$rpc": "null"
}
}
},
"5b3b2e4ed756": {
"name": "stats",
"ordinal": 3,
"value": {
"host-1": {
"error": "refused"
}
}
},
"5f68a32120c1": {
"name": "stats.summary#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "stats.summary"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "refused",
"message": ""
},
"id": "frame-1",
"ok": false
}
}
},
"6437faa290bd": {
"name": "stats",
"ordinal": 3,
"value": {
"host-1": {
"error": {
"message": "inner refused"
},
"ok": false
}
}
},
"8c4863e9051a": {
"name": "stats.summary#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "stats.summary"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "refused",
"message": "outer refused"
},
"id": "frame-1",
"ok": false
}
}
},
"94616379becc": {
"name": "stats",
"ordinal": 3,
"value": {
"host-1": {
"activeWorktrees": 1,
"totalWorktrees": 3
}
}
},
"9698bf4d8a23": {
"name": "stats.summary#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "stats.summary"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"activeWorktrees": 1,
"totalWorktrees": 3
}
}
}
},
"9a84a7559023": {
"host-1": {
"activeWorktrees": 1,
"totalWorktrees": 3
}
},
"9c34abfa17e7": {
"host-1": {
"error": {
"message": "inner refused"
},
"ok": false
}
},
"af5ba5c22147": {
"name": "stats.summary#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "stats.summary"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"$rpc": "null"
}
}
}
},
"b474d2a02a6a": {
"host-1": {
"error": "refused"
}
},
"c5bfd5f18460": {
"host-1": {
"$rpc": "undefined"
}
},
"c60ee6b032a7": {
"name": "stats.summary#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "stats.summary"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "method_not_found",
"message": "Unknown method"
},
"id": "frame-1",
"ok": false
}
}
},
"cf7f6575cf73": {
"name": "stats.summary#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "stats.summary"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"d3ca24e5a81b": {
"name": "stats.summary#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "stats.summary"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"error": "inner refused",
"ok": false
}
}
}
},
"d548975ae3f5": {
"name": "stats",
"ordinal": 3,
"value": {
"host-1": {
"error": "inner refused",
"ok": false
}
}
},
"d66672644411": {
"name": "stats.summary#1",
"ordinal": 2,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"stats.summary\"}"
},
"db6e2ff86f92": {
"name": "stats",
"ordinal": 3,
"value": {
"host-1": {
"$rpc": "undefined"
}
}
},
"dccf8acd7784": {
"name": "stats.summary#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "stats.summary"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true
}
}
},
"e05986a4b6e2": {
"host-1": {
"error": "inner refused",
"ok": false
}
},
"eb79a9b3682a": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"$rpc": "undefined"
}
},
"fa85158f27a0": {
"name": "stats.summary#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "stats.summary"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "transport failure",
"isRpcDeliveryUnknown": true
}
}
}
},
"recording": {
"scenario": "matrix-home.host-stats-stats.summary-1",
"checkpoints": [
{
"id": "home-host-stats.prelude:stats-pending",
"observation": {
"sender": ["cf7f6575cf73"],
"payloads": ["d66672644411"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "home-host-stats.normal:settled",
"observation": {
"sender": ["9698bf4d8a23"],
"payloads": ["d66672644411"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "9a84a7559023",
"effects": ["94616379becc"]
}
},
{
"id": "home-host-stats.result-absent:settled",
"observation": {
"sender": ["dccf8acd7784"],
"payloads": ["d66672644411"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "c5bfd5f18460",
"effects": ["db6e2ff86f92"]
}
},
{
"id": "home-host-stats.result-null:settled",
"observation": {
"sender": ["af5ba5c22147"],
"payloads": ["d66672644411"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "556edbbc8712",
"effects": ["570b0ab475a2"]
}
},
{
"id": "home-host-stats.inner-ok-missing:settled",
"observation": {
"sender": ["5691d0e49e5a"],
"payloads": ["d66672644411"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "b474d2a02a6a",
"effects": ["5b3b2e4ed756"]
}
},
{
"id": "home-host-stats.inner-false-string-error:settled",
"observation": {
"sender": ["d3ca24e5a81b"],
"payloads": ["d66672644411"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "e05986a4b6e2",
"effects": ["d548975ae3f5"]
}
},
{
"id": "home-host-stats.inner-false-object-error:settled",
"observation": {
"sender": ["4211139e96bd"],
"payloads": ["d66672644411"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "9c34abfa17e7",
"effects": ["6437faa290bd"]
}
},
{
"id": "home-host-stats.outer-refused:settled",
"observation": {
"sender": ["8c4863e9051a"],
"payloads": ["d66672644411"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "home-host-stats.outer-refused-no-message:settled",
"observation": {
"sender": ["5f68a32120c1"],
"payloads": ["d66672644411"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "home-host-stats.method-not-found:settled",
"observation": {
"sender": ["c60ee6b032a7"],
"payloads": ["d66672644411"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "home-host-stats.transport-rejection:settled",
"observation": {
"sender": ["fa85158f27a0"],
"payloads": ["d66672644411"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "home-host-stats.transport-rejection-no-message:settled",
"observation": {
"sender": ["1ac8bfa526f1"],
"payloads": ["d66672644411"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
}
]
}
}
@@ -1,719 +0,0 @@
{
"goldenFormatVersion": 6,
"operation": "settings.home-providers",
"family": "settings.home-providers",
"namedDeltas": [],
"values": {
"015b0f5c6e12": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"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
}
}
},
"036d405abb7b": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"ok": true,
"result": {
"error": "refused"
}
}
}
},
"0a5b778e224a": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"ok": true,
"result": {
"connected": false
}
}
}
},
"362f21a2ec36": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"ok": true
}
}
},
"403872f494c7": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "refused",
"message": ""
},
"id": "frame-3",
"ok": false
}
}
},
"44136fa355b3": {},
"4777b5dc4e66": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"58995bbfcb17": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"776ed8372613": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
}
},
"79b8c1b0d1d1": {
"host-1": ["github"]
},
"7b7b4c5e7804": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"ok": true,
"result": {
"error": {
"message": "inner refused"
},
"ok": false
}
}
}
},
"805497bacbd3": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"ok": true,
"result": {
"error": "inner refused",
"ok": false
}
}
}
},
"89e9993ccc21": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "transport failure",
"isRpcDeliveryUnknown": true
}
}
},
"8fce29bab175": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"settings": {
"defaultTuiAgent": "codex",
"disabledTuiAgents": ["claude"],
"hostSettingOverrides": {},
"prBotAuthorOverrides": ["bot-user"],
"visibleTaskProviders": ["github", "linear"]
}
}
}
}
},
"9037ac778ace": {
"name": "providers",
"ordinal": 7,
"value": {
"host-1": ["github"]
}
},
"908e43fdefae": {
"name": "preflight.check#1",
"ordinal": 5,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"preflight.check\"}"
},
"975f2ff9a730": {
"name": "settings.get#1",
"ordinal": 4,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}"
},
"a8d3d66241fe": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"ok": true,
"result": {
"$rpc": "null"
}
}
}
},
"aa3490f8f6f8": {
"name": "linear.status#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"linear.status\"}"
},
"ad3f1d9d51b1": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "refused",
"message": "outer refused"
},
"id": "frame-3",
"ok": false
}
}
},
"adf2776f4eea": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "",
"isRpcDeliveryUnknown": true
}
}
},
"eb79a9b3682a": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"$rpc": "undefined"
}
},
"ec8967c2d560": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
}
},
"recording": {
"scenario": "matrix-settings.home-providers-linear.status-1",
"checkpoints": [
{
"id": "settings-home-providers-fulfilled.prelude:settings-pending",
"observation": {
"sender": ["ec8967c2d560", "4777b5dc4e66", "58995bbfcb17"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "settings-home-providers-fulfilled.normal:settled",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.result-absent:settled",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "362f21a2ec36"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.result-null:settled",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "a8d3d66241fe"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.inner-ok-missing:settled",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "036d405abb7b"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.inner-false-string-error:settled",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "805497bacbd3"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.inner-false-object-error:settled",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "7b7b4c5e7804"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.outer-refused:settled",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "ad3f1d9d51b1"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.outer-refused-no-message:settled",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "403872f494c7"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.method-not-found:settled",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "015b0f5c6e12"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.transport-rejection:settled",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "89e9993ccc21"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.transport-rejection-no-message:settled",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "adf2776f4eea"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
}
]
}
}
@@ -1,719 +0,0 @@
{
"goldenFormatVersion": 6,
"operation": "settings.home-providers",
"family": "settings.home-providers",
"namedDeltas": [],
"values": {
"00a1ac4d876f": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"ok": true,
"result": {
"error": "inner refused",
"ok": false
}
}
}
},
"04f6f989667f": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "method_not_found",
"message": "Unknown method"
},
"id": "frame-2",
"ok": false
}
}
},
"0a5b778e224a": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"ok": true,
"result": {
"connected": false
}
}
}
},
"32bc15340619": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "refused",
"message": "outer refused"
},
"id": "frame-2",
"ok": false
}
}
},
"44136fa355b3": {},
"4777b5dc4e66": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"58995bbfcb17": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"59eef396a6b8": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "",
"isRpcDeliveryUnknown": true
}
}
},
"73d2f90315b5": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"ok": true,
"result": {
"$rpc": "null"
}
}
}
},
"776ed8372613": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
}
},
"79b8c1b0d1d1": {
"host-1": ["github"]
},
"86350c837240": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"ok": true,
"result": {
"error": "refused"
}
}
}
},
"88df61a1af58": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "transport failure",
"isRpcDeliveryUnknown": true
}
}
},
"8fce29bab175": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"settings": {
"defaultTuiAgent": "codex",
"disabledTuiAgents": ["claude"],
"hostSettingOverrides": {},
"prBotAuthorOverrides": ["bot-user"],
"visibleTaskProviders": ["github", "linear"]
}
}
}
}
},
"9037ac778ace": {
"name": "providers",
"ordinal": 7,
"value": {
"host-1": ["github"]
}
},
"908e43fdefae": {
"name": "preflight.check#1",
"ordinal": 5,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"preflight.check\"}"
},
"975f2ff9a730": {
"name": "settings.get#1",
"ordinal": 4,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}"
},
"a7c1d986eef2": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"ok": true,
"result": {
"error": {
"message": "inner refused"
},
"ok": false
}
}
}
},
"aa3490f8f6f8": {
"name": "linear.status#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"linear.status\"}"
},
"ad454c0116cc": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "refused",
"message": ""
},
"id": "frame-2",
"ok": false
}
}
},
"cef9af4132f7": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"ok": true
}
}
},
"eb79a9b3682a": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"$rpc": "undefined"
}
},
"ec8967c2d560": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
}
},
"recording": {
"scenario": "matrix-settings.home-providers-preflight.check-1",
"checkpoints": [
{
"id": "settings-home-providers-fulfilled.prelude:settings-pending",
"observation": {
"sender": ["ec8967c2d560", "4777b5dc4e66", "58995bbfcb17"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "settings-home-providers-fulfilled.normal:settled",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.result-absent:settled",
"observation": {
"sender": ["8fce29bab175", "cef9af4132f7", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.result-null:settled",
"observation": {
"sender": ["8fce29bab175", "73d2f90315b5", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.inner-ok-missing:settled",
"observation": {
"sender": ["8fce29bab175", "86350c837240", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.inner-false-string-error:settled",
"observation": {
"sender": ["8fce29bab175", "00a1ac4d876f", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.inner-false-object-error:settled",
"observation": {
"sender": ["8fce29bab175", "a7c1d986eef2", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.outer-refused:settled",
"observation": {
"sender": ["8fce29bab175", "32bc15340619", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.outer-refused-no-message:settled",
"observation": {
"sender": ["8fce29bab175", "ad454c0116cc", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.method-not-found:settled",
"observation": {
"sender": ["8fce29bab175", "04f6f989667f", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.transport-rejection:settled",
"observation": {
"sender": ["8fce29bab175", "88df61a1af58", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.transport-rejection-no-message:settled",
"observation": {
"sender": ["8fce29bab175", "59eef396a6b8", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
}
]
}
}
@@ -1,729 +0,0 @@
{
"goldenFormatVersion": 6,
"operation": "settings.home-providers",
"family": "settings.home-providers",
"namedDeltas": [],
"values": {
"08bc52c3b785": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"error": "inner refused",
"ok": false
}
}
}
},
"0a5b778e224a": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"ok": true,
"result": {
"connected": false
}
}
}
},
"1b8b10efd49c": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "refused",
"message": "outer refused"
},
"id": "frame-1",
"ok": false
}
}
},
"44136fa355b3": {},
"456bddaee00f": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true
}
}
},
"4777b5dc4e66": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"58995bbfcb17": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"776ed8372613": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
}
},
"79b8c1b0d1d1": {
"host-1": ["github"]
},
"83ed5cc34997": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"error": "refused"
}
}
}
},
"868d9c7eed63": {
"host-1": ["github", "jira"]
},
"8fce29bab175": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"settings": {
"defaultTuiAgent": "codex",
"disabledTuiAgents": ["claude"],
"hostSettingOverrides": {},
"prBotAuthorOverrides": ["bot-user"],
"visibleTaskProviders": ["github", "linear"]
}
}
}
}
},
"9037ac778ace": {
"name": "providers",
"ordinal": 7,
"value": {
"host-1": ["github"]
}
},
"908e43fdefae": {
"name": "preflight.check#1",
"ordinal": 5,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"preflight.check\"}"
},
"975f2ff9a730": {
"name": "settings.get#1",
"ordinal": 4,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}"
},
"9ed7403a150d": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "",
"isRpcDeliveryUnknown": true
}
}
},
"a4cb9e2892b6": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"error": {
"message": "inner refused"
},
"ok": false
}
}
}
},
"a940d6fc7d14": {
"name": "providers",
"ordinal": 7,
"value": {
"host-1": ["github", "jira"]
}
},
"aa3490f8f6f8": {
"name": "linear.status#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"linear.status\"}"
},
"adf76218a426": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "refused",
"message": ""
},
"id": "frame-1",
"ok": false
}
}
},
"eb79a9b3682a": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"$rpc": "undefined"
}
},
"ec8967c2d560": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"eed02fc42efb": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"$rpc": "null"
}
}
}
},
"f93cbe622448": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "transport failure",
"isRpcDeliveryUnknown": true
}
}
},
"fa46c6c59726": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "method_not_found",
"message": "Unknown method"
},
"id": "frame-1",
"ok": false
}
}
}
},
"recording": {
"scenario": "matrix-settings.home-providers-settings.get-1",
"checkpoints": [
{
"id": "settings-home-providers-fulfilled.prelude:settings-pending",
"observation": {
"sender": ["ec8967c2d560", "4777b5dc4e66", "58995bbfcb17"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "settings-home-providers-fulfilled.normal:settled",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.result-absent:settled",
"observation": {
"sender": ["456bddaee00f", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.result-null:settled",
"observation": {
"sender": ["eed02fc42efb", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.inner-ok-missing:settled",
"observation": {
"sender": ["83ed5cc34997", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "868d9c7eed63",
"effects": ["a940d6fc7d14"]
}
},
{
"id": "settings-home-providers-fulfilled.inner-false-string-error:settled",
"observation": {
"sender": ["08bc52c3b785", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "868d9c7eed63",
"effects": ["a940d6fc7d14"]
}
},
{
"id": "settings-home-providers-fulfilled.inner-false-object-error:settled",
"observation": {
"sender": ["a4cb9e2892b6", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "868d9c7eed63",
"effects": ["a940d6fc7d14"]
}
},
{
"id": "settings-home-providers-fulfilled.outer-refused:settled",
"observation": {
"sender": ["1b8b10efd49c", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "868d9c7eed63",
"effects": ["a940d6fc7d14"]
}
},
{
"id": "settings-home-providers-fulfilled.outer-refused-no-message:settled",
"observation": {
"sender": ["adf76218a426", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "868d9c7eed63",
"effects": ["a940d6fc7d14"]
}
},
{
"id": "settings-home-providers-fulfilled.method-not-found:settled",
"observation": {
"sender": ["fa46c6c59726", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "868d9c7eed63",
"effects": ["a940d6fc7d14"]
}
},
{
"id": "settings-home-providers-fulfilled.transport-rejection:settled",
"observation": {
"sender": ["f93cbe622448", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.transport-rejection-no-message:settled",
"observation": {
"sender": ["9ed7403a150d", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
}
]
}
}
@@ -4,24 +4,6 @@
"family": "worktree.home-catalog",
"namedDeltas": [],
"values": {
"07d517ec3da6": {
"name": "info",
"ordinal": 3,
"value": {
"host-1": {
"activeCount": 2,
"countsProvenAt": 1767225600000,
"hostId": "host-1",
"lastActiveWorktree": {
"displayName": "One",
"repo": "Repo",
"status": "working",
"worktreeId": "w-1"
},
"totalWorktrees": 2
}
}
},
"0c5e517bcdb4": {
"name": "worktree.ps#1",
"ordinal": 1,
@@ -54,6 +36,41 @@
}
}
},
"0d02c4637af4": {
"name": "info",
"ordinal": 3,
"value": {
"host-1": {
"activeCount": 2,
"attention": [
{
"branch": {
"$rpc": "undefined"
},
"displayName": "One",
"dotStatus": "working",
"lastOutputAt": {
"$rpc": "undefined"
},
"repo": "Repo",
"workingMode": {
"$rpc": "undefined"
},
"worktreeId": "w-1"
}
],
"countsProvenAt": 1767225600000,
"hostId": "host-1",
"lastActiveWorktree": {
"displayName": "One",
"repo": "Repo",
"status": "working",
"worktreeId": "w-1"
},
"totalWorktrees": 2
}
}
},
"14379cad1cb5": {
"name": "worktree.ps#1",
"ordinal": 1,
@@ -89,6 +106,22 @@
}
}
},
"1d0a5fbc1e3b": {
"name": "info",
"ordinal": 3,
"value": {
"host-1": {
"activeCount": 0,
"attention": [],
"countsProvenAt": 1767225600000,
"hostId": "host-1",
"lastActiveWorktree": {
"$rpc": "null"
},
"totalWorktrees": 0
}
}
},
"2b2ff73e3290": {
"name": "worktree.ps#1",
"ordinal": 1,
@@ -126,21 +159,6 @@
}
}
},
"2fb25a9212b1": {
"name": "info",
"ordinal": 3,
"value": {
"host-1": {
"activeCount": 0,
"countsProvenAt": 1767225600000,
"hostId": "host-1",
"lastActiveWorktree": {
"$rpc": "null"
},
"totalWorktrees": 0
}
}
},
"3b32328e5154": {
"name": "worktree.ps#1",
"ordinal": 1,
@@ -258,9 +276,26 @@
}
}
},
"6624fb97ea31": {
"87aae336d391": {
"host-1": {
"activeCount": 2,
"attention": [
{
"branch": {
"$rpc": "undefined"
},
"displayName": "One",
"dotStatus": "working",
"lastOutputAt": {
"$rpc": "undefined"
},
"repo": "Repo",
"workingMode": {
"$rpc": "undefined"
},
"worktreeId": "w-1"
}
],
"countsProvenAt": 1767225600000,
"hostId": "host-1",
"lastActiveWorktree": {
@@ -381,6 +416,18 @@
"ordinal": 2,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"worktree.ps\",\"params\":{\"limit\":10000,\"supportsWorktreeVisibilitySourceDefaults\":true}}"
},
"abea2b7ff9d3": {
"host-1": {
"activeCount": 0,
"attention": [],
"countsProvenAt": 1767225600000,
"hostId": "host-1",
"lastActiveWorktree": {
"$rpc": "null"
},
"totalWorktrees": 0
}
},
"b7bbf0d88929": {
"name": "worktree.ps#1",
"ordinal": 1,
@@ -431,17 +478,6 @@
}
}
},
"d0ec31ab66d5": {
"host-1": {
"activeCount": 0,
"countsProvenAt": 1767225600000,
"hostId": "host-1",
"lastActiveWorktree": {
"$rpc": "null"
},
"totalWorktrees": 0
}
},
"d1800200eda0": {
"name": "worktree.ps#1",
"ordinal": 1,
@@ -543,8 +579,8 @@
"settlements": {
"load": "eb79a9b3682a"
},
"state": "6624fb97ea31",
"effects": ["07d517ec3da6"]
"state": "87aae336d391",
"effects": ["0d02c4637af4"]
}
},
{
@@ -579,8 +615,8 @@
"settlements": {
"load": "eb79a9b3682a"
},
"state": "d0ec31ab66d5",
"effects": ["2fb25a9212b1"]
"state": "abea2b7ff9d3",
"effects": ["1d0a5fbc1e3b"]
}
},
{
@@ -591,8 +627,8 @@
"settlements": {
"load": "eb79a9b3682a"
},
"state": "d0ec31ab66d5",
"effects": ["2fb25a9212b1"]
"state": "abea2b7ff9d3",
"effects": ["1d0a5fbc1e3b"]
}
},
{
@@ -603,8 +639,8 @@
"settlements": {
"load": "eb79a9b3682a"
},
"state": "d0ec31ab66d5",
"effects": ["2fb25a9212b1"]
"state": "abea2b7ff9d3",
"effects": ["1d0a5fbc1e3b"]
}
},
{
@@ -1,760 +0,0 @@
{
"goldenFormatVersion": 6,
"operation": "settings.home-providers",
"family": "settings.home-providers",
"namedDeltas": [],
"values": {
"014c74879acc": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"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
}
}
}
},
"0a5b778e224a": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"ok": true,
"result": {
"connected": false
}
}
}
},
"2470587460aa": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "Connection lost",
"isRpcDeliveryUnknown": true
}
}
},
"3002f45fd1db": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"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
}
}
}
},
"44136fa355b3": {},
"4777b5dc4e66": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"534badf93f95": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 30000,
"error": {
"category": "Error",
"message": "Request timed out: settings.get",
"isRpcDeliveryUnknown": true
}
}
},
"540740375fe4": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "Connection lost",
"isRpcDeliveryUnknown": true
}
}
},
"571a5f07f058": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"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
}
}
}
},
"58995bbfcb17": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"722f2edb4231": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "settings.get#1 rejected",
"isRpcDeliveryUnknown": true
}
}
},
"776ed8372613": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
}
},
"79b8c1b0d1d1": {
"host-1": ["github"]
},
"84170a267808": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "Connection lost",
"isRpcDeliveryUnknown": true
}
}
},
"8fce29bab175": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"settings": {
"defaultTuiAgent": "codex",
"disabledTuiAgents": ["claude"],
"hostSettingOverrides": {},
"prBotAuthorOverrides": ["bot-user"],
"visibleTaskProviders": ["github", "linear"]
}
}
}
}
},
"9037ac778ace": {
"name": "providers",
"ordinal": 7,
"value": {
"host-1": ["github"]
}
},
"908e43fdefae": {
"name": "preflight.check#1",
"ordinal": 5,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"preflight.check\"}"
},
"975f2ff9a730": {
"name": "settings.get#1",
"ordinal": 4,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}"
},
"aa3490f8f6f8": {
"name": "linear.status#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"linear.status\"}"
},
"b67ae9ca9783": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "preflight.check#1 rejected",
"isRpcDeliveryUnknown": true
}
}
},
"d1204a893729": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 30000,
"error": {
"category": "Error",
"message": "Request timed out: preflight.check",
"isRpcDeliveryUnknown": true
}
}
},
"d9e806b6981a": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 30000,
"error": {
"category": "Error",
"message": "Request timed out: linear.status",
"isRpcDeliveryUnknown": true
}
}
},
"eb79a9b3682a": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"$rpc": "undefined"
}
},
"ec8967c2d560": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
}
},
"recording": {
"scenario": "schedules-settings-home-providers-fulfilled",
"checkpoints": [
{
"id": "settings-home-providers-fulfilled.forward:sibling-pending",
"observation": {
"sender": ["8fce29bab175", "4777b5dc4e66", "58995bbfcb17"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "settings-home-providers-fulfilled.forward:settled",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.reverse:sibling-pending",
"observation": {
"sender": ["ec8967c2d560", "776ed8372613", "58995bbfcb17"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "settings-home-providers-fulfilled.reverse:settled",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.both-reject-forward:sibling-pending",
"observation": {
"sender": ["722f2edb4231", "4777b5dc4e66", "58995bbfcb17"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.both-reject-forward:settled",
"observation": {
"sender": ["722f2edb4231", "b67ae9ca9783", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.both-reject-reverse:sibling-pending",
"observation": {
"sender": ["ec8967c2d560", "b67ae9ca9783", "58995bbfcb17"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.both-reject-reverse:settled",
"observation": {
"sender": ["722f2edb4231", "b67ae9ca9783", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.reject-peer-pending:sibling-pending",
"observation": {
"sender": ["722f2edb4231", "4777b5dc4e66", "58995bbfcb17"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.reject-peer-pending:settled",
"observation": {
"sender": ["722f2edb4231", "4777b5dc4e66", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.timeout:settled",
"observation": {
"sender": ["534badf93f95", "d1204a893729", "d9e806b6981a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.disconnect:settled",
"observation": {
"sender": ["540740375fe4", "84170a267808", "2470587460aa"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a",
"disconnect": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "settings-home-providers-fulfilled.client-cutover:settled",
"observation": {
"sender": ["3002f45fd1db", "014c74879acc", "571a5f07f058"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a",
"cutover": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
}
]
}
}
@@ -1,639 +0,0 @@
{
"goldenFormatVersion": 6,
"operation": "settings.home-providers",
"family": "settings.home-providers",
"namedDeltas": [],
"values": {
"0a5b778e224a": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"ok": true,
"result": {
"connected": false
}
}
}
},
"1425cdcf73af": {
"name": "settings.get#3",
"ordinal": 18,
"json": "{\"id\":\"frame-7\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}"
},
"1578fb797a9b": {
"name": "providers",
"ordinal": 13,
"value": {
"host-1": ["github"]
}
},
"1eef16c4ad65": {
"name": "preflight.check#2",
"ordinal": 9,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-5",
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
}
},
"235eb9cfa7e0": {
"name": "settings.get#2",
"ordinal": 8,
"json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}"
},
"2bb81a9fc867": {
"name": "providers",
"ordinal": 14,
"value": {
"host-1": ["github"]
}
},
"44136fa355b3": {},
"4777b5dc4e66": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"48ef1d658168": {
"name": "preflight.check#2",
"ordinal": 10,
"json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"preflight.check\"}"
},
"4ff081739994": {
"name": "preflight.check#2",
"ordinal": 9,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"525e8231aebc": {
"name": "preflight.check#3",
"ordinal": 19,
"json": "{\"id\":\"frame-8\",\"deviceToken\":\"recording-device\",\"method\":\"preflight.check\"}"
},
"58995bbfcb17": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"596151a25a84": {
"name": "linear.status#2",
"ordinal": 12,
"json": "{\"id\":\"frame-6\",\"deviceToken\":\"recording-device\",\"method\":\"linear.status\"}"
},
"599b30e9d8ec": {
"name": "linear.status#3",
"ordinal": 17,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"776ed8372613": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
}
},
"79b8c1b0d1d1": {
"host-1": ["github"]
},
"7a145a4c7f2f": {
"name": "linear.status#2",
"ordinal": 11,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"8e3ababdaa74": {
"name": "preflight.check#3",
"ordinal": 16,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"8fce29bab175": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"settings": {
"defaultTuiAgent": "codex",
"disabledTuiAgents": ["claude"],
"hostSettingOverrides": {},
"prBotAuthorOverrides": ["bot-user"],
"visibleTaskProviders": ["github", "linear"]
}
}
}
}
},
"908e43fdefae": {
"name": "preflight.check#1",
"ordinal": 5,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"preflight.check\"}"
},
"93f2060336d8": {
"name": "linear.status#2",
"ordinal": 11,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-6",
"ok": true,
"result": {
"connected": false
}
}
}
},
"975f2ff9a730": {
"name": "settings.get#1",
"ordinal": 4,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}"
},
"a7075803ae4d": {
"name": "linear.status#3",
"ordinal": 20,
"json": "{\"id\":\"frame-9\",\"deviceToken\":\"recording-device\",\"method\":\"linear.status\"}"
},
"aa3490f8f6f8": {
"name": "linear.status#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"linear.status\"}"
},
"eb79a9b3682a": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"$rpc": "undefined"
}
},
"ec8967c2d560": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"f6bd43dd03d3": {
"name": "settings.get#3",
"ordinal": 15,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"faeb2251338b": {
"name": "settings.get#2",
"ordinal": 7,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"ff1b4ecd513c": {
"name": "settings.get#2",
"ordinal": 7,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-4",
"ok": true,
"result": {
"settings": {
"defaultTuiAgent": "codex",
"disabledTuiAgents": ["claude"],
"hostSettingOverrides": {},
"prBotAuthorOverrides": ["bot-user"],
"visibleTaskProviders": ["github", "linear"]
}
}
}
}
}
},
"recording": {
"scenario": "settings-home-coalesced",
"checkpoints": [
{
"id": "settings-pending",
"observation": {
"sender": ["ec8967c2d560", "4777b5dc4e66", "58995bbfcb17"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a",
"overlapping-load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "settled",
"observation": {
"sender": [
"8fce29bab175",
"776ed8372613",
"0a5b778e224a",
"faeb2251338b",
"4ff081739994",
"7a145a4c7f2f"
],
"payloads": [
"975f2ff9a730",
"908e43fdefae",
"aa3490f8f6f8",
"235eb9cfa7e0",
"48ef1d658168",
"596151a25a84"
],
"settlements": {
"load": "eb79a9b3682a",
"overlapping-load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["1578fb797a9b"]
}
},
{
"id": "follow-up-settled",
"observation": {
"sender": [
"8fce29bab175",
"776ed8372613",
"0a5b778e224a",
"ff1b4ecd513c",
"1eef16c4ad65",
"93f2060336d8"
],
"payloads": [
"975f2ff9a730",
"908e43fdefae",
"aa3490f8f6f8",
"235eb9cfa7e0",
"48ef1d658168",
"596151a25a84"
],
"settlements": {
"load": "eb79a9b3682a",
"overlapping-load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["1578fb797a9b", "2bb81a9fc867"]
}
},
{
"id": "third-query",
"observation": {
"sender": [
"8fce29bab175",
"776ed8372613",
"0a5b778e224a",
"ff1b4ecd513c",
"1eef16c4ad65",
"93f2060336d8",
"f6bd43dd03d3",
"8e3ababdaa74",
"599b30e9d8ec"
],
"payloads": [
"975f2ff9a730",
"908e43fdefae",
"aa3490f8f6f8",
"235eb9cfa7e0",
"48ef1d658168",
"596151a25a84",
"1425cdcf73af",
"525e8231aebc",
"a7075803ae4d"
],
"settlements": {
"load": "eb79a9b3682a",
"overlapping-load": "eb79a9b3682a",
"third-load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["1578fb797a9b", "2bb81a9fc867"]
}
}
]
}
}
@@ -1,259 +0,0 @@
{
"goldenFormatVersion": 6,
"operation": "settings.home-providers",
"family": "settings.home-providers",
"namedDeltas": [],
"values": {
"0a5b778e224a": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"ok": true,
"result": {
"connected": false
}
}
}
},
"44136fa355b3": {},
"4777b5dc4e66": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"58995bbfcb17": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"776ed8372613": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
}
},
"79b8c1b0d1d1": {
"host-1": ["github"]
},
"8fce29bab175": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"settings": {
"defaultTuiAgent": "codex",
"disabledTuiAgents": ["claude"],
"hostSettingOverrides": {},
"prBotAuthorOverrides": ["bot-user"],
"visibleTaskProviders": ["github", "linear"]
}
}
}
}
},
"9037ac778ace": {
"name": "providers",
"ordinal": 7,
"value": {
"host-1": ["github"]
}
},
"908e43fdefae": {
"name": "preflight.check#1",
"ordinal": 5,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"preflight.check\"}"
},
"975f2ff9a730": {
"name": "settings.get#1",
"ordinal": 4,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}"
},
"aa3490f8f6f8": {
"name": "linear.status#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"linear.status\"}"
},
"eb79a9b3682a": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"$rpc": "undefined"
}
},
"ec8967c2d560": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
}
},
"recording": {
"scenario": "settings-home-providers-fulfilled",
"checkpoints": [
{
"id": "settings-pending",
"observation": {
"sender": ["ec8967c2d560", "4777b5dc4e66", "58995bbfcb17"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "settled",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
}
]
}
}
@@ -1,533 +0,0 @@
{
"goldenFormatVersion": 6,
"operation": "settings.home-providers",
"family": "settings.home-providers",
"namedDeltas": [],
"values": {
"0a5b778e224a": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"ok": true,
"result": {
"connected": false
}
}
}
},
"1594e2fa3a9c": {
"name": "preflight.check#2",
"ordinal": 12,
"json": "{\"id\":\"frame-5\",\"deviceToken\":\"recording-device\",\"method\":\"preflight.check\"}"
},
"177d9dc43a98": {
"name": "linear.status#2",
"ordinal": 10,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-6",
"ok": true,
"result": {
"connected": false
}
}
}
},
"1eef16c4ad65": {
"name": "preflight.check#2",
"ordinal": 9,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-5",
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
}
},
"44136fa355b3": {},
"44e1776d841d": {
"name": "settings.get#2",
"ordinal": 8,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"4777b5dc4e66": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"4ff081739994": {
"name": "preflight.check#2",
"ordinal": 9,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"56227393d82f": {
"name": "providers",
"ordinal": 14,
"value": {
"host-1": ["github", "jira"]
}
},
"58995bbfcb17": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"5d9b2e6167a0": {
"name": "linear.status#2",
"ordinal": 10,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"6198eef8dbfc": {
"name": "settings.get#2",
"ordinal": 8,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "refused",
"message": "settings refused"
},
"id": "frame-4",
"ok": false
}
}
},
"776ed8372613": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
}
},
"79b8c1b0d1d1": {
"host-1": ["github"]
},
"868d9c7eed63": {
"host-1": ["github", "jira"]
},
"8fce29bab175": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-1",
"ok": true,
"result": {
"settings": {
"defaultTuiAgent": "codex",
"disabledTuiAgents": ["claude"],
"hostSettingOverrides": {},
"prBotAuthorOverrides": ["bot-user"],
"visibleTaskProviders": ["github", "linear"]
}
}
}
}
},
"9037ac778ace": {
"name": "providers",
"ordinal": 7,
"value": {
"host-1": ["github"]
}
},
"908e43fdefae": {
"name": "preflight.check#1",
"ordinal": 5,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"preflight.check\"}"
},
"975f2ff9a730": {
"name": "settings.get#1",
"ordinal": 4,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}"
},
"9b19d4e082d8": {
"name": "settings.get#2",
"ordinal": 11,
"json": "{\"id\":\"frame-4\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}"
},
"aa3490f8f6f8": {
"name": "linear.status#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"linear.status\"}"
},
"eb79a9b3682a": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"$rpc": "undefined"
}
},
"ec8967c2d560": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"fd11a094b656": {
"name": "linear.status#2",
"ordinal": 13,
"json": "{\"id\":\"frame-6\",\"deviceToken\":\"recording-device\",\"method\":\"linear.status\"}"
}
},
"recording": {
"scenario": "settings-home-providers-refuse-after-data",
"checkpoints": [
{
"id": "settings-pending",
"observation": {
"sender": ["ec8967c2d560", "4777b5dc4e66", "58995bbfcb17"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "settled",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "data-present",
"observation": {
"sender": ["8fce29bab175", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "refresh-pending",
"observation": {
"sender": [
"8fce29bab175",
"776ed8372613",
"0a5b778e224a",
"44e1776d841d",
"4ff081739994",
"5d9b2e6167a0"
],
"payloads": [
"975f2ff9a730",
"908e43fdefae",
"aa3490f8f6f8",
"9b19d4e082d8",
"1594e2fa3a9c",
"fd11a094b656"
],
"settlements": {
"load": "eb79a9b3682a",
"reload": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
},
{
"id": "refused-after-data",
"observation": {
"sender": [
"8fce29bab175",
"776ed8372613",
"0a5b778e224a",
"6198eef8dbfc",
"1eef16c4ad65",
"177d9dc43a98"
],
"payloads": [
"975f2ff9a730",
"908e43fdefae",
"aa3490f8f6f8",
"9b19d4e082d8",
"1594e2fa3a9c",
"fd11a094b656"
],
"settlements": {
"load": "eb79a9b3682a",
"reload": "eb79a9b3682a"
},
"state": "868d9c7eed63",
"effects": ["9037ac778ace", "56227393d82f"]
}
}
]
}
}
@@ -1,254 +0,0 @@
{
"goldenFormatVersion": 6,
"operation": "settings.home-providers",
"family": "settings.home-providers",
"namedDeltas": [],
"values": {
"0a5b778e224a": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"ok": true,
"result": {
"connected": false
}
}
}
},
"44136fa355b3": {},
"4777b5dc4e66": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"58995bbfcb17": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"776ed8372613": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
}
},
"868d9c7eed63": {
"host-1": ["github", "jira"]
},
"908e43fdefae": {
"name": "preflight.check#1",
"ordinal": 5,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"preflight.check\"}"
},
"975f2ff9a730": {
"name": "settings.get#1",
"ordinal": 4,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}"
},
"a940d6fc7d14": {
"name": "providers",
"ordinal": 7,
"value": {
"host-1": ["github", "jira"]
}
},
"aa3490f8f6f8": {
"name": "linear.status#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"linear.status\"}"
},
"ba6fa6c1c0e6": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"error": {
"code": "refused",
"message": "settings refused"
},
"id": "frame-1",
"ok": false
}
}
},
"eb79a9b3682a": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"$rpc": "undefined"
}
},
"ec8967c2d560": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
}
},
"recording": {
"scenario": "settings-home-providers-refused",
"checkpoints": [
{
"id": "settings-pending",
"observation": {
"sender": ["ec8967c2d560", "4777b5dc4e66", "58995bbfcb17"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "settled",
"observation": {
"sender": ["ba6fa6c1c0e6", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "868d9c7eed63",
"effects": ["a940d6fc7d14"]
}
}
]
}
}
@@ -1,251 +0,0 @@
{
"goldenFormatVersion": 6,
"operation": "settings.home-providers",
"family": "settings.home-providers",
"namedDeltas": [],
"values": {
"0a5b778e224a": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-3",
"ok": true,
"result": {
"connected": false
}
}
}
},
"30e5e23fe0a9": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "rejected",
"startedAt": 0,
"settledAt": 0,
"error": {
"category": "Error",
"message": "settings disconnected",
"isRpcDeliveryUnknown": true
}
}
},
"44136fa355b3": {},
"4777b5dc4e66": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"58995bbfcb17": {
"name": "linear.status#1",
"ordinal": 3,
"args": [
{
"name": "method",
"value": "linear.status"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
},
"776ed8372613": {
"name": "preflight.check#1",
"ordinal": 2,
"args": [
{
"name": "method",
"value": "preflight.check"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"id": "frame-2",
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
}
},
"79b8c1b0d1d1": {
"host-1": ["github"]
},
"9037ac778ace": {
"name": "providers",
"ordinal": 7,
"value": {
"host-1": ["github"]
}
},
"908e43fdefae": {
"name": "preflight.check#1",
"ordinal": 5,
"json": "{\"id\":\"frame-2\",\"deviceToken\":\"recording-device\",\"method\":\"preflight.check\"}"
},
"975f2ff9a730": {
"name": "settings.get#1",
"ordinal": 4,
"json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"settings.get\"}"
},
"aa3490f8f6f8": {
"name": "linear.status#1",
"ordinal": 6,
"json": "{\"id\":\"frame-3\",\"deviceToken\":\"recording-device\",\"method\":\"linear.status\"}"
},
"eb79a9b3682a": {
"status": "fulfilled",
"startedAt": 0,
"settledAt": 0,
"value": {
"$rpc": "undefined"
}
},
"ec8967c2d560": {
"name": "settings.get#1",
"ordinal": 1,
"args": [
{
"name": "method",
"value": "settings.get"
},
{
"name": "params",
"value": {
"$rpc": "undefined"
}
},
{
"name": "options",
"value": {
"$rpc": "absent"
}
}
],
"settlement": {
"status": "pending",
"startedAt": 0
}
}
},
"recording": {
"scenario": "settings-home-providers-transport-error",
"checkpoints": [
{
"id": "settings-pending",
"observation": {
"sender": ["ec8967c2d560", "4777b5dc4e66", "58995bbfcb17"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "44136fa355b3",
"effects": []
}
},
{
"id": "settled",
"observation": {
"sender": ["30e5e23fe0a9", "776ed8372613", "0a5b778e224a"],
"payloads": ["975f2ff9a730", "908e43fdefae", "aa3490f8f6f8"],
"settlements": {
"load": "eb79a9b3682a"
},
"state": "79b8c1b0d1d1",
"effects": ["9037ac778ace"]
}
}
]
}
}
@@ -4,12 +4,29 @@
"family": "worktree.home-catalog",
"namedDeltas": [],
"values": {
"07d517ec3da6": {
"0d02c4637af4": {
"name": "info",
"ordinal": 3,
"value": {
"host-1": {
"activeCount": 2,
"attention": [
{
"branch": {
"$rpc": "undefined"
},
"displayName": "One",
"dotStatus": "working",
"lastOutputAt": {
"$rpc": "undefined"
},
"repo": "Repo",
"workingMode": {
"$rpc": "undefined"
},
"worktreeId": "w-1"
}
],
"countsProvenAt": 1767225600000,
"hostId": "host-1",
"lastActiveWorktree": {
@@ -96,9 +113,26 @@
"startedAt": 0
}
},
"6624fb97ea31": {
"87aae336d391": {
"host-1": {
"activeCount": 2,
"attention": [
{
"branch": {
"$rpc": "undefined"
},
"displayName": "One",
"dotStatus": "working",
"lastOutputAt": {
"$rpc": "undefined"
},
"repo": "Repo",
"workingMode": {
"$rpc": "undefined"
},
"worktreeId": "w-1"
}
],
"countsProvenAt": 1767225600000,
"hostId": "host-1",
"lastActiveWorktree": {
@@ -151,8 +185,8 @@
"settlements": {
"load": "eb79a9b3682a"
},
"state": "6624fb97ea31",
"effects": ["07d517ec3da6"]
"state": "87aae336d391",
"effects": ["0d02c4637af4"]
}
}
]
-494
View File
@@ -681,182 +681,6 @@
],
"namedDeltas": ["new-workspace-runtime-context-null-results-degrade-to-absent"]
},
{
"id": "settings-home-providers-fulfilled",
"operation": "settings.home-providers",
"family": "settings.home-providers",
"sites": ["mobile/src/home/mobile-home-host-requests.ts"],
"schedules": ["settings-first"],
"steps": [
{
"action": "load",
"id": "load"
},
{
"checkpoint": "settings-pending"
},
{
"complete": "settings.get#1",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"settings": {
"disabledTuiAgents": ["claude"],
"defaultTuiAgent": "codex",
"prBotAuthorOverrides": ["bot-user"],
"visibleTaskProviders": ["github", "linear"],
"hostSettingOverrides": {}
}
}
}
},
{
"complete": "preflight.check#1",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
},
{
"complete": "linear.status#1",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"connected": false
}
}
},
{
"checkpoint": "settled"
}
]
},
{
"id": "settings-home-providers-refused",
"operation": "settings.home-providers",
"family": "settings.home-providers",
"sites": ["mobile/src/home/mobile-home-host-requests.ts"],
"schedules": ["settings-first"],
"steps": [
{
"action": "load",
"id": "load"
},
{
"checkpoint": "settings-pending"
},
{
"complete": "settings.get#1",
"params": {
"$undefined": true
},
"reply": {
"ok": false,
"error": {
"code": "refused",
"message": "settings refused"
}
}
},
{
"complete": "preflight.check#1",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
},
{
"complete": "linear.status#1",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"connected": false
}
}
},
{
"checkpoint": "settled"
}
]
},
{
"id": "settings-home-providers-transport-error",
"operation": "settings.home-providers",
"family": "settings.home-providers",
"sites": ["mobile/src/home/mobile-home-host-requests.ts"],
"schedules": ["settings-first"],
"steps": [
{
"action": "load",
"id": "load"
},
{
"checkpoint": "settings-pending"
},
{
"complete": "settings.get#1",
"params": {
"$undefined": true
},
"reject": {
"message": "settings disconnected",
"deliveryUnknown": true
}
},
{
"complete": "preflight.check#1",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
},
{
"complete": "linear.status#1",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"connected": false
}
}
},
{
"checkpoint": "settled"
}
]
},
{
"id": "settings-resume-metadata-fulfilled",
"operation": "settings.resume-metadata",
@@ -2263,127 +2087,6 @@
}
]
},
{
"id": "settings-home-coalesced",
"operation": "settings.home-providers",
"family": "settings.home-providers",
"sites": ["mobile/src/home/mobile-home-host-requests.ts"],
"schedules": ["stale-inflight-cleanup", "follow-up-cache-observation"],
"steps": [
{
"action": "load",
"id": "load"
},
{
"action": "load",
"id": "overlapping-load"
},
{
"checkpoint": "settings-pending"
},
{
"complete": "settings.get#1",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"settings": {
"disabledTuiAgents": ["claude"],
"defaultTuiAgent": "codex",
"prBotAuthorOverrides": ["bot-user"],
"visibleTaskProviders": ["github", "linear"],
"hostSettingOverrides": {}
}
}
}
},
{
"complete": "preflight.check#1",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
},
{
"complete": "linear.status#1",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"connected": false
}
}
},
{
"checkpoint": "settled"
},
{
"complete": "settings.get#2",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"settings": {
"disabledTuiAgents": ["claude"],
"defaultTuiAgent": "codex",
"prBotAuthorOverrides": ["bot-user"],
"visibleTaskProviders": ["github", "linear"],
"hostSettingOverrides": {}
}
}
}
},
{
"complete": "preflight.check#2",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
},
{
"complete": "linear.status#2",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"connected": false
}
}
},
{
"checkpoint": "follow-up-settled"
},
{
"action": "load",
"id": "third-load"
},
{
"checkpoint": "third-query"
}
]
},
{
"id": "settings-repo-cache-expiry",
"operation": "settings.repo-metadata",
@@ -2977,121 +2680,6 @@
}
]
},
{
"id": "settings-home-providers-refuse-after-data",
"operation": "settings.home-providers",
"family": "settings.home-providers",
"sites": ["mobile/src/home/mobile-home-host-requests.ts"],
"schedules": ["success-then-refused"],
"steps": [
{
"action": "load",
"id": "load"
},
{
"checkpoint": "settings-pending"
},
{
"complete": "settings.get#1",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"settings": {
"disabledTuiAgents": ["claude"],
"defaultTuiAgent": "codex",
"prBotAuthorOverrides": ["bot-user"],
"visibleTaskProviders": ["github", "linear"],
"hostSettingOverrides": {}
}
}
}
},
{
"complete": "preflight.check#1",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
},
{
"complete": "linear.status#1",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"connected": false
}
}
},
{
"checkpoint": "settled"
},
{
"checkpoint": "data-present"
},
{
"action": "load",
"id": "reload"
},
{
"checkpoint": "refresh-pending"
},
{
"complete": "settings.get#2",
"params": {
"$undefined": true
},
"reply": {
"ok": false,
"error": {
"code": "refused",
"message": "settings refused"
}
}
},
{
"complete": "preflight.check#2",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"glab": {
"installed": false
}
}
}
},
{
"complete": "linear.status#2",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"connected": false
}
}
},
{
"checkpoint": "refused-after-data"
}
]
},
{
"id": "settings-resume-metadata-refuse-after-data",
"operation": "settings.resume-metadata",
@@ -8058,38 +7646,6 @@
}
]
},
{
"id": "home-host-stats",
"operation": "home.host-stats",
"family": "home.host-stats",
"sites": ["mobile/src/home/mobile-home-host-requests.ts"],
"schedules": [],
"steps": [
{
"action": "load",
"id": "load"
},
{
"checkpoint": "stats-pending"
},
{
"complete": "stats.summary#1",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"totalWorktrees": 3,
"activeWorktrees": 1
}
}
},
{
"checkpoint": "settled"
}
]
},
{
"id": "worktree-home-catalog",
"operation": "worktree.home-catalog",
@@ -19858,56 +19414,6 @@
}
]
},
{
"id": "home-host-accounts",
"operation": "home.host-accounts",
"family": "home.host-accounts",
"sites": ["mobile/src/home/mobile-home-host-requests.ts"],
"schedules": [],
"steps": [
{
"action": "load",
"id": "load"
},
{
"checkpoint": "accounts-pending"
},
{
"complete": "accounts.list#1",
"params": {
"$undefined": true
},
"reply": {
"ok": true,
"result": {
"claude": {
"accounts": [
{
"id": "claude-1",
"email": "claude@example.test",
"updatedAt": 1700000000000
}
],
"activeAccountId": "claude-1"
},
"codex": {
"accounts": [],
"activeAccountId": null
},
"rateLimits": {
"claude": null,
"codex": null,
"inactiveClaudeAccounts": [],
"inactiveCodexAccounts": []
}
}
}
},
{
"checkpoint": "accounts-published"
}
]
},
{
"id": "notifications-display-test-accepted",
"operation": "notifications.display-test-screen",
@@ -42,19 +42,6 @@
"settings-workspace-context-transport-error"
]
},
{
"manifestOperation": "main:mobile/src/home/mobile-home-host-requests.ts:fetchMobileHomeTaskProviders:settings.get:76",
"manifestScenarioIds": [
"main:mobile/src/home/mobile-home-host-requests.ts:fetchMobileHomeTaskProviders:settings.get:76:fulfilled",
"main:mobile/src/home/mobile-home-host-requests.ts:fetchMobileHomeTaskProviders:settings.get:76:refused",
"main:mobile/src/home/mobile-home-host-requests.ts:fetchMobileHomeTaskProviders:settings.get:76:transport-error"
],
"scenarios": [
"settings-home-providers-fulfilled",
"settings-home-providers-refused",
"settings-home-providers-transport-error"
]
},
{
"manifestOperation": "main:mobile/src/host-screen/use-host-repo-metadata.ts:fetchRepoMetadata:settings.get:124",
"manifestScenarioIds": [
@@ -1,14 +0,0 @@
import { useCallback } from 'react'
import { useOpenHostStackRoute } from '../navigation/use-open-host-stack-route'
import { mobileAccountsRouteTarget } from './mobile-accounts-route'
export function useOpenMobileAccounts(): (hostId: string) => void {
const openHostStackRoute = useOpenHostStackRoute()
return useCallback(
(hostId) => {
openHostStackRoute(hostId, mobileAccountsRouteTarget(hostId))
},
[openHostStackRoute]
)
}
+1 -3
View File
@@ -1,6 +1,5 @@
// Seed cold-start/resume cards from persisted last-good snapshot, then update via RPC; avoids ~1s empty flash while reconnecting.
import AsyncStorage from '@react-native-async-storage/async-storage'
import type { AccountsSnapshot } from '../components/AccountUsage'
// Persist canonical countsProvenAt so rehydrated counts retain freshness evidence.
import type { HostWorktreeInfo } from '../worktree/home-worktree-info'
@@ -8,7 +7,6 @@ const STORAGE_KEY = 'orca:home-snapshot:v1'
export type HomeSnapshot = {
worktreeInfo: Record<string, HostWorktreeInfo>
accountsByHost: Record<string, AccountsSnapshot>
savedAt: number
}
@@ -29,7 +27,7 @@ export async function loadHomeSnapshot(): Promise<HomeSnapshot | null> {
typeof parsed !== 'object' ||
parsed === null ||
typeof parsed.worktreeInfo !== 'object' ||
typeof parsed.accountsByHost !== 'object'
parsed.worktreeInfo === null
) {
return null
}
+3 -3
View File
@@ -9,7 +9,7 @@ import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useWorkingRingRotation } from './use-working-ring-rotation'
// Match desktop sidebar StatusIndicator colors/sizes for the worktree rollup; palette distinguishes moving/alive/completed states.
const statusColors = (): Record<WorktreeStatus, string> => ({
export const agentStatusColors = (): Record<WorktreeStatus, string> => ({
working: '#eab308',
monitoring: '#eab308',
active: '#10b981',
@@ -34,7 +34,7 @@ export function AgentSpinner({
status === 'monitoring' || (status === 'working' && workingMode === 'monitoring')
const rotate = useWorkingRingRotation(status === 'working' && !monitoring)
const palette = statusColors()
const palette = agentStatusColors()
const color = palette[status] ?? palette.inactive
if (monitoring) {
@@ -93,7 +93,7 @@ const styles = themedStyleSheet(() => ({
borderRadius: radii.pill,
borderWidth: 1.5,
borderStyle: 'dashed',
borderColor: statusColors().unconfirmed
borderColor: agentStatusColors().unconfirmed
},
spinner: {
width: 8,
@@ -1,55 +0,0 @@
import { createElement } from 'react'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { HostProfile } from '../transport/types'
import { MobileHomeQuickActions } from './MobileHomeQuickActions'
import { findAllByHostType } from '../test-support/react-test-renderer-host-nodes'
vi.mock('react-native', () => ({
Pressable: 'Pressable',
StyleSheet: { create: (styles: unknown) => styles },
Text: 'Text',
View: 'View'
}))
vi.mock('lucide-react-native', () => ({
Plus: 'Plus',
QrCode: 'QrCode'
}))
describe('MobileHomeQuickActions', () => {
let renderer: ReactTestRenderer | null = null
afterEach(() => {
act(() => renderer?.unmount())
renderer = null
vi.restoreAllMocks()
})
async function renderQuickActions(connectedHosts: HostProfile[]) {
const onPairDesktop = vi.fn()
const onCreateWorkspace = vi.fn()
const consoleError = vi.spyOn(console, 'error').mockImplementation((...args) => {
if (typeof args[0] !== 'string' || !args[0].includes('react-test-renderer is deprecated')) {
throw new Error(String(args[0]))
}
})
await act(async () => {
renderer = create(
createElement(MobileHomeQuickActions, { connectedHosts, onPairDesktop, onCreateWorkspace })
)
})
consoleError.mockRestore()
}
function newWorkspaceButton() {
return findAllByHostType(renderer!.root, 'Pressable')[1]
}
it('disables workspace creation without a connected host', async () => {
await renderQuickActions([])
expect(newWorkspaceButton().props.disabled).toBe(true)
expect(newWorkspaceButton().props.accessibilityState).toEqual({ disabled: true })
})
})
@@ -1,107 +0,0 @@
import { Plus, QrCode } from 'lucide-react-native'
import { Pressable, Text, View } from 'react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { HostProfile } from '../transport/types'
import { colors, radii, spacing } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
type Props = {
connectedHosts: HostProfile[]
onPairDesktop: () => void
onCreateWorkspace: () => void
}
export function MobileHomeQuickActions(props: Props) {
const t = useMobileTranslation()
useMobileColorScheme()
const canCreateWorkspace = props.connectedHosts.length > 0
return (
<>
<Text style={styles.sectionHeading}>
{t('components.homeQuickActions.title', 'Quick Actions')}
</Text>
<View style={styles.quickActions}>
<Pressable
accessibilityRole="button"
style={({ pressed }) => [styles.quickAction, pressed && styles.quickActionPressed]}
onPress={props.onPairDesktop}
>
<View style={styles.quickActionIcon}>
<QrCode size={16} color={colors.textSecondary} />
</View>
<Text style={styles.quickActionLabel}>
{t('components.homeQuickActions.pairDesktop', 'Pair Desktop')}
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
accessibilityState={{ disabled: !canCreateWorkspace }}
disabled={!canCreateWorkspace}
style={({ pressed }) => [
styles.quickAction,
!canCreateWorkspace && styles.quickActionDisabled,
pressed && styles.quickActionPressed
]}
onPress={props.onCreateWorkspace}
>
<View style={styles.quickActionIcon}>
<Plus size={16} color={colors.textSecondary} />
</View>
<Text style={styles.quickActionLabel}>
{t('components.homeQuickActions.newWorkspace', 'New Workspace')}
</Text>
</Pressable>
</View>
</>
)
}
const styles = themedStyleSheet(() => ({
sectionHeading: {
marginTop: spacing.xl,
marginBottom: spacing.sm,
paddingHorizontal: spacing.xs,
color: colors.textMuted,
fontSize: 11,
fontWeight: '600',
textTransform: 'uppercase',
letterSpacing: 0.6
},
quickActions: {
flexDirection: 'row',
gap: spacing.sm
},
quickAction: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
gap: spacing.sm + 2,
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm + 2,
borderWidth: 1,
borderColor: colors.borderSubtle,
borderRadius: radii.surface,
backgroundColor: colors.bgPanel
},
quickActionPressed: {
backgroundColor: colors.bgRaised
},
quickActionDisabled: {
opacity: 0.45
},
quickActionIcon: {
width: 28,
height: 28,
borderRadius: radii.tile,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.bgRaised
},
quickActionLabel: {
color: colors.textSecondary,
fontSize: 12,
fontWeight: '600'
}
}))
@@ -1,166 +0,0 @@
import { createElement } from 'react'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { ConnectionVerdict } from '../transport/connection-health'
import type { MobileConnectionPath } from '../transport/stable-logical-rpc-client'
import type { ConnectionState, HostCredentialStatus, HostProfile } from '../transport/types'
import {
markHomeWorktreeCatalogUnavailable,
type HostWorktreeInfo
} from '../worktree/home-worktree-info'
import { MobileHostCard } from './MobileHostCard'
import { findAllByHostType } from '../test-support/react-test-renderer-host-nodes'
vi.mock('react-native', () => ({
Pressable: 'Pressable',
StyleSheet: { create: <T,>(styles: T) => styles },
Text: 'Text',
View: 'View'
}))
vi.mock('lucide-react-native', () => ({ Monitor: 'Monitor', MoreVertical: 'MoreVertical' }))
vi.mock('./StatusDot', () => ({ StatusDot: 'StatusDot' }))
const host: HostProfile = {
id: 'host-1',
name: 'Studio',
endpoint: 'ws://studio.local:8765',
deviceToken: 'token',
publicKeyB64: 'key',
lastConnected: 0
}
const verdict: ConnectionVerdict = { kind: 'normal', label: 'Connected' }
const loaded: HostWorktreeInfo = {
hostId: 'host-1',
totalWorktrees: 12,
activeCount: 2,
lastActiveWorktree: null,
countsProvenAt: Date.now()
}
describe('MobileHostCard', () => {
let renderer: ReactTestRenderer | null = null
afterEach(() => {
act(() => renderer?.unmount())
renderer = null
})
async function renderCard(
worktreeInfo: HostWorktreeInfo | undefined,
overrides?: {
state?: ConnectionState
verdict?: ConnectionVerdict
path?: MobileConnectionPath
credentialStatus?: HostCredentialStatus
}
): Promise<string[]> {
await act(async () => {
renderer = create(
createElement(MobileHostCard, {
host,
state: overrides?.state ?? 'connected',
verdict: overrides?.verdict ?? verdict,
path: overrides?.path ?? 'lan',
credentialStatus: overrides?.credentialStatus,
worktreeInfo,
onPress: () => {},
onLongPress: () => {},
onOpenActions: () => {}
})
)
})
return findAllByHostType(renderer!.root, 'Text').flatMap((node) =>
node.children.filter((child) => typeof child === 'string')
)
}
it('renders the counts the host proved', async () => {
expect(await renderCard(loaded)).toContain('12 worktrees · 2 active')
})
it('keeps rendering the last proven counts after a failed refresh', async () => {
// Regress caller dropping counts deliberately preserved on failure.
expect(await renderCard(markHomeWorktreeCatalogUnavailable(loaded, 'host-1'))).toContain(
'Last known: 12 worktrees · 2 active'
)
})
it('never asserts a count for a catalog that failed with nothing proven', async () => {
expect(await renderCard(markHomeWorktreeCatalogUnavailable(undefined, 'host-1'))).toContain(
'Worktree list unavailable'
)
})
it('names the relay while the dial is still in flight', async () => {
const lines = await renderCard(undefined, {
state: 'connecting',
verdict: { kind: 'normal', label: 'Connecting via Relay…', dialing: true },
path: 'relay'
})
expect(lines).toContain('Connecting via Relay…')
expect(lines).not.toContain(' · Orca Relay')
})
it('names the relay while a failed direct dial is still retrying', async () => {
const lines = await renderCard(undefined, {
state: 'reconnecting',
verdict: { kind: 'normal', label: 'Connecting via Relay…', dialing: true },
path: 'relay'
})
expect(lines).toContain('Connecting via Relay…')
expect(lines).not.toContain(' · Orca Relay')
})
it('leaves an idle disconnected host unlabelled', async () => {
const lines = await renderCard(undefined, {
state: 'disconnected',
verdict: { kind: 'normal', label: 'Disconnected' },
path: 'relay'
})
expect(lines).not.toContain(' · Orca Relay')
})
it('does not guess a direct path before the dial resolves', async () => {
const lines = await renderCard(undefined, {
state: 'connecting',
verdict: { kind: 'normal', label: 'Connecting…', dialing: true },
path: 'lan'
})
expect(lines).not.toContain(' · Direct · LAN')
})
it('shows no worktree line before the first read lands', async () => {
const lines = await renderCard(undefined)
expect(lines).not.toContain('0 worktrees')
expect(lines).not.toContain('Worktree list unavailable')
})
it('offers re-pairing when the credential is missing', async () => {
const lines = await renderCard(loaded, {
state: 'connected',
verdict: { kind: 'auth-failed', label: 'Pairing invalid' },
credentialStatus: 'missing'
})
expect(lines).toContain('Pairing invalid')
expect(lines).toContain('Tap to re-pair with your desktop')
expect(lines).not.toContain('12 worktrees · 2 active')
})
it('offers a retry without declaring a transient read failure invalid', async () => {
const lines = await renderCard(undefined, {
state: 'disconnected',
verdict: { kind: 'normal', label: 'Disconnected' },
credentialStatus: 'temporarily-unavailable'
})
expect(lines).toContain('Pairing temporarily unavailable')
expect(lines).toContain('Unlock your phone, then tap to retry')
expect(lines).not.toContain('Pairing invalid')
})
})
@@ -1,350 +0,0 @@
import { createElement, type ReactElement } from 'react'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { MobileHostCard } from './MobileHostCard'
import {
recordHostDescriptor,
resetHostDescriptorStoreForTests
} from '../transport/host-descriptor-store'
import { findAllByHostType } from '../test-support/react-test-renderer-host-nodes'
vi.mock('react-native', () => ({
Pressable: 'Pressable',
StyleSheet: { create: (styles: unknown) => styles },
Text: 'Text',
View: 'View'
}))
vi.mock('lucide-react-native', () => ({
Monitor: 'Monitor',
MoreVertical: 'MoreVertical'
}))
vi.mock('./StatusDot', () => ({
StatusDot: 'StatusDot'
}))
function suppressRendererDeprecation() {
return vi.spyOn(console, 'error').mockImplementation((...args) => {
if (typeof args[0] !== 'string' || !args[0].includes('react-test-renderer is deprecated')) {
throw new Error(String(args[0]))
}
})
}
async function render(element: ReactElement): Promise<ReactTestRenderer> {
const consoleError = suppressRendererDeprecation()
const mounted: { renderer?: ReactTestRenderer } = {}
await act(async () => {
mounted.renderer = create(element)
})
consoleError.mockRestore()
if (!mounted.renderer) {
throw new Error('MobileHostCard did not mount')
}
return mounted.renderer
}
describe('MobileHostCard', () => {
let renderer: ReactTestRenderer | null = null
afterEach(() => {
act(() => renderer?.unmount())
renderer = null
resetHostDescriptorStoreForTests()
vi.restoreAllMocks()
})
it('keeps host navigation and actions as separate accessible controls', async () => {
const onPress = vi.fn()
const onLongPress = vi.fn()
const onOpenActions = vi.fn()
renderer = await render(
createElement(MobileHostCard, {
host: {
id: 'desk',
name: 'Desk',
endpoint: 'ws://192.168.1.2:6768',
deviceToken: 'token',
publicKeyB64: 'key',
lastConnected: 1
},
state: 'disconnected',
verdict: { kind: 'normal', label: 'Disconnected' },
path: 'lan',
onPress,
onLongPress,
onOpenActions
})
)
const buttons = findAllByHostType(renderer.root, 'Pressable')
expect(buttons).toHaveLength(2)
expect(buttons[0].props.accessibilityRole).toBe('button')
expect(buttons[0].props.accessibilityLabel).toBe('Open Desk, Disconnected')
expect(buttons[1].props.accessibilityRole).toBe('button')
expect(buttons[1].props.accessibilityLabel).toBe('Actions for Desk')
act(() => buttons[1].props.onPress())
expect(onOpenActions).toHaveBeenCalledOnce()
expect(onPress).not.toHaveBeenCalled()
act(() => buttons[0].props.onPress())
act(() => buttons[0].props.onLongPress())
expect(onPress).toHaveBeenCalledOnce()
expect(onLongPress).toHaveBeenCalledOnce()
})
it('announces the connection path without the visual separator', async () => {
renderer = await render(
createElement(MobileHostCard, {
host: {
id: 'desk',
name: 'Desk',
endpoint: 'ws://192.168.1.2:6768',
deviceToken: 'token',
publicKeyB64: 'key',
lastConnected: 1
},
state: 'connected',
verdict: { kind: 'normal', label: 'Connected' },
path: 'tailscale',
worktreeInfo: {
hostId: 'desk',
totalWorktrees: 3,
activeCount: 2,
lastActiveWorktree: null,
countsProvenAt: Date.now()
},
onPress: vi.fn(),
onLongPress: vi.fn(),
onOpenActions: vi.fn()
})
)
const navigationButton = findAllByHostType(renderer.root, 'Pressable')[0]
expect(navigationButton.props.accessibilityLabel).toBe(
'Open Desk, Connected, Direct via Tailscale, 3 worktrees, 2 active'
)
})
it('keeps a phone rename above a disagreeing machine descriptor', async () => {
recordHostDescriptor('desk', { machineName: 'm4airs-Air', platform: 'darwin' })
renderer = await render(
createElement(MobileHostCard, {
host: {
id: 'desk',
name: 'Windows-Low Spec',
personalName: 'Windows-Low Spec',
endpoint: 'ws://192.168.1.2:6768',
deviceToken: 'token',
publicKeyB64: 'key',
lastConnected: 1
},
state: 'connected',
verdict: { kind: 'normal', label: 'Connected' },
path: 'lan',
onPress: vi.fn(),
onLongPress: vi.fn(),
onOpenActions: vi.fn()
})
)
const texts = findAllByHostType(renderer.root, 'Text').map((node) => node.children.join(''))
expect(texts).toContain('Windows-Low Spec')
expect(texts).toContain('macOS · m4airs-Air')
})
it('titles an unrenamed host with the live machine name before its stored name catches up', async () => {
// The desktop was renamed; the stored `name` still holds the old machine name until reload.
recordHostDescriptor('desk', { machineName: 'Studio 2', platform: 'darwin' })
renderer = await render(
createElement(MobileHostCard, {
host: {
id: 'desk',
name: 'Studio',
lastKnownMachineName: 'Studio',
lastKnownHostPlatform: 'darwin',
endpoint: 'ws://192.168.1.2:6768',
deviceToken: 'token',
publicKeyB64: 'key',
lastConnected: 1
},
state: 'connected',
verdict: { kind: 'normal', label: 'Connected' },
path: 'lan',
onPress: vi.fn(),
onLongPress: vi.fn(),
onOpenActions: vi.fn()
})
)
const texts = findAllByHostType(renderer.root, 'Text').map((node) => node.children.join(''))
expect(texts).toContain('Studio 2')
expect(texts).toContain('macOS')
expect(texts).not.toContain('Studio')
})
it('shows the stored descriptor when the host is offline, as after a restart', async () => {
// No live descriptor is recorded: the row has only what the stored profile carries.
renderer = await render(
createElement(MobileHostCard, {
host: {
id: 'desk',
name: 'Desk',
personalName: 'Desk',
lastKnownMachineName: 'm4airs-Air',
lastKnownHostPlatform: 'darwin',
endpoint: 'ws://192.168.1.2:6768',
deviceToken: 'token',
publicKeyB64: 'key',
lastConnected: 1
},
state: 'disconnected',
verdict: { kind: 'normal', label: 'Disconnected' },
path: 'lan',
onPress: vi.fn(),
onLongPress: vi.fn(),
onOpenActions: vi.fn()
})
)
expect(
findAllByHostType(renderer.root, 'Text').map((node) => node.children.join(''))
).toContain('macOS · m4airs-Air')
})
it('collapses the machine name into the OS line when it is the shown name', async () => {
recordHostDescriptor('desk', { machineName: 'Desk', platform: 'darwin' })
renderer = await render(
createElement(MobileHostCard, {
host: {
id: 'desk',
name: 'Desk',
endpoint: 'ws://192.168.1.2:6768',
deviceToken: 'token',
publicKeyB64: 'key',
lastConnected: 1
},
state: 'connected',
verdict: { kind: 'normal', label: 'Connected' },
path: 'lan',
onPress: vi.fn(),
onLongPress: vi.fn(),
onOpenActions: vi.fn()
})
)
const texts = findAllByHostType(renderer.root, 'Text').map((node) => node.children.join(''))
expect(texts).toContain('macOS')
expect(texts).not.toContain('macOS · Desk')
})
it('preserves the connected worktree-catalog failure state', async () => {
renderer = await render(
createElement(MobileHostCard, {
host: {
id: 'desk',
name: 'Desk',
endpoint: 'ws://192.168.1.2:6768',
deviceToken: 'token',
publicKeyB64: 'key',
lastConnected: 1
},
state: 'connected',
verdict: { kind: 'normal', label: 'Connected' },
path: 'relay',
worktreeInfo: {
hostId: 'desk',
totalWorktrees: 0,
activeCount: 0,
lastActiveWorktree: null,
catalogUnavailable: true
},
onPress: vi.fn(),
onLongPress: vi.fn(),
onOpenActions: vi.fn()
})
)
const navigationButton = findAllByHostType(renderer.root, 'Pressable')[0]
expect(navigationButton.props.accessibilityLabel).toBe(
'Open Desk, Connected, Orca Relay, Worktree list unavailable'
)
expect(
findAllByHostType(renderer.root, 'Text').some((node) =>
node.children.includes('Worktree list unavailable')
)
).toBe(true)
})
it('includes visible offline recovery guidance in the navigation label', async () => {
renderer = await render(
createElement(MobileHostCard, {
host: {
id: 'desk',
name: 'Desk',
endpoint: 'ws://192.168.1.2:6768',
deviceToken: 'token',
publicKeyB64: 'key',
lastConnected: 1
},
state: 'reconnecting',
verdict: {
kind: 'unreachable',
label: "Can't reach desktop",
reason: 'never-connected'
},
path: 'lan',
onPress: vi.fn(),
onLongPress: vi.fn(),
onOpenActions: vi.fn()
})
)
const navigationButton = findAllByHostType(renderer.root, 'Pressable')[0]
expect(navigationButton.props.accessibilityLabel).toBe(
"Open Desk, Can't reach desktop, Update desktop Orca and sign in to connect from anywhere"
)
})
it('renders the verdict detail as a second line and announces it', async () => {
renderer = await render(
createElement(MobileHostCard, {
host: {
id: 'desk',
name: 'Host 1',
endpoint: 'ws://192.168.1.2:6768',
deviceToken: 'token',
publicKeyB64: 'key',
lastConnected: 1,
relay: {
v: 1 as const,
directorUrl: 'https://relay-staging.onorca.dev',
cellUrl: 'https://c1.relay-staging.onorca.dev',
assignmentEpoch: 4,
relayHostId: 'AbCdEf0123_-xyZ9',
e2eeFraming: 2 as const
}
},
state: 'connecting',
verdict: {
kind: 'unreachable',
label: 'Host 1 is offline',
reason: 'never-connected',
detail: "Check it's awake, Orca is running, and you're signed in"
},
path: 'lan',
onPress: vi.fn(),
onLongPress: vi.fn(),
onOpenActions: vi.fn()
})
)
const texts = findAllByHostType(renderer.root, 'Text').map((node) => node.props.children)
expect(texts).toContainEqual("Check it's awake, Orca is running, and you're signed in")
expect(findAllByHostType(renderer.root, 'Pressable')[0]?.props.accessibilityLabel).toBe(
"Open Host 1, Host 1 is offline, Check it's awake, Orca is running, and you're signed in"
)
})
})
-231
View File
@@ -1,231 +0,0 @@
import { Monitor, MoreVertical } from 'lucide-react-native'
import { Pressable, Text, View } from 'react-native'
import type { Translate } from '../i18n/mobile-i18n'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { ConnectionVerdict } from '../transport/connection-health'
import { verdictDisplayLabel } from '../transport/connection-health'
import {
mobileConnectionPathAccessibilityLabel,
mobileConnectionPathLabel
} from '../transport/mobile-connection-path-label'
import { useHostDisplay } from '../transport/use-host-display'
import type { MobileConnectionPath } from '../transport/stable-logical-rpc-client'
import type { ConnectionState, HostCatalogEntry, HostProfile } from '../transport/types'
import { colors, radii, spacing } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { homeHostWorktreeSummary, type HostWorktreeInfo } from '../worktree/home-worktree-info'
import { StatusDot } from './StatusDot'
function hostCardWorktreeSummaries(info: HostWorktreeInfo | undefined, t: Translate) {
const now = Date.now()
const visual = homeHostWorktreeSummary(info, now)
const accessibility =
info && info.activeCount > 0 && (!info.catalogUnavailable || info.staleCounts)
? t(
'components.hostCard.worktreeAccessibilitySummary',
'{{summary}}, {{activeCount}} active',
{
summary: homeHostWorktreeSummary({ ...info, activeCount: 0 }, now),
activeCount: info.activeCount
}
)
: visual
return { visual, accessibility }
}
export function MobileHostCard(props: {
host: HostProfile | HostCatalogEntry
credentialStatus?: HostCatalogEntry['credentialStatus']
state: ConnectionState
verdict: ConnectionVerdict
path: MobileConnectionPath
// Card owns fresh/stale/unavailable wording so callers cannot hide retained counts again (STA-3123).
worktreeInfo?: HostWorktreeInfo
onPress: () => void
onLongPress: () => void
onOpenActions: () => void
}) {
const t = useMobileTranslation()
useMobileColorScheme()
const credentialUnavailable = props.credentialStatus === 'temporarily-unavailable'
const credentialMissing = props.credentialStatus === 'missing'
const connected = props.state === 'connected' && !credentialUnavailable && !credentialMissing
const isError =
credentialMissing || ['warning', 'unreachable', 'auth-failed'].includes(props.verdict.kind)
const statusLabel = credentialMissing
? t('components.hostCard.pairingInvalid', 'Pairing invalid')
: credentialUnavailable
? t('components.hostCard.pairingTemporarilyUnavailable', 'Pairing temporarily unavailable')
: verdictDisplayLabel(props.verdict)
const statusVerdict: ConnectionVerdict = credentialMissing
? { kind: 'auth-failed', label: statusLabel }
: credentialUnavailable
? { kind: 'warning', label: statusLabel }
: props.verdict
const worktreeSummary = hostCardWorktreeSummaries(props.worktreeInfo, t)
const display = useHostDisplay(props.host)
const descriptorText = display.descriptorLine
const connectionPathLabel =
!credentialMissing && !credentialUnavailable && connected
? mobileConnectionPathLabel(props.path)
: null
const discoveryHint =
props.verdict.kind === 'unreachable' && !props.host.relay
? t(
'components.hostCard.updateDesktopHint',
'Update desktop Orca and sign in to connect from anywhere'
)
: null
const credentialHint = credentialMissing
? t('components.hostCard.repairHint', 'Tap to re-pair with your desktop')
: credentialUnavailable
? t('components.hostCard.unlockHint', 'Unlock your phone, then tap to retry')
: null
// The verdict's own second line (what to check on the desktop); credential copy wins.
const verdictDetail =
credentialHint === null && 'detail' in props.verdict ? (props.verdict.detail ?? null) : null
const accessibilityLabel = [
t('components.hostCard.openHost', 'Open {{title}}', { title: display.title }),
descriptorText,
statusLabel,
connectionPathLabel ? mobileConnectionPathAccessibilityLabel(props.path) : null,
connected ? worktreeSummary.accessibility : null,
discoveryHint,
credentialHint,
verdictDetail
]
.filter(Boolean)
.join(', ')
return (
<View style={styles.card}>
<Pressable
accessibilityRole="button"
accessibilityLabel={accessibilityLabel}
style={({ pressed }) => [styles.cardMain, pressed && styles.cardPressed]}
onPress={props.onPress}
onLongPress={props.onLongPress}
delayLongPress={400}
>
<View style={styles.icon}>
<Monitor size={20} color={connected ? colors.textPrimary : colors.textSecondary} />
</View>
<View style={styles.main}>
<Text
style={[styles.name, !connected && { color: colors.textSecondary }]}
numberOfLines={1}
>
{display.title}
</Text>
{descriptorText ? (
<Text style={styles.platformText} numberOfLines={1}>
{descriptorText}
</Text>
) : null}
<View style={styles.meta}>
<StatusDot state={props.state} verdict={statusVerdict} />
<Text
style={[
styles.metaText,
isError && { color: colors.statusRed },
credentialUnavailable && { color: colors.statusAmber }
]}
numberOfLines={1}
>
{statusLabel}
{connectionPathLabel ? ` · ${connectionPathLabel}` : ''}
</Text>
</View>
{connected && worktreeSummary.visual ? (
<Text style={styles.worktreeMetaText} numberOfLines={1}>
{worktreeSummary.visual}
</Text>
) : null}
{discoveryHint ? (
<Text style={styles.discoveryHint} numberOfLines={2}>
{discoveryHint}
</Text>
) : null}
{credentialHint ? (
<Text style={styles.discoveryHint} numberOfLines={2}>
{credentialHint}
</Text>
) : null}
{verdictDetail ? (
<Text style={styles.discoveryHint} numberOfLines={2}>
{verdictDetail}
</Text>
) : null}
</View>
</Pressable>
<Pressable
accessibilityRole="button"
accessibilityLabel={t('components.hostCard.hostActions', 'Actions for {{title}}', {
title: display.title
})}
hitSlop={8}
style={({ pressed }) => [styles.actionButton, pressed && styles.actionButtonPressed]}
onPress={props.onOpenActions}
>
<MoreVertical size={18} color={colors.textSecondary} />
</Pressable>
</View>
)
}
const styles = themedStyleSheet(() => ({
card: {
flexDirection: 'row',
alignItems: 'center',
borderRadius: radii.surface,
backgroundColor: colors.bgPanel,
borderWidth: 1,
borderColor: colors.borderSubtle,
overflow: 'hidden'
},
cardMain: {
flex: 1,
minWidth: 0,
flexDirection: 'row',
alignItems: 'center',
paddingLeft: spacing.md,
paddingVertical: 12
},
cardPressed: { backgroundColor: colors.bgRaised },
icon: {
width: 46,
height: 46,
borderRadius: radii.tile,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: colors.bgRaised,
marginRight: 14
},
main: { flex: 1, minWidth: 0, marginRight: spacing.sm },
name: { color: colors.textPrimary, fontSize: 15, fontWeight: '600', lineHeight: 20 },
platformText: { color: colors.textSecondary, fontSize: 12, lineHeight: 16 },
meta: { flexDirection: 'row', alignItems: 'center', gap: 6, marginTop: 3, minWidth: 0 },
metaText: { flex: 1, fontSize: 12, color: colors.textSecondary },
worktreeMetaText: {
marginTop: 2,
fontSize: 12,
color: colors.textMuted
},
discoveryHint: {
marginTop: spacing.xs,
fontSize: 11,
lineHeight: 15,
color: colors.textMuted
},
actionButton: {
width: 40,
height: 40,
marginHorizontal: spacing.xs,
borderRadius: radii.control,
alignItems: 'center',
justifyContent: 'center'
},
actionButtonPressed: {
backgroundColor: colors.bgRaised
}
}))
+18 -25
View File
@@ -27,16 +27,12 @@ export function AppUpdateCard(props: {
style={({ pressed }) => [styles.cardMain, pressed && styles.cardPressed]}
onPress={props.onPress}
>
<View style={styles.icon}>
<ArrowUpFromLine size={20} strokeWidth={1.75} color={colors.textPrimary} />
</View>
<View style={styles.main}>
<Text style={styles.title} numberOfLines={1}>
{title}
</Text>
<Text style={styles.subtitle} numberOfLines={1}>
{t('home.appUpdateCard.updateHint', 'Tap to update')}
</Text>
<ArrowUpFromLine size={20} color={colors.accentBlue} />
<Text style={styles.title} numberOfLines={1}>
{title}
</Text>
<View style={styles.updateButton}>
<Text style={styles.updateLabel}>{t('home.appUpdateCard.update', 'Update')}</Text>
</View>
</Pressable>
<Pressable
@@ -50,7 +46,7 @@ export function AppUpdateCard(props: {
style={({ pressed }) => [styles.dismissButton, pressed && styles.dismissPressed]}
onPress={props.onDismiss}
>
<X size={16} color={colors.textSecondary} />
<X size={17} color={colors.textMuted} />
</Pressable>
</View>
)
@@ -60,10 +56,9 @@ const styles = themedStyleSheet(() => ({
card: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 26,
borderRadius: radii.surface,
backgroundColor: colors.bgPanel,
borderWidth: 1,
borderColor: colors.borderSubtle,
overflow: 'hidden'
},
cardMain: {
@@ -71,22 +66,20 @@ const styles = themedStyleSheet(() => ({
minWidth: 0,
flexDirection: 'row',
alignItems: 'center',
paddingLeft: spacing.md,
paddingVertical: 12
gap: spacing.md,
paddingLeft: spacing.lg,
paddingVertical: spacing.md
},
cardPressed: { backgroundColor: colors.bgRaised },
icon: {
width: 46,
height: 46,
borderRadius: radii.tile,
alignItems: 'center',
title: { flex: 1, minWidth: 0, color: colors.textPrimary, fontSize: 15, fontWeight: '600' },
updateButton: {
minHeight: 32,
justifyContent: 'center',
backgroundColor: colors.bgRaised,
marginRight: 14
paddingHorizontal: spacing.md,
borderRadius: radii.control,
backgroundColor: colors.bgRaised
},
main: { flex: 1, minWidth: 0, marginRight: spacing.sm },
title: { color: colors.textPrimary, fontSize: 15, fontWeight: '600', lineHeight: 20 },
subtitle: { color: colors.textSecondary, fontSize: 12, lineHeight: 16 },
updateLabel: { color: colors.accentBlue, fontSize: 15, fontWeight: '600' },
dismissButton: {
width: 40,
height: 40,
@@ -1,140 +0,0 @@
import { Pressable, Text, View } from 'react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { ClaudeIcon, OpenAIIcon } from '../components/AgentIcons'
import {
getActiveProviderRateLimits,
getUsageBarState,
hasActiveProviderUsage,
UsageBar,
type AccountsSnapshot,
type ProviderKey
} from '../components/AccountUsage'
import { colors, radii, spacing } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import type { HostProfile } from '../transport/types'
export function MobileHomeAccountUsageCards(props: {
items: { host: HostProfile; snapshot: AccountsSnapshot }[]
onOpen: (hostId: string) => void
}) {
const t = useMobileTranslation()
useMobileColorScheme()
if (props.items.length === 0) {
return null
}
return (
<>
<Text style={styles.sectionHeading}>
{t('home.accountUsageCards.sectionHeading', 'Account usage')}
</Text>
{props.items.map(({ host, snapshot }) => {
const claudeActive =
snapshot.claude.accounts.find(
(account) => account.id === snapshot.claude.activeAccountId
) ?? null
const codexActive =
snapshot.codex.accounts.find(
(account) => account.id === snapshot.codex.activeAccountId
) ?? null
return (
<Pressable
key={host.id}
style={({ pressed }) => [styles.card, pressed && styles.cardPressed]}
onPress={() => props.onOpen(host.id)}
>
{props.items.length > 1 ? (
<Text style={styles.hostLabel} numberOfLines={1}>
{host.name}
</Text>
) : null}
{(['claude', 'codex'] as ProviderKey[]).map((provider) => {
const active = provider === 'claude' ? claudeActive : codexActive
const accounts =
provider === 'claude' ? snapshot.claude.accounts : snapshot.codex.accounts
const limits = getActiveProviderRateLimits(snapshot, provider)
if (accounts.length === 0 && !hasActiveProviderUsage(limits)) {
return null
}
const sessionBar = getUsageBarState(limits, 'session')
const weeklyBar = getUsageBarState(limits, 'weekly')
return (
<View key={provider} style={styles.row}>
<View style={styles.icon}>
{provider === 'claude' ? (
<ClaudeIcon size={18} />
) : (
<OpenAIIcon size={18} color={colors.textPrimary} />
)}
</View>
<View style={styles.info}>
<Text style={styles.email} numberOfLines={1}>
{active?.email ?? t('home.accountUsageCards.systemDefault', 'System default')}
</Text>
<View style={styles.bars}>
<UsageBar
label={t('home.accountUsageCards.sessionWindow', '5h')}
usedPercent={sessionBar.usedPercent}
unavailable={sessionBar.unavailable}
loading={sessionBar.loading}
/>
<UsageBar
label={t('home.accountUsageCards.weeklyWindow', '7d')}
usedPercent={weeklyBar.usedPercent}
unavailable={weeklyBar.unavailable}
loading={weeklyBar.loading}
/>
</View>
</View>
</View>
)
})}
</Pressable>
)
})}
</>
)
}
const styles = themedStyleSheet(() => ({
sectionHeading: {
fontSize: 11,
fontWeight: '600',
color: colors.textMuted,
textTransform: 'uppercase',
letterSpacing: 0.6,
marginTop: spacing.xl,
marginBottom: spacing.sm,
paddingHorizontal: spacing.xs
},
card: {
backgroundColor: colors.bgPanel,
borderWidth: 1,
borderColor: colors.borderSubtle,
borderRadius: radii.surface,
paddingHorizontal: spacing.md,
paddingVertical: spacing.sm + 2,
gap: spacing.sm,
marginBottom: spacing.sm
},
cardPressed: { backgroundColor: colors.bgRaised },
hostLabel: {
fontSize: 11,
color: colors.textMuted,
fontWeight: '500',
textTransform: 'uppercase',
letterSpacing: 0.4
},
row: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm + 2 },
icon: {
width: 32,
height: 32,
borderRadius: radii.tile,
backgroundColor: colors.bgRaised,
alignItems: 'center',
justifyContent: 'center'
},
info: { flex: 1, minWidth: 0, gap: 2 },
email: { fontSize: 13, fontWeight: '600', color: colors.textPrimary },
bars: { flexDirection: 'row', gap: spacing.md, marginTop: 4 }
}))
+5 -11
View File
@@ -1,4 +1,3 @@
import { StyleSheet, View } from 'react-native'
import { undismissedAppUpdate } from '../app-update/app-update-checker'
import {
appUpdateChecker,
@@ -6,7 +5,6 @@ import {
useAppUpdateState
} from '../app-update/app-update-runtime'
import { AppUpdateCard } from './AppUpdateCard'
import { spacing } from '../theme/mobile-theme'
export function MobileHomeAppUpdateCard() {
const available = undismissedAppUpdate(useAppUpdateState())
@@ -14,14 +12,10 @@ export function MobileHomeAppUpdateCard() {
return null
}
return (
<View style={styles.slot}>
<AppUpdateCard
version={available.version}
onPress={() => openAppUpdate(available.url)}
onDismiss={() => appUpdateChecker.dismiss(available.version)}
/>
</View>
<AppUpdateCard
version={available.version}
onPress={() => openAppUpdate(available.url)}
onDismiss={() => appUpdateChecker.dismiss(available.version)}
/>
)
}
const styles = StyleSheet.create({ slot: { marginBottom: spacing.lg } })
+164
View File
@@ -0,0 +1,164 @@
import { ListTodo, Plus, QrCode, type LucideIcon } from 'lucide-react-native'
import type { ReactNode } from 'react'
import { Pressable, Text, View } from 'react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { PickerModal } from '../components/PickerModal'
import { useConnectedHostPicker } from '../components/use-connected-host-picker'
import { colors, radii, spacing } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import type { HostProfile } from '../transport/types'
/** Home's actions, pinned within thumb reach above the home indicator. */
export function MobileHomeBottomBar(props: {
bottomInset: number
contentMaxWidth: number
isWideLayout: boolean
connectedHosts: HostProfile[]
onOpenTasks: (hostId: string) => void
onCreateWorkspace: () => void
}) {
const t = useMobileTranslation()
useMobileColorScheme()
const tasksHostPicker = useConnectedHostPicker(props.connectedHosts)
const enabled = props.connectedHosts.length > 0
return (
<MobileHomeBottomBarFrame {...props}>
{enabled ? null : (
<Text style={styles.note}>
{t('home.bottomBar.connectFirst', 'Connect a desktop to start new work.')}
</Text>
)}
<View style={styles.buttons}>
<BarButton
icon={ListTodo}
label={t('home.bottomBar.tasks', 'Tasks')}
variant="secondary"
disabled={!enabled}
onPress={() => tasksHostPicker.pickHost(props.onOpenTasks)}
/>
<BarButton
icon={Plus}
label={t('home.bottomBar.newWorkspace', 'New workspace')}
variant="primary"
disabled={!enabled}
onPress={props.onCreateWorkspace}
/>
</View>
<PickerModal
title={t('home.listFooter.hostPickerTitle', 'Open Tasks On')}
{...tasksHostPicker.pickerProps}
/>
</MobileHomeBottomBarFrame>
)
}
/** First run's single action, in the same pinned spot. */
export function MobileHomePairBar(props: {
bottomInset: number
contentMaxWidth: number
isWideLayout: boolean
onPairDesktop: () => void
}) {
const t = useMobileTranslation()
useMobileColorScheme()
return (
<MobileHomeBottomBarFrame {...props}>
<View style={styles.buttons}>
<BarButton
icon={QrCode}
label={t('home.emptyState.pairDesktopButton', 'Pair desktop')}
variant="primary"
onPress={props.onPairDesktop}
/>
</View>
</MobileHomeBottomBarFrame>
)
}
function MobileHomeBottomBarFrame(props: {
bottomInset: number
contentMaxWidth: number
isWideLayout: boolean
children: ReactNode
}) {
useMobileColorScheme()
return (
<View style={[styles.bar, { paddingBottom: props.bottomInset + spacing.sm }]}>
<View
style={
props.isWideLayout
? { maxWidth: props.contentMaxWidth, width: '100%', alignSelf: 'center' }
: null
}
>
{props.children}
</View>
</View>
)
}
function BarButton(props: {
icon: LucideIcon
label: string
variant: 'primary' | 'secondary'
disabled?: boolean
onPress: () => void
}) {
useMobileColorScheme()
const primary = props.variant === 'primary'
const foreground = primary ? colors.bgBase : colors.textPrimary
const Icon = props.icon
return (
<Pressable
accessibilityRole="button"
accessibilityState={{ disabled: Boolean(props.disabled) }}
disabled={props.disabled}
style={({ pressed }) => [
styles.button,
primary ? styles.primaryButton : styles.secondaryButton,
pressed && styles.buttonPressed,
props.disabled && styles.buttonDisabled
]}
onPress={props.onPress}
>
<Icon size={19} color={foreground} strokeWidth={2.2} />
<Text style={[styles.buttonLabel, { color: foreground }]} numberOfLines={1}>
{props.label}
</Text>
</Pressable>
)
}
const styles = themedStyleSheet(() => ({
bar: {
backgroundColor: colors.bgBase,
borderTopWidth: 1,
borderTopColor: colors.borderSubtle,
paddingTop: 10,
paddingHorizontal: spacing.lg
},
note: {
fontSize: 13,
lineHeight: 18,
color: colors.textSecondary,
textAlign: 'center',
marginBottom: 10
},
buttons: { flexDirection: 'row', gap: 10 },
button: {
flex: 1,
minHeight: 50,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: spacing.sm,
paddingHorizontal: spacing.md,
borderRadius: radii.control
},
primaryButton: { backgroundColor: colors.surfaceBright },
secondaryButton: { backgroundColor: colors.bgRaised },
buttonPressed: { opacity: 0.72, transform: [{ scale: 0.98 }] },
buttonDisabled: { opacity: 0.4 },
buttonLabel: { fontSize: 17, fontWeight: '600' }
}))
@@ -0,0 +1,158 @@
import { createElement } from 'react'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { findAllByHostType } from '../test-support/react-test-renderer-host-nodes'
import { colors } from '../theme/mobile-theme'
import type { ConnectionVerdict } from '../transport/connection-health'
import type { ConnectionState, HostCredentialStatus, HostProfile } from '../transport/types'
import {
markHomeWorktreeCatalogUnavailable,
type HostWorktreeInfo
} from '../worktree/home-worktree-info'
import { MobileHomeDesktopRow } from './MobileHomeDesktopRow'
vi.mock('react-native', () => ({
Pressable: 'Pressable',
StyleSheet: { create: <T,>(styles: T) => styles, hairlineWidth: 0.5 },
Text: 'Text',
View: 'View'
}))
vi.mock('lucide-react-native', () => ({ ChevronRight: 'ChevronRight' }))
vi.mock('../components/StatusDot', async (importOriginal) => ({
...(await importOriginal<Record<string, unknown>>()),
StatusDot: 'StatusDot'
}))
const host: HostProfile = {
id: 'host-1',
name: 'Studio',
endpoint: 'ws://studio.local:8765',
deviceToken: 'token',
publicKeyB64: 'key',
lastConnected: 0
}
const NOW = 1_800_000_000_000
const connectedVerdict: ConnectionVerdict = { kind: 'normal', label: 'Connected' }
const loaded: HostWorktreeInfo = {
hostId: 'host-1',
totalWorktrees: 12,
activeCount: 2,
lastActiveWorktree: null,
countsProvenAt: NOW
}
describe('MobileHomeDesktopRow', () => {
let renderer: ReactTestRenderer | null = null
// The row ages counts against the clock; pin it so "proven just now" stays true.
beforeEach(() => {
vi.useFakeTimers({ toFake: ['Date'], now: NOW })
})
afterEach(() => {
act(() => renderer?.unmount())
renderer = null
vi.useRealTimers()
})
async function renderRow(
worktreeInfo: HostWorktreeInfo | undefined,
overrides?: {
state?: ConnectionState
verdict?: ConnectionVerdict
credentialStatus?: HostCredentialStatus
host?: HostProfile
onLongPress?: () => void
}
) {
await act(async () => {
renderer = create(
createElement(MobileHomeDesktopRow, {
host: overrides?.host ?? host,
state: overrides?.state ?? 'connected',
verdict: overrides?.verdict ?? connectedVerdict,
path: 'lan',
credentialStatus: overrides?.credentialStatus,
worktreeInfo,
onPress: () => {},
onLongPress: overrides?.onLongPress ?? (() => {})
})
)
})
const root = renderer!.root
const texts = findAllByHostType(root, 'Text')
return {
lines: texts.flatMap((node) => node.children.filter((child) => typeof child === 'string')),
texts,
row: findAllByHostType(root, 'Pressable')[0]
}
}
it('renders the counts the host proved', async () => {
expect((await renderRow(loaded)).lines).toContain('12 worktrees · 2 active')
})
it('keeps rendering the last proven counts after a failed refresh', async () => {
const { lines } = await renderRow(markHomeWorktreeCatalogUnavailable(loaded, 'host-1'))
expect(lines).toContain('Last known: 12 worktrees · 2 active')
})
it('never asserts a count for a catalog that failed with nothing proven', async () => {
const { lines } = await renderRow(markHomeWorktreeCatalogUnavailable(undefined, 'host-1'))
expect(lines).toContain('Worktree list unavailable')
})
it('offers re-pairing when the credential is missing, instead of counts', async () => {
const { lines } = await renderRow(loaded, {
verdict: { kind: 'auth-failed', label: 'Pairing invalid' },
credentialStatus: 'missing'
})
expect(lines).toContain('Pairing invalid')
expect(lines).toContain('Tap to re-pair with your desktop')
expect(lines).not.toContain('12 worktrees · 2 active')
})
it('offers a retry without declaring a transient read failure invalid', async () => {
const { lines } = await renderRow(undefined, {
state: 'disconnected',
verdict: { kind: 'normal', label: 'Disconnected' },
credentialStatus: 'temporarily-unavailable'
})
expect(lines).toContain('Pairing temporarily unavailable')
expect(lines).toContain('Unlock your phone, then tap to retry')
expect(lines).not.toContain('Pairing invalid')
})
it('colours a warning amber like its dot, not red', async () => {
const { texts } = await renderRow(undefined, {
state: 'reconnecting',
verdict: { kind: 'warning', label: "Can't connect", detail: 'Check Tailscale' }
})
const status = texts.find((node) => node.children.includes("Can't connect"))
expect(status?.props.style).toContainEqual({ color: colors.statusAmber })
})
it('shows and announces the fix for an offline desktop', async () => {
const { lines, row } = await renderRow(undefined, {
host: { ...host, name: 'Desk' },
state: 'reconnecting',
verdict: {
kind: 'unreachable',
label: "Can't reach desktop",
reason: 'never-connected',
detail: "Check it's awake, Orca is running, and you're signed in"
}
})
expect(lines).toContain("Check it's awake, Orca is running, and you're signed in")
expect(row.props.accessibilityLabel).toBe(
"Open Desk, Can't reach desktop, Update desktop Orca and sign in to connect from anywhere, Check it's awake, Orca is running, and you're signed in"
)
})
it('opens the desktop menu from the accessibility long-press action', async () => {
const onLongPress = vi.fn()
const { row } = await renderRow(loaded, { onLongPress })
act(() => row.props.onAccessibilityAction({ nativeEvent: { actionName: 'longpress' } }))
expect(onLongPress).toHaveBeenCalledTimes(1)
})
})
+143
View File
@@ -0,0 +1,143 @@
import { ChevronRight } from 'lucide-react-native'
import { Pressable, Text, View } from 'react-native'
import type { Translate } from '../i18n/mobile-i18n'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { StatusDot, statusDotColor } from '../components/StatusDot'
import type { ConnectionVerdict } from '../transport/connection-health'
import { verdictDisplayLabel } from '../transport/connection-health'
import { mobileConnectionPathAccessibilityLabel } from '../transport/mobile-connection-path-label'
import { useHostDisplay } from '../transport/use-host-display'
import type { MobileConnectionPath } from '../transport/stable-logical-rpc-client'
import type { ConnectionState, HostCatalogEntry, HostProfile } from '../transport/types'
import { colors } from '../theme/mobile-theme'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { homeHostWorktreeSummary, type HostWorktreeInfo } from '../worktree/home-worktree-info'
import { homeListStyles as styles } from './home-list-styles'
const PROBLEM_VERDICTS: ReadonlySet<ConnectionVerdict['kind']> = new Set([
'warning',
'unreachable',
'auth-failed'
])
function worktreeSummaries(info: HostWorktreeInfo | undefined, t: Translate) {
const now = Date.now()
const visual = homeHostWorktreeSummary(info, now)
const accessibility =
info && info.activeCount > 0 && (!info.catalogUnavailable || info.staleCounts)
? t(
'components.hostCard.worktreeAccessibilitySummary',
'{{summary}}, {{activeCount}} active',
{
summary: homeHostWorktreeSummary({ ...info, activeCount: 0 }, now),
activeCount: info.activeCount
}
)
: visual
return { visual, accessibility }
}
export function MobileHomeDesktopRow(props: {
host: HostProfile | HostCatalogEntry
credentialStatus?: HostCatalogEntry['credentialStatus']
state: ConnectionState
verdict: ConnectionVerdict
path: MobileConnectionPath
// The row owns fresh/stale/unavailable wording so callers cannot hide retained counts again (STA-3123).
worktreeInfo?: HostWorktreeInfo
onPress: () => void
onLongPress: () => void
}) {
const t = useMobileTranslation()
useMobileColorScheme()
const credentialUnavailable = props.credentialStatus === 'temporarily-unavailable'
const credentialMissing = props.credentialStatus === 'missing'
const connected = props.state === 'connected' && !credentialUnavailable && !credentialMissing
const statusLabel = credentialMissing
? t('components.hostCard.pairingInvalid', 'Pairing invalid')
: credentialUnavailable
? t('components.hostCard.pairingTemporarilyUnavailable', 'Pairing temporarily unavailable')
: verdictDisplayLabel(props.verdict)
const statusVerdict: ConnectionVerdict = credentialMissing
? { kind: 'auth-failed', label: statusLabel }
: credentialUnavailable
? { kind: 'warning', label: statusLabel }
: props.verdict
// The words take the dot's severity, so an amber warning never reads red.
const statusColor = PROBLEM_VERDICTS.has(statusVerdict.kind)
? statusDotColor(props.state, statusVerdict)
: colors.textSecondary
const worktreeSummary = worktreeSummaries(props.worktreeInfo, t)
const display = useHostDisplay(props.host)
const discoveryHint =
props.verdict.kind === 'unreachable' && !props.host.relay
? t(
'components.hostCard.updateDesktopHint',
'Update desktop Orca and sign in to connect from anywhere'
)
: null
const credentialHint = credentialMissing
? t('components.hostCard.repairHint', 'Tap to re-pair with your desktop')
: credentialUnavailable
? t('components.hostCard.unlockHint', 'Unlock your phone, then tap to retry')
: null
// The verdict's own second line (what to check on the desktop); credential copy wins.
const verdictDetail =
credentialHint === null && 'detail' in props.verdict ? (props.verdict.detail ?? null) : null
const details = [discoveryHint, credentialHint, verdictDetail].filter((line): line is string =>
Boolean(line)
)
const subtitle = connected && worktreeSummary.visual ? worktreeSummary.visual : statusLabel
const accessibilityLabel = [
t('components.hostCard.openHost', 'Open {{title}}', { title: display.title }),
display.descriptorLine,
statusLabel,
connected ? mobileConnectionPathAccessibilityLabel(props.path) : null,
connected ? worktreeSummary.accessibility : null,
...details
]
.filter(Boolean)
.join(', ')
const optionsLabel = t('components.hostCard.hostActions', 'Actions for {{title}}', {
title: display.title
})
return (
<Pressable
accessibilityRole="button"
accessibilityLabel={accessibilityLabel}
accessibilityActions={[{ name: 'longpress', label: optionsLabel }]}
onAccessibilityAction={(event) => {
if (event.nativeEvent.actionName === 'longpress') {
props.onLongPress()
}
}}
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
onPress={props.onPress}
onLongPress={props.onLongPress}
delayLongPress={400}
>
<View style={styles.status}>
<View style={styles.statusDotBox}>
<StatusDot state={props.state} verdict={statusVerdict} />
</View>
</View>
<View style={styles.text}>
<Text style={styles.title} numberOfLines={1}>
{display.title}
</Text>
<Text style={[styles.subtitle, { color: statusColor }]} numberOfLines={connected ? 1 : 2}>
{subtitle}
</Text>
{details.map((line) => (
<Text key={line} style={styles.detail} numberOfLines={3}>
{line}
</Text>
))}
</View>
<View style={styles.trailing}>
<ChevronRight size={16} color={colors.textMuted} />
</View>
</Pressable>
)
}
+50 -89
View File
@@ -1,5 +1,6 @@
import { QrCode } from 'lucide-react-native'
import { Pressable, ScrollView, Text, View } from 'react-native'
import { Fragment } from 'react'
import { ScrollView, StyleSheet, Text, View } from 'react-native'
import { OrcaLogo } from '../components/OrcaLogo'
import type { Translate } from '../i18n/mobile-i18n'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { colors, radii, spacing } from '../theme/mobile-theme'
@@ -20,8 +21,8 @@ function onboardingSteps(t: Translate) {
id: 'scan-code',
title: t('home.emptyState.scanCodeTitle', 'Scan the code'),
desc: t(
'home.emptyState.scanCodeDescription',
'Tap the button above to open the scanner. Point at the QR code on your screen.'
'home.emptyState.scanCodeBelowDescription',
'Tap Pair desktop below, then point your camera at the QR code on your screen.'
)
},
{
@@ -35,20 +36,14 @@ function onboardingSteps(t: Translate) {
]
}
export function MobileHomeEmptyState(props: {
bottomInset: number
contentMaxWidth: number
isWideLayout: boolean
onPairDesktop: () => void
}) {
export function MobileHomeEmptyState(props: { contentMaxWidth: number; isWideLayout: boolean }) {
const t = useMobileTranslation()
useMobileColorScheme()
// Scrolls because a phone in landscape is shorter than the hero and the steps together.
return (
<ScrollView
contentContainerStyle={[
styles.emptyContainer,
{ paddingBottom: props.bottomInset },
styles.container,
props.isWideLayout && {
maxWidth: props.contentMaxWidth,
width: '100%',
@@ -56,109 +51,75 @@ export function MobileHomeEmptyState(props: {
}
]}
>
<View style={styles.emptyHero}>
<Text style={styles.emptyTitle}>{t('home.emptyState.title', 'Connect your desktop')}</Text>
<Text style={styles.emptyBody}>
<View style={styles.hero}>
<OrcaLogo size={44} />
<Text style={styles.title} accessibilityRole="header">
{t('home.emptyState.title', 'Connect your desktop')}
</Text>
<Text style={styles.body}>
{t(
'home.emptyState.description',
'Pair with Orca on your computer to check on your agents, jump into any terminal, and drive work from your phone.'
)}
</Text>
<Pressable style={styles.primaryButton} onPress={props.onPairDesktop}>
<QrCode size={17} color={colors.bgBase} />
<Text style={styles.primaryButtonText}>
{t('home.emptyState.pairDesktop', 'Pair Desktop')}
</Text>
</Pressable>
</View>
<View style={styles.stepsSection}>
<Text style={styles.sectionHeading}>
{t('home.emptyState.stepsHeading', 'How it works')}
</Text>
<View style={styles.steps}>
{onboardingSteps(t).map((step, index) => (
<View key={step.id} style={[styles.stepRow, index > 0 && styles.stepRowBorder]}>
<View style={styles.stepNum}>
<Text style={styles.stepNumText}>{index + 1}</Text>
<Fragment key={step.id}>
{index > 0 ? <View style={styles.separator} /> : null}
<View style={styles.step}>
<View style={styles.stepNumber}>
<Text style={styles.stepNumberText}>{index + 1}</Text>
</View>
<View style={styles.stepText}>
<Text style={styles.stepTitle}>{step.title}</Text>
<Text style={styles.stepDescription}>{step.desc}</Text>
</View>
</View>
<View style={styles.stepText}>
<Text style={styles.stepTitle}>{step.title}</Text>
<Text style={styles.stepDesc}>{step.desc}</Text>
</View>
</View>
</Fragment>
))}
</View>
</ScrollView>
)
}
const STEP_NUMBER_SIZE = 26
const styles = themedStyleSheet(() => ({
emptyContainer: { flexGrow: 1 },
emptyHero: {
flexGrow: 1,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 32,
paddingBottom: 40
},
emptyTitle: {
fontSize: 22,
container: { flexGrow: 1, justifyContent: 'center', padding: spacing.lg },
hero: { alignItems: 'center', paddingHorizontal: spacing.sm, marginBottom: 34 },
title: {
fontSize: 28,
fontWeight: '700',
letterSpacing: -0.4,
color: colors.textPrimary,
textAlign: 'center',
marginTop: 26,
marginBottom: 10
},
emptyBody: {
fontSize: 15,
color: colors.textSecondary,
textAlign: 'center',
lineHeight: 22,
marginBottom: 32
body: { fontSize: 17, lineHeight: 24, color: colors.textSecondary, textAlign: 'center' },
steps: { backgroundColor: colors.bgPanel, borderRadius: radii.surface, overflow: 'hidden' },
separator: {
height: StyleSheet.hairlineWidth,
backgroundColor: colors.borderSubtle,
marginLeft: spacing.lg + STEP_NUMBER_SIZE + 14
},
primaryButton: {
flexDirection: 'row',
alignItems: 'center',
gap: 10,
backgroundColor: colors.textPrimary,
paddingHorizontal: 28,
paddingVertical: 14,
borderRadius: radii.control
},
primaryButtonText: { color: colors.bgBase, fontSize: 15, fontWeight: '700' },
stepsSection: { paddingHorizontal: spacing.xl },
sectionHeading: {
fontSize: 11,
fontWeight: '600',
color: colors.textMuted,
textTransform: 'uppercase',
letterSpacing: 0.6,
marginBottom: spacing.sm,
paddingHorizontal: spacing.xs
},
stepRow: {
flexDirection: 'row',
alignItems: 'flex-start',
gap: 14,
paddingVertical: spacing.lg
},
stepRowBorder: { borderTopWidth: 1, borderTopColor: colors.borderSubtle },
stepNum: {
width: 28,
height: 28,
step: { flexDirection: 'row', alignItems: 'flex-start', gap: 14, padding: spacing.lg },
stepNumber: {
width: STEP_NUMBER_SIZE,
height: STEP_NUMBER_SIZE,
borderRadius: radii.pill,
backgroundColor: colors.bgPanel,
borderWidth: 1,
borderColor: colors.borderSubtle,
backgroundColor: colors.bgRaised,
alignItems: 'center',
justifyContent: 'center',
marginTop: 1
justifyContent: 'center'
},
stepNumText: { fontSize: 12, fontWeight: '700', color: colors.textSecondary },
stepText: { flex: 1 },
stepTitle: {
stepNumberText: {
fontSize: 14,
fontWeight: '600',
color: colors.textPrimary,
marginBottom: 3
fontVariant: ['tabular-nums']
},
stepDesc: { fontSize: 12, color: colors.textMuted, lineHeight: 17 }
stepText: { flex: 1, minWidth: 0 },
stepTitle: { fontSize: 17, lineHeight: 22, color: colors.textPrimary },
stepDescription: { fontSize: 15, lineHeight: 20, color: colors.textSecondary, marginTop: 2 }
}))
+66 -60
View File
@@ -1,20 +1,25 @@
import { memo, useCallback, type ReactElement } from 'react'
import { FlatList, StyleSheet, View } from 'react-native'
import type { ListRenderItemInfo } from 'react-native'
import { MobileHostCard } from '../components/MobileHostCard'
import type { HomeStatsSummary } from '../stats/home-stats-total'
import { spacing } from '../theme/mobile-theme'
import { Plus } from 'lucide-react-native'
import { Fragment, memo, useCallback, type ReactElement } from 'react'
import { Pressable, ScrollView, Text, View } from 'react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { colors, spacing } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { classifyConnection } from '../transport/connection-health'
import { resolveHomeHostConnectionState } from '../transport/home-host-auto-connect'
import type { ConnectionState, HostCatalogEntry } from '../transport/types'
import type { HostWorktreeInfo } from '../worktree/home-worktree-info'
import type { HomeHostConnections } from './home-host-connection-projection'
import { MobileHomeListHeader } from './MobileHomeListHeader'
import { homeListStyles } from './home-list-styles'
import { MobileHomeDesktopRow } from './MobileHomeDesktopRow'
type MobileHomeHostListProps = {
autoConnectHostIds: string[]
bottomInset: number
contentMaxWidth: number
/** Sections above Desktops (Jump back in); null when there's nothing to resume. */
header: ReactElement | null
/** Rows after Desktops (the app update notice). */
footer: ReactElement
hostAttempts: Record<string, number>
hostLastConnected: Record<string, number | null>
@@ -22,48 +27,19 @@ type MobileHomeHostListProps = {
hosts: HostCatalogEntry[]
hostStates: Record<string, ConnectionState>
isWideLayout: boolean
stats: HomeStatsSummary | null
worktreeInfo: Record<string, HostWorktreeInfo>
onOpen: (host: HostCatalogEntry) => void
onLongPress: (host: HostCatalogEntry) => void
onOpenActions: (host: HostCatalogEntry) => void
onPairDesktop: () => void
}
export function MobileHomeHostList(props: MobileHomeHostListProps) {
const renderHost = useCallback(
({ item }: ListRenderItemInfo<HostCatalogEntry>) => (
<MobileHomeHostRow
item={item}
autoConnectHostIds={props.autoConnectHostIds}
hostAttempts={props.hostAttempts}
hostLastConnected={props.hostLastConnected}
hostConnections={props.hostConnections}
hostStates={props.hostStates}
worktreeInfo={props.worktreeInfo}
onOpen={props.onOpen}
onLongPress={props.onLongPress}
onOpenActions={props.onOpenActions}
/>
),
[
props.autoConnectHostIds,
props.hostAttempts,
props.hostLastConnected,
props.hostConnections,
props.hostStates,
props.onLongPress,
props.onOpen,
props.onOpenActions,
props.worktreeInfo
]
)
const t = useMobileTranslation()
useMobileColorScheme()
return (
<FlatList
data={props.hosts}
keyExtractor={(host) => host.id}
<ScrollView
contentContainerStyle={[
styles.list,
styles.content,
{ paddingBottom: spacing.xl + props.bottomInset },
props.isWideLayout && {
maxWidth: props.contentMaxWidth,
@@ -71,11 +47,49 @@ export function MobileHomeHostList(props: MobileHomeHostListProps) {
alignSelf: 'center'
}
]}
ListHeaderComponent={<MobileHomeListHeader stats={props.stats} />}
ItemSeparatorComponent={CardGap}
renderItem={renderHost}
ListFooterComponent={props.footer}
/>
>
{props.header}
<Text
style={[homeListStyles.sectionHeading, !props.header && homeListStyles.firstSectionHeading]}
accessibilityRole="header"
>
{t('home.listHeader.desktopsHeading', 'Desktops')}
</Text>
<View style={homeListStyles.group}>
{props.hosts.map((host, index) => (
<Fragment key={host.id}>
<MobileHomeHostRow
item={host}
autoConnectHostIds={props.autoConnectHostIds}
hostAttempts={props.hostAttempts}
hostLastConnected={props.hostLastConnected}
hostConnections={props.hostConnections}
hostStates={props.hostStates}
worktreeInfo={props.worktreeInfo}
onOpen={props.onOpen}
onOpenActions={props.onOpenActions}
/>
<View
style={[
homeListStyles.separator,
index === props.hosts.length - 1 && homeListStyles.fullSeparator
]}
/>
</Fragment>
))}
<Pressable
accessibilityRole="button"
style={({ pressed }) => [homeListStyles.actionRow, pressed && homeListStyles.rowPressed]}
onPress={props.onPairDesktop}
>
<Plus size={20} color={colors.accentBlue} />
<Text style={homeListStyles.actionLabel}>
{t('home.desktops.pairDesktop', 'Pair a desktop')}
</Text>
</Pressable>
</View>
{props.footer}
</ScrollView>
)
}
@@ -88,12 +102,11 @@ type MobileHomeHostRowProps = Pick<
| 'hostStates'
| 'worktreeInfo'
| 'onOpen'
| 'onLongPress'
| 'onOpenActions'
> & { item: HostCatalogEntry }
const MobileHomeHostRow = memo(function MobileHomeHostRow(props: MobileHomeHostRowProps) {
const { item, onLongPress, onOpen, onOpenActions } = props
const { item, onOpen, onOpenActions } = props
const state = resolveHomeHostConnectionState(
item.id,
props.hostStates[item.id],
@@ -111,11 +124,10 @@ const MobileHomeHostRow = memo(function MobileHomeHostRow(props: MobileHomeHostR
hostName: item.name
})
const open = useCallback(() => onOpen(item), [item, onOpen])
const longPress = useCallback(() => onLongPress(item), [item, onLongPress])
const openActions = useCallback(() => onOpenActions(item), [item, onOpenActions])
return (
<MobileHostCard
<MobileHomeDesktopRow
host={item}
credentialStatus={item.credentialStatus}
state={state}
@@ -123,17 +135,11 @@ const MobileHomeHostRow = memo(function MobileHomeHostRow(props: MobileHomeHostR
path={connection?.path ?? 'lan'}
worktreeInfo={props.worktreeInfo[item.id]}
onPress={open}
onLongPress={longPress}
onOpenActions={openActions}
onLongPress={openActions}
/>
)
})
function CardGap() {
return <View style={styles.cardGap} />
}
const styles = StyleSheet.create({
list: { paddingHorizontal: spacing.lg, paddingBottom: spacing.xl },
cardGap: { height: spacing.sm }
})
const styles = themedStyleSheet(() => ({
content: { paddingHorizontal: spacing.lg }
}))
+121
View File
@@ -0,0 +1,121 @@
import { ChevronRight } from 'lucide-react-native'
import { Fragment } from 'react'
import { Pressable, Text, View } from 'react-native'
import type { WorktreeStatus } from '../../../src/shared/worktree-status-rollup'
import { AgentSpinner, agentStatusColors } from '../components/AgentSpinner'
import { useNow } from '../hooks/use-now'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { colors } from '../theme/mobile-theme'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useHostDisplay } from '../transport/use-host-display'
import type { HostCatalogEntry } from '../transport/types'
import type { HomeJumpBackInRow } from '../worktree/home-jump-back-in'
import { agentStateLabel, formatTimeAgo, type AgentDotState } from '../worktree/agent-row-display'
import { homeListStyles as styles } from './home-list-styles'
const LABEL_STATE: Partial<Record<WorktreeStatus, AgentDotState>> = {
permission: 'waiting',
failed: 'failed',
done: 'done',
working: 'working',
monitoring: 'monitoring'
}
// Only states that ask something of the user take the dot's colour; running work stays quiet.
const TINTED_STATUSES: ReadonlySet<WorktreeStatus> = new Set(['permission', 'failed', 'done'])
export function MobileHomeJumpBackIn(props: {
rows: readonly HomeJumpBackInRow[]
hosts: readonly HostCatalogEntry[]
onOpen: (row: HomeJumpBackInRow) => void
}) {
const t = useMobileTranslation()
useMobileColorScheme()
const now = useNow()
if (props.rows.length === 0) {
return null
}
// One desktop needs no naming; the repo says more.
const nameDesktops = props.hosts.length > 1
return (
<>
<Text style={[styles.sectionHeading, styles.firstSectionHeading]} accessibilityRole="header">
{t('home.jumpBackIn.heading', 'Jump back in')}
</Text>
<View style={styles.group}>
{props.rows.map((row, index) => (
<Fragment key={`${row.hostId}:${row.worktree.worktreeId}`}>
{index > 0 ? <View style={styles.separator} /> : null}
<JumpBackInRow
row={row}
host={props.hosts.find((host) => host.id === row.hostId) ?? null}
nameDesktop={nameDesktops}
now={now}
onOpen={props.onOpen}
/>
</Fragment>
))}
</View>
</>
)
}
function JumpBackInRow(props: {
row: HomeJumpBackInRow
host: HostCatalogEntry | null
nameDesktop: boolean
now: number
onOpen: (row: HomeJumpBackInRow) => void
}) {
const t = useMobileTranslation()
useMobileColorScheme()
const { row } = props
const desktopName = useHostDisplay(props.host).title
const name = row.worktree.displayName || row.worktree.repo
const labelState = LABEL_STATE[row.worktree.dotStatus]
const status = row.lastOpened
? row.actionable
? t('home.jumpBackIn.lastOpened', 'Last opened')
: t('home.jumpBackIn.connectToOpen', 'Connect {{desktop}} to open', { desktop: desktopName })
: labelState
? agentStateLabel(labelState)
: ''
const where = props.nameDesktop ? desktopName : row.worktree.repo
const showWhere = Boolean(where) && (row.actionable || !row.lastOpened)
const statusColor =
!row.lastOpened && TINTED_STATUSES.has(row.worktree.dotStatus)
? agentStatusColors()[row.worktree.dotStatus]
: colors.textSecondary
const time =
typeof row.worktree.lastOutputAt === 'number'
? formatTimeAgo(row.worktree.lastOutputAt, props.now)
: null
return (
<Pressable
accessibilityRole="button"
accessibilityLabel={[name, status, showWhere ? where : null, time].filter(Boolean).join(', ')}
accessibilityState={{ disabled: !row.actionable }}
disabled={!row.actionable}
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
onPress={() => props.onOpen(row)}
>
<View style={[styles.status, !row.actionable && styles.rowDisabled]}>
<AgentSpinner status={row.worktree.dotStatus} workingMode={row.worktree.workingMode} />
</View>
<View style={[styles.text, !row.actionable && styles.rowDisabled]}>
<Text style={styles.title} numberOfLines={1}>
{name}
</Text>
<Text style={styles.subtitle} numberOfLines={1}>
<Text style={{ color: statusColor }}>{status}</Text>
{showWhere ? ` · ${where}` : ''}
</Text>
</View>
<View style={styles.trailing}>
{time ? <Text style={styles.time}>{time}</Text> : null}
<ChevronRight size={16} color={colors.textMuted} />
</View>
</Pressable>
)
}
@@ -1,188 +0,0 @@
import { createElement } from 'react'
import { act, create, type ReactTestInstance, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { TaskProvider } from '../../../src/shared/task-providers'
import type { HostProfile } from '../transport/types'
import { MobileHomeListFooter } from './MobileHomeListFooter'
vi.mock('react-native', () => ({
Pressable: 'Pressable',
StyleSheet: { create: <T,>(styles: T) => styles },
Text: 'Text',
View: 'View'
}))
vi.mock('lucide-react-native', () => ({
ChevronRight: 'ChevronRight',
ListTodo: 'ListTodo',
Plus: 'Plus',
QrCode: 'QrCode'
}))
vi.mock('../components/TaskProviderLogo', () => ({ TaskProviderLogo: 'TaskProviderLogo' }))
vi.mock('./MobileHomeResumeCard', () => ({ MobileHomeResumeCard: 'MobileHomeResumeCard' }))
vi.mock('./MobileHomeAccountUsageCards', () => ({
MobileHomeAccountUsageCards: 'MobileHomeAccountUsageCards'
}))
vi.mock('../components/PickerModal', () => ({ PickerModal: 'PickerModal' }))
function host(id: string, name: string, endpoint: string): HostProfile {
return {
id,
name,
endpoint,
deviceToken: `token-${id}`,
publicKeyB64: `key-${id}`,
lastConnected: 1
}
}
const desk = host('desk', 'Desk', 'ws://192.168.1.2:6768')
const laptop = host('laptop', 'Laptop', 'wss://relay.example.com/mobile')
const server = host('server', 'Server', 'wss://ssh.example.com/mobile')
describe('MobileHomeListFooter tasks', () => {
let renderer: ReactTestRenderer | null = null
afterEach(() => {
act(() => renderer?.unmount())
renderer = null
vi.restoreAllMocks()
})
async function renderFooter(
connectedHosts: HostProfile[],
taskProvidersByHost: Record<string, TaskProvider[]> = {}
) {
const onOpenTasks = vi.fn()
const onCreateWorkspace = vi.fn()
const consoleError = vi.spyOn(console, 'error').mockImplementation((...args) => {
if (typeof args[0] !== 'string' || !args[0].includes('react-test-renderer is deprecated')) {
throw new Error(String(args[0]))
}
})
await act(async () => {
renderer = create(
createElement(MobileHomeListFooter, {
accountsHosts: [],
connectedHosts,
resumeCard: null,
taskProvidersByHost,
onCreateWorkspace,
onOpenAccounts: vi.fn(),
onOpenResume: vi.fn(),
onOpenTasks,
onPairDesktop: vi.fn()
})
)
})
consoleError.mockRestore()
return { onOpenTasks, onCreateWorkspace }
}
function pressable(matches: (node: ReactTestInstance) => boolean) {
const node = renderer!.root
.findAll((candidate) => String(candidate.type) === 'Pressable')
.find(matches)
if (!node) {
throw new Error('Pressable not rendered')
}
return node
}
function pressableWithText(text: string) {
return pressable((node) =>
node
.findAll((candidate) => String(candidate.type) === 'Text')
.some((candidate) => candidate.props.children === text)
)
}
function providerChip(label: string) {
return pressable((node) => node.props.accessibilityLabel === `Open ${label} tasks`)
}
function picker(title: string) {
return renderer!.root
.findAll((node) => String(node.type) === 'PickerModal')
.find((node) => node.props.title === title)!
}
function tasksPicker() {
return picker('Open Tasks On')
}
it('opens the only connected host directly', async () => {
const callbacks = await renderFooter([desk])
act(() => pressableWithText('Tasks').props.onPress())
expect(callbacks.onOpenTasks).toHaveBeenCalledWith('desk', undefined)
expect(tasksPicker().props.visible).toBe(false)
})
it('asks which connected host to open from the card', async () => {
const callbacks = await renderFooter([desk, laptop])
act(() => pressableWithText('Tasks').props.onPress())
expect(callbacks.onOpenTasks).not.toHaveBeenCalled()
expect(tasksPicker().props.visible).toBe(true)
expect(tasksPicker().props.options).toEqual([
{ value: 'desk', label: 'Desk', subtitle: '192.168.1.2:6768' },
{ value: 'laptop', label: 'Laptop', subtitle: 'relay.example.com' }
])
act(() => tasksPicker().props.onSelect('laptop'))
act(() => tasksPicker().props.onAfterClose())
expect(callbacks.onOpenTasks).toHaveBeenCalledWith('laptop', undefined)
})
it('lists each provider once, in the order connected hosts report them', async () => {
await renderFooter([desk, laptop, server], {
desk: ['github', 'jira'],
laptop: ['gitlab', 'github'],
server: ['jira', 'linear']
})
const labels = renderer!.root
.findAll((node) => String(node.type) === 'TaskProviderLogo')
.map((node) => node.props.provider)
expect(labels).toEqual(['github', 'jira', 'gitlab', 'linear'])
})
it('opens a provider directly on the only host that has it', async () => {
const callbacks = await renderFooter([desk, laptop], {
desk: ['github'],
laptop: ['github', 'jira']
})
act(() => providerChip('Jira').props.onPress({ stopPropagation: () => {} }))
expect(tasksPicker().props.visible).toBe(false)
expect(callbacks.onOpenTasks).toHaveBeenCalledWith('laptop', 'jira')
})
it('offers only the hosts that have the tapped provider', async () => {
const callbacks = await renderFooter([desk, laptop, server], {
desk: ['github', 'jira'],
laptop: ['github'],
server: ['jira']
})
act(() => providerChip('Jira').props.onPress({ stopPropagation: () => {} }))
expect(tasksPicker().props.visible).toBe(true)
expect(tasksPicker().props.options.map((option: { value: string }) => option.value)).toEqual([
'desk',
'server'
])
act(() => tasksPicker().props.onSelect('server'))
act(() => tasksPicker().props.onAfterClose())
expect(callbacks.onOpenTasks).toHaveBeenCalledWith('server', 'jira')
})
})
-88
View File
@@ -1,88 +0,0 @@
import { Text, View } from 'react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { AccountsSnapshot } from '../components/AccountUsage'
import { MobileHomeQuickActions } from '../components/MobileHomeQuickActions'
import { PickerModal } from '../components/PickerModal'
import { useConnectedHostPicker } from '../components/use-connected-host-picker'
import type { TaskProvider } from '../../../src/shared/task-providers'
import { colors, spacing } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import type { HostProfile } from '../transport/types'
import type { HomeResumeCard } from '../worktree/home-resume-card'
import { MobileHomeAccountUsageCards } from './MobileHomeAccountUsageCards'
import { MobileHomeResumeCard } from './MobileHomeResumeCard'
import { MobileHomeTasksCard } from './MobileHomeTasksCard'
const DEFAULT_TASK_PROVIDERS: TaskProvider[] = ['github']
export function MobileHomeListFooter(props: {
accountsHosts: { host: HostProfile; snapshot: AccountsSnapshot }[]
connectedHosts: HostProfile[]
resumeCard: HomeResumeCard | null
taskProvidersByHost: Record<string, TaskProvider[]>
onCreateWorkspace: () => void
onOpenAccounts: (hostId: string) => void
onOpenResume: (card: HomeResumeCard) => void
onOpenTasks: (hostId: string, provider?: TaskProvider) => void
onPairDesktop: () => void
}) {
const t = useMobileTranslation()
useMobileColorScheme()
const tasksHostPicker = useConnectedHostPicker(props.connectedHosts)
const hostProviders = props.connectedHosts.map((host) => ({
host,
providers: props.taskProvidersByHost[host.id] ?? DEFAULT_TASK_PROVIDERS
}))
const providers = [...new Set(hostProviders.flatMap((entry) => entry.providers))]
function openTasks(provider?: TaskProvider) {
// Why: a provider chip offers only desktops that have it, so it never opens a different one.
const hosts = provider
? hostProviders
.filter((entry) => entry.providers.includes(provider))
.map((entry) => entry.host)
: props.connectedHosts
tasksHostPicker.pickHost((hostId) => props.onOpenTasks(hostId, provider), hosts)
}
return (
<View>
{props.resumeCard ? (
<>
<Text style={styles.sectionHeading}>{t('home.listFooter.resumeHeading', 'Resume')}</Text>
<MobileHomeResumeCard card={props.resumeCard} onOpen={props.onOpenResume} />
</>
) : null}
<Text style={styles.sectionHeading}>{t('home.listFooter.tasksHeading', 'Tasks')}</Text>
<MobileHomeTasksCard
enabled={props.connectedHosts.length > 0}
providers={providers}
onOpen={openTasks}
/>
<PickerModal
title={t('home.listFooter.hostPickerTitle', 'Open Tasks On')}
{...tasksHostPicker.pickerProps}
/>
<MobileHomeQuickActions
connectedHosts={props.connectedHosts}
onPairDesktop={props.onPairDesktop}
onCreateWorkspace={props.onCreateWorkspace}
/>
<MobileHomeAccountUsageCards items={props.accountsHosts} onOpen={props.onOpenAccounts} />
</View>
)
}
const styles = themedStyleSheet(() => ({
sectionHeading: {
fontSize: 11,
fontWeight: '600',
color: colors.textMuted,
textTransform: 'uppercase',
letterSpacing: 0.6,
marginTop: spacing.lg,
marginBottom: spacing.sm,
paddingHorizontal: spacing.xs
}
}))
-95
View File
@@ -1,95 +0,0 @@
import { Text, View } from 'react-native'
import type { Translate } from '../i18n/mobile-i18n'
import { getUiIntlLocale } from '../i18n/ui-intl-locale'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { HomeStatsSummary } from '../stats/home-stats-total'
import { colors, radii, spacing } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { MobileHomeAppUpdateCard } from './MobileHomeAppUpdateCard'
function formatDuration(ms: number, t: Translate): string {
const totalMinutes = Math.floor(ms / 60_000)
const totalHours = Math.floor(totalMinutes / 60)
const days = Math.floor(totalHours / 24)
const hours = totalHours % 24
if (days > 0) {
return t('home.listHeader.daysHours', '{{days}}d {{hours}}h', { days, hours })
}
const minutes = totalMinutes % 60
return totalHours > 0
? t('home.listHeader.hoursMinutes', '{{hours}}h {{minutes}}m', { hours: totalHours, minutes })
: t('home.listHeader.minutes', '{{minutes}}m', { minutes: totalMinutes })
}
export function MobileHomeListHeader({ stats }: { stats: HomeStatsSummary | null }) {
const t = useMobileTranslation()
useMobileColorScheme()
return (
<View>
<View style={styles.hero}>
<Text style={styles.heroTitle}>{t('home.listHeader.title', 'Welcome back')}</Text>
</View>
{stats ? (
<View style={styles.statsRow}>
<View style={styles.statCard}>
<Text style={styles.statValue}>
{stats.totalAgentsSpawned.toLocaleString(getUiIntlLocale())}
</Text>
<Text style={styles.statLabel}>
{t('home.listHeader.agentsSpawned', 'Agents spawned')}
</Text>
</View>
<View style={styles.statCard}>
<Text style={styles.statValue}>{formatDuration(stats.totalAgentTimeMs, t)}</Text>
<Text style={styles.statLabel}>{t('home.listHeader.agentTime', 'Agent time')}</Text>
</View>
<View style={styles.statCard}>
<Text style={styles.statValue}>
{stats.totalPRsCreated.toLocaleString(getUiIntlLocale())}
</Text>
<Text style={styles.statLabel}>{t('home.listHeader.prsCreated', 'PRs created')}</Text>
</View>
</View>
) : null}
<MobileHomeAppUpdateCard />
<Text style={styles.sectionHeading}>{t('home.listHeader.desktopsHeading', 'Desktops')}</Text>
</View>
)
}
const styles = themedStyleSheet(() => ({
hero: { paddingTop: spacing.xs, paddingBottom: spacing.md },
heroTitle: {
color: colors.textPrimary,
fontSize: 24,
fontWeight: '800',
letterSpacing: -0.3
},
statsRow: { flexDirection: 'row', gap: 10, marginBottom: spacing.lg },
statCard: {
flex: 1,
backgroundColor: colors.bgPanel,
borderWidth: 1,
borderColor: colors.borderSubtle,
borderRadius: radii.surface,
paddingVertical: 8,
paddingHorizontal: spacing.md
},
statValue: {
color: colors.textPrimary,
fontSize: 18,
fontWeight: '700',
letterSpacing: -0.3
},
statLabel: { color: colors.textMuted, fontSize: 11, fontWeight: '500', marginTop: 2 },
sectionHeading: {
fontSize: 11,
fontWeight: '600',
color: colors.textMuted,
textTransform: 'uppercase',
letterSpacing: 0.6,
marginBottom: spacing.sm,
paddingHorizontal: spacing.xs
}
}))
-87
View File
@@ -1,87 +0,0 @@
import { ChevronRight, Terminal } from 'lucide-react-native'
import { Pressable, Text, View } from 'react-native'
import { colors, radii, spacing } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import type { HomeResumeCard } from '../worktree/home-resume-card'
const REPO_COLORS = ['#8b5cf6', '#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#ec4899', '#06b6d4']
function homeResumeRepoColor(name: string): string {
let hash = 0
for (let index = 0; index < name.length; index += 1) {
hash = (hash * 31 + name.charCodeAt(index)) | 0
}
return REPO_COLORS[Math.abs(hash) % REPO_COLORS.length]
}
export function MobileHomeResumeCard(props: {
card: HomeResumeCard
onOpen: (card: HomeResumeCard) => void
}) {
useMobileColorScheme()
return (
<Pressable
disabled={!props.card.actionable}
style={({ pressed }) => [
styles.resumeCard,
!props.card.actionable && styles.cardDisabled,
pressed && styles.cardPressed
]}
onPress={() => props.onOpen(props.card)}
>
<View style={styles.resumeIcon}>
<Terminal size={18} color={colors.textSecondary} />
</View>
<View style={styles.resumeMain}>
<Text style={styles.resumeTitle} numberOfLines={1}>
{props.card.worktree.displayName}
</Text>
<View style={styles.resumeSub}>
<View
style={[
styles.repoDot,
{ backgroundColor: homeResumeRepoColor(props.card.worktree.repo) }
]}
/>
<Text style={styles.resumeSubText} numberOfLines={1}>
{props.card.worktree.repo}
{' · '}
{props.card.worktree.branch}
</Text>
</View>
</View>
<ChevronRight size={16} color={colors.textMuted} />
</Pressable>
)
}
const styles = themedStyleSheet(() => ({
resumeCard: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.bgPanel,
borderWidth: 1,
borderColor: colors.borderSubtle,
borderRadius: radii.surface,
paddingLeft: spacing.md,
paddingRight: spacing.md,
paddingVertical: 12
},
cardDisabled: { opacity: 0.45 },
cardPressed: { backgroundColor: colors.bgRaised },
resumeIcon: {
width: 46,
height: 46,
borderRadius: radii.tile,
backgroundColor: colors.bgRaised,
alignItems: 'center',
justifyContent: 'center',
marginRight: 14
},
resumeMain: { flex: 1, minWidth: 0 },
resumeTitle: { fontSize: 13, fontWeight: '600', color: colors.textPrimary },
resumeSub: { flexDirection: 'row', alignItems: 'center', gap: 6, marginTop: 3 },
repoDot: { width: 7, height: 7, borderRadius: radii.pill },
resumeSubText: { fontSize: 12, color: colors.textSecondary, flex: 1 }
}))
+89 -64
View File
@@ -1,8 +1,7 @@
import { useCallback, useState } from 'react'
import { Alert } from 'react-native'
import { Alert, View } from 'react-native'
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { useOpenMobileAccounts } from '../accounts/use-open-mobile-accounts'
import { getProvenCachedWorktrees } from '../cache/worktree-cache'
import { ActionSheetModal } from '../components/ActionSheetModal'
import { ConfirmModal } from '../components/ConfirmModal'
@@ -26,11 +25,14 @@ import { removeHostAndCloseClient } from '../transport/host-removal-lifecycle'
import { loadHostCatalog } from '../transport/host-store'
import type { HostCatalogEntry, HostProfile } from '../transport/types'
import { useOpenMobileHostEdit } from '../transport/use-open-mobile-host-edit'
import type { HomeWorktreeSummary } from '../worktree/home-worktree-info'
import { isResumeTargetConfirmedMissing, type HomeResumeCard } from '../worktree/home-resume-card'
import { mobileConnectionPathLabel } from '../transport/mobile-connection-path-label'
import type { HomeJumpBackInRow } from '../worktree/home-jump-back-in'
import { isResumeTargetConfirmedMissing } from '../worktree/home-resume-card'
import { MobileHomeAppUpdateCard } from './MobileHomeAppUpdateCard'
import { MobileHomeBottomBar, MobileHomePairBar } from './MobileHomeBottomBar'
import { MobileHomeEmptyState } from './MobileHomeEmptyState'
import { MobileHomeHostList } from './MobileHomeHostList'
import { MobileHomeListFooter } from './MobileHomeListFooter'
import { MobileHomeJumpBackIn } from './MobileHomeJumpBackIn'
import { MobileHomeTopBar } from './MobileHomeTopBar'
import { useMobileHomeData } from './use-mobile-home-data'
import { newWorkspaceOverlayHref } from '../new-workspace/new-workspace-overlay-route'
@@ -44,28 +46,22 @@ export function MobileHomeScreen() {
const openMobileHostEdit = useOpenMobileHostEdit()
const openMobileTasks = useOpenMobileTasks()
const openMobileSession = useOpenMobileSession()
const openMobileAccounts = useOpenMobileAccounts()
const disconnectHostClient = useDisconnectHostClient()
const forgetHostClient = useForgetHostClient()
const forceReconnectHost = useForceReconnect()
const [actionTarget, setActionTarget] = useState<HostProfile | null>(null)
const [confirmRemove, setConfirmRemove] = useState<{ id: string; name: string } | null>(null)
const openResume = useCallback(
(card: HomeResumeCard) => {
if (
isResumeTargetConfirmedMissing(
card,
getProvenCachedWorktrees(card.hostId) as HomeWorktreeSummary[] | null
)
) {
data.router.push(hostRouteWithNotice(card.hostId, 'worktree-missing'))
const openWorktree = useCallback(
(row: HomeJumpBackInRow) => {
if (isResumeTargetConfirmedMissing(row, getProvenCachedWorktrees(row.hostId))) {
data.router.push(hostRouteWithNotice(row.hostId, 'worktree-missing'))
return
}
openMobileSession({
hostId: card.hostId,
worktreeId: card.worktree.worktreeId,
name: card.worktree.displayName || card.worktree.repo
hostId: row.hostId,
worktreeId: row.worktree.worktreeId,
name: row.worktree.displayName || row.worktree.repo
})
},
[data.router, openMobileSession]
@@ -114,58 +110,86 @@ export function MobileHomeScreen() {
}
}
const pairDesktop = () => data.router.push('/pair-scan')
const actionTargetPath = actionTarget ? data.hostConnections[actionTarget.id]?.path : undefined
const actionTargetConnected =
actionTarget != null && data.hostStates[actionTarget.id] === 'connected'
return (
<SafeAreaView style={styles.container} edges={['top']}>
<MobileHomeTopBar onOpenSettings={() => data.router.push('/settings')} />
{data.hostCatalog.length === 0 ? (
<MobileHomeEmptyState
bottomInset={insets.bottom}
contentMaxWidth={contentMaxWidth}
isWideLayout={isWideLayout}
onPairDesktop={() => data.router.push('/pair-scan')}
/>
<MobileHomeTopBar
showBrand={!data.hostCatalogLoaded || data.hostCatalog.length > 0}
onOpenSettings={() => data.router.push('/settings')}
/>
{!data.hostCatalogLoaded ? (
<View style={styles.fill} />
) : data.hostCatalog.length === 0 ? (
<>
<MobileHomeEmptyState contentMaxWidth={contentMaxWidth} isWideLayout={isWideLayout} />
<MobileHomePairBar
bottomInset={insets.bottom}
contentMaxWidth={contentMaxWidth}
isWideLayout={isWideLayout}
onPairDesktop={pairDesktop}
/>
</>
) : (
<MobileHomeHostList
autoConnectHostIds={data.autoConnectHostIds}
bottomInset={insets.bottom}
contentMaxWidth={contentMaxWidth}
footer={
<MobileHomeListFooter
accountsHosts={data.accountsHosts}
connectedHosts={data.connectedHosts}
resumeCard={data.resumeCard}
taskProvidersByHost={data.taskProvidersByHost}
onCreateWorkspace={() =>
data.router.push(
newWorkspaceOverlayHref(data.connectedHosts.map((host) => host.id))
)
}
onOpenAccounts={openMobileAccounts}
onOpenResume={openResume}
onOpenTasks={openMobileTasks}
onPairDesktop={() => data.router.push('/pair-scan')}
/>
}
hostAttempts={data.hostAttempts}
hostLastConnected={data.hostLastConnected}
hostConnections={data.hostConnections}
hosts={data.sortedHostCatalog}
hostStates={data.hostStates}
isWideLayout={isWideLayout}
stats={data.stats}
worktreeInfo={data.worktreeInfo}
onOpen={openHost}
onLongPress={(host) => {
triggerMediumImpact()
openHostActions(host)
}}
onOpenActions={openHostActions}
/>
<>
<MobileHomeHostList
autoConnectHostIds={data.autoConnectHostIds}
bottomInset={0}
contentMaxWidth={contentMaxWidth}
header={
data.jumpBackInRows.length > 0 ? (
<MobileHomeJumpBackIn
rows={data.jumpBackInRows}
hosts={data.sortedHostCatalog}
onOpen={openWorktree}
/>
) : null
}
footer={<MobileHomeAppUpdateCard />}
hostAttempts={data.hostAttempts}
hostLastConnected={data.hostLastConnected}
hostConnections={data.hostConnections}
hosts={data.sortedHostCatalog}
hostStates={data.hostStates}
isWideLayout={isWideLayout}
worktreeInfo={data.worktreeInfo}
onOpen={openHost}
onOpenActions={(host) => {
triggerMediumImpact()
openHostActions(host)
}}
onPairDesktop={pairDesktop}
/>
<MobileHomeBottomBar
bottomInset={insets.bottom}
contentMaxWidth={contentMaxWidth}
isWideLayout={isWideLayout}
connectedHosts={data.connectedHosts}
onOpenTasks={(hostId) => openMobileTasks(hostId)}
onCreateWorkspace={() =>
data.router.push(newWorkspaceOverlayHref(data.connectedHosts.map((host) => host.id)))
}
/>
</>
)}
<ActionSheetModal
visible={actionTarget != null}
title={actionTarget?.name}
message={actionTarget ? hostEndpointLabel(actionTarget.endpoint) : undefined}
message={
actionTarget
? [
actionTargetConnected && actionTargetPath
? mobileConnectionPathLabel(actionTargetPath)
: null,
hostEndpointLabel(actionTarget.endpoint)
]
.filter(Boolean)
.join(' · ')
: undefined
}
actions={getHostListActionSheetActions({
host: actionTarget,
state: actionTarget
@@ -204,5 +228,6 @@ export function MobileHomeScreen() {
}
const styles = themedStyleSheet(() => ({
container: { flex: 1, backgroundColor: colors.bgBase }
container: { flex: 1, backgroundColor: colors.bgBase },
fill: { flex: 1 }
}))
-126
View File
@@ -1,126 +0,0 @@
import { ChevronRight, ListTodo } from 'lucide-react-native'
import { Pressable, Text, View } from 'react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { TaskProviderLogo } from '../components/TaskProviderLogo'
import type { TaskProvider } from '../../../src/shared/task-providers'
import { colors, radii, spacing } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
const TASK_PROVIDER_LABELS: Record<TaskProvider, string> = {
github: 'GitHub',
gitlab: 'GitLab',
linear: 'Linear',
jira: 'Jira'
}
export function MobileHomeTasksCard(props: {
enabled: boolean
providers: TaskProvider[]
onOpen: (provider?: TaskProvider) => void
}) {
const t = useMobileTranslation()
useMobileColorScheme()
return (
<Pressable
disabled={!props.enabled}
style={({ pressed }) => [
styles.card,
!props.enabled && styles.cardDisabled,
pressed && styles.cardPressed
]}
onPress={() => props.onOpen()}
>
<View style={styles.icon}>
<ListTodo size={18} color={colors.textSecondary} />
</View>
<View style={styles.main}>
<Text style={styles.title}>{t('home.tasksCard.title', 'Tasks')}</Text>
<Text style={styles.subtitle} numberOfLines={1}>
{props.providers.length > 0
? props.providers.map((provider) => TASK_PROVIDER_LABELS[provider]).join(' · ')
: t('home.tasksCard.noSources', 'No task sources connected')}
</Text>
</View>
<View style={styles.trailing}>
<View
style={styles.providerRow}
accessibilityLabel={props.providers
.map((provider) => TASK_PROVIDER_LABELS[provider])
.join(', ')}
>
{props.providers.map((provider) => (
<Pressable
key={provider}
accessibilityRole="button"
accessibilityLabel={t('home.tasksCard.openProviderTasks', 'Open {{provider}} tasks', {
provider: TASK_PROVIDER_LABELS[provider]
})}
hitSlop={8}
style={({ pressed }) => [
styles.providerButton,
pressed && styles.providerButtonPressed
]}
onPress={(event) => {
event.stopPropagation()
props.onOpen(provider)
}}
>
<TaskProviderLogo provider={provider} size={22} color={colors.textSecondary} />
</Pressable>
))}
</View>
</View>
<ChevronRight size={16} color={colors.textMuted} />
</Pressable>
)
}
const styles = themedStyleSheet(() => ({
card: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.bgPanel,
borderWidth: 1,
borderColor: colors.borderSubtle,
borderRadius: radii.surface,
minHeight: 72,
paddingLeft: spacing.md,
paddingRight: spacing.md,
paddingVertical: 12
},
cardDisabled: { opacity: 0.45 },
cardPressed: { backgroundColor: colors.bgRaised },
icon: {
width: 46,
height: 46,
borderRadius: radii.tile,
backgroundColor: colors.bgRaised,
alignItems: 'center',
justifyContent: 'center',
marginRight: 14
},
main: { flex: 1, minWidth: 0 },
title: { fontSize: 13, fontWeight: '600', color: colors.textPrimary },
subtitle: { fontSize: 12, color: colors.textSecondary, marginTop: 3 },
trailing: {
flexDirection: 'row',
alignItems: 'center',
flexShrink: 0,
marginLeft: spacing.sm
},
providerRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-end',
gap: 2
},
providerButton: {
width: 34,
height: 34,
alignItems: 'center',
justifyContent: 'center',
borderRadius: radii.control
},
providerButtonPressed: { backgroundColor: colors.bgRaised }
}))
+25 -14
View File
@@ -6,25 +6,35 @@ import { colors, radii, spacing } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
export function MobileHomeTopBar({ onOpenSettings }: { onOpenSettings: () => void }) {
export function MobileHomeTopBar({
onOpenSettings,
showBrand = true
}: {
onOpenSettings: () => void
/** First run draws the logo large below, so the bar keeps only Settings. */
showBrand?: boolean
}) {
const t = useMobileTranslation()
useMobileColorScheme()
return (
<View style={styles.topBar}>
<View style={styles.brandLockup}>
<View style={styles.logoMark}>
<OrcaLogo size={18} />
{showBrand ? (
<View style={styles.brandLockup}>
<View style={styles.logoMark}>
<OrcaLogo size={19} />
</View>
<Text style={styles.brandName}>Orca</Text>
</View>
<Text style={styles.brandName}>Orca</Text>
</View>
) : (
<View />
)}
<Pressable
style={({ pressed }) => [styles.iconButton, pressed && styles.iconButtonPressed]}
onPress={onOpenSettings}
hitSlop={8}
accessibilityRole="button"
accessibilityLabel={t('common.settings', 'Settings')}
>
<Settings size={18} color={colors.textSecondary} />
<Settings size={22} color={colors.textSecondary} />
</Pressable>
</View>
)
@@ -35,16 +45,17 @@ const styles = themedStyleSheet(() => ({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: spacing.lg,
paddingTop: spacing.sm,
paddingBottom: spacing.md
paddingLeft: spacing.lg + spacing.xs,
paddingRight: spacing.sm,
paddingTop: spacing.xs,
paddingBottom: spacing.xs
},
brandLockup: { flexDirection: 'row', alignItems: 'center', minWidth: 0 },
logoMark: { marginRight: spacing.sm },
brandName: { color: colors.textPrimary, fontSize: 17, fontWeight: '700' },
brandName: { color: colors.textPrimary, fontSize: 20, fontWeight: '700', letterSpacing: -0.4 },
iconButton: {
width: 36,
height: 36,
width: 44,
height: 44,
borderRadius: radii.control,
alignItems: 'center',
justifyContent: 'center'
@@ -1,81 +0,0 @@
import { describe, expect, it } from 'vitest'
import type { z } from 'zod'
import { totalHomeStats } from '../stats/home-stats-total'
import { homeHostAccountsSchema, homeHostStatsSchema } from './home-host-reply-schema'
function reads<T>(schema: z.ZodType<T, unknown>, value: unknown): T {
const parsed = schema.safeParse(value)
if (!parsed.success) {
throw new Error(`expected a readable reply: ${parsed.error.message}`)
}
return parsed.data
}
function readsRow(value: unknown): NonNullable<z.output<typeof homeHostStatsSchema>> {
const row = reads(homeHostStatsSchema, value)
if (!row) {
throw new Error('expected an object row')
}
return row
}
function refuses(schema: z.ZodType<unknown, unknown>, value: unknown): boolean {
return !schema.safeParse(value).success
}
describe('a stats row is checked as an object and nothing more', () => {
it('reads the row a current host sends', () => {
const row = {
totalAgentsSpawned: 3,
totalPRsCreated: 1,
totalAgentTimeMs: 90,
firstEventAt: 1700000000000
}
expect(readsRow(row)).toMatchObject(row)
})
it('reads a host that answers a shape totalHomeStats still sums', () => {
expect(readsRow({ totalWorktrees: 3 })).toMatchObject({ totalWorktrees: 3 })
expect(readsRow({}).totalAgentsSpawned).toBe(undefined)
})
it('preserves an explicit null firstEventAt, which the total reads as no events yet', () => {
expect(readsRow({ firstEventAt: null }).firstEventAt).toBe(null)
expect(readsRow({ firstEventAt: 'never' }).firstEventAt).toBe(undefined)
})
// Unreadable summaries must still occupy the host slot: totals skip them and render zero; refusal would omit the slot and remove the header row.
it('seats a non-object summary in the slot rather than refusing the host out of the total', () => {
for (const value of ['garbage', 7, true, []]) {
expect(refuses(homeHostStatsSchema, value)).toBe(false)
expect(reads(homeHostStatsSchema, value)).toBe(undefined)
}
expect(totalHomeStats({ 'host-1': reads(homeHostStatsSchema, 'garbage') }, ['host-1'])).toEqual(
{
totalAgentsSpawned: 0,
totalPRsCreated: 0,
totalAgentTimeMs: 0,
firstEventAt: null
}
)
})
it('seats a null or absent summary in the card slot, which the total skips', () => {
expect(refuses(homeHostStatsSchema, null)).toBe(false)
expect(refuses(homeHostStatsSchema, undefined)).toBe(false)
expect(reads(homeHostStatsSchema, null)).toBe(null)
expect(totalHomeStats({ 'host-1': reads(homeHostStatsSchema, null) }, ['host-1'])).toEqual({
totalAgentsSpawned: 0,
totalPRsCreated: 0,
totalAgentTimeMs: 0,
firstEventAt: null
})
})
})
describe('the accounts snapshot stays opaque', () => {
it('takes every shape decodeAccountsSnapshot judges', () => {
expect(refuses(homeHostAccountsSchema, null)).toBe(false)
expect(refuses(homeHostAccountsSchema, { claude: { accounts: [] } })).toBe(false)
})
})
-17
View File
@@ -1,17 +0,0 @@
import { z } from 'zod'
import { salvagedOptional } from '../../../src/shared/zod-salvage'
// Decode any unreadable row as absent: totalHomeStats skips it, but seating the host slot preserves the zeroed header instead of removing it.
// Keep firstEventAt:null as the host’s explicit no-events state when computing the minimum.
export const homeHostStatsSchema = z
.looseObject({
totalAgentsSpawned: salvagedOptional('totalAgentsSpawned', z.number()),
totalPRsCreated: salvagedOptional('totalPRsCreated', z.number()),
totalAgentTimeMs: salvagedOptional('totalAgentTimeMs', z.number()),
firstEventAt: salvagedOptional('firstEventAt', z.number().nullable())
})
.nullish()
.catch(undefined)
// Forward accounts opaquely; decodeAccountsSnapshot already validates them at the call site and is shared with the account pane and reset sheet.
export const homeHostAccountsSchema = z.unknown()
+60
View File
@@ -0,0 +1,60 @@
import { StyleSheet } from 'react-native'
import { colors, radii, spacing } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
// Text starts after the status column, so separators inset to it.
const STATUS_COLUMN_WIDTH = 24
const ROW_LEADING_PADDING = spacing.lg
/** Home's grouped lists: one inset group per section, a status column, a title and a wrapping detail line. */
export const homeListStyles = themedStyleSheet(() => ({
sectionHeading: {
fontSize: 15,
fontWeight: '600',
color: colors.textPrimary,
paddingHorizontal: spacing.xs,
marginTop: 26,
marginBottom: 9
},
firstSectionHeading: { marginTop: spacing.sm },
group: { backgroundColor: colors.bgPanel, borderRadius: radii.surface, overflow: 'hidden' },
row: { flexDirection: 'row', alignItems: 'flex-start', paddingLeft: ROW_LEADING_PADDING },
rowPressed: { backgroundColor: colors.bgRaised },
rowDisabled: { opacity: 0.45 },
separator: {
height: StyleSheet.hairlineWidth,
backgroundColor: colors.borderSubtle,
marginLeft: ROW_LEADING_PADDING + STATUS_COLUMN_WIDTH
},
fullSeparator: { marginLeft: ROW_LEADING_PADDING },
status: {
width: STATUS_COLUMN_WIDTH,
height: 22,
marginTop: 12,
justifyContent: 'center'
},
// AgentSpinner's box, so connection dots and agent dots share one column.
statusDotBox: { width: 12, alignItems: 'center' },
text: { flex: 1, minWidth: 0, paddingVertical: 12 },
title: { fontSize: 17, lineHeight: 22, color: colors.textPrimary },
subtitle: { fontSize: 15, lineHeight: 20, color: colors.textSecondary, marginTop: 1 },
detail: { fontSize: 15, lineHeight: 20, color: colors.textMuted, marginTop: 3 },
trailing: {
flexDirection: 'row',
alignItems: 'center',
gap: 6,
height: 22,
marginTop: 12,
paddingLeft: 10,
paddingRight: 14
},
time: { fontSize: 15, color: colors.textMuted, fontVariant: ['tabular-nums'] },
actionRow: {
flexDirection: 'row',
alignItems: 'center',
gap: 10,
minHeight: 52,
paddingHorizontal: ROW_LEADING_PADDING
},
actionLabel: { fontSize: 17, color: colors.accentBlue }
}))
@@ -1,25 +0,0 @@
import { bindDeferredRpcOperation, defineRpcOperation } from '../transport/rpc-operation'
import { rpcResultVariant } from '../transport/rpc-operation-result-reader'
import { homeHostAccountsSchema, homeHostStatsSchema } from './home-host-reply-schema'
// Decorative counts skip refusals to retain the displayed snapshot; accepted unreadable rows decode as absent to preserve the host slot. Reuse Tasks operations for glab/Linear probes.
export const homeHostStatsRead = bindDeferredRpcOperation(
defineRpcOperation({
name: 'stats.home-summary-or-skip',
method: 'stats.summary',
acceptance: 'success-result-or-skip',
barrier: 'after-caller-barrier',
read: rpcResultVariant('home-stats-summary', homeHostStatsSchema)
})
)
// Skip refused accounts to retain the snapshot; decodeAccountsSnapshot validates the forwarded payload at the call site.
export const homeHostAccountsRead = bindDeferredRpcOperation(
defineRpcOperation({
name: 'accounts.home-snapshot-or-skip',
method: 'accounts.list',
acceptance: 'success-result-or-skip',
barrier: 'after-caller-barrier',
read: rpcResultVariant('home-accounts-snapshot', homeHostAccountsSchema)
})
)
@@ -1,104 +0,0 @@
import { settingsRead } from '../transport/settings-read-operations'
import { decodeAccountsSnapshot, type AccountsSnapshot } from '../components/AccountUsage'
import type { HomeStatsRow } from '../stats/home-stats-total'
import { taskLinearStatusRead, taskPreflightRead } from '../tasks/mobile-task-runtime-operations'
import {
filterAvailableTaskProviders,
normalizeVisibleTaskProviders,
type TaskProvider
} from '../../../src/shared/task-providers'
import type { RpcClient } from '../transport/rpc-client'
import { homeHostAccountsRead, homeHostStatsRead } from './mobile-home-host-operations'
type HomeTaskSettings = {
visibleTaskProviders?: unknown
}
export type HomeStatsSetter = (
updater: (previous: Record<string, HomeStatsRow>) => Record<string, HomeStatsRow>
) => void
export type HomeAccountsSetter = (
updater: (previous: Record<string, AccountsSnapshot>) => Record<string, AccountsSnapshot>
) => void
export type HomeTaskProvidersSetter = (
updater: (previous: Record<string, TaskProvider[]>) => Record<string, TaskProvider[]>
) => void
export function fetchMobileHomeStats(
client: RpcClient,
hostId: string,
setStats: HomeStatsSetter,
disposed: () => boolean
): void {
homeHostStatsRead
.requestSingleFlight(client, hostId)
.then((reply) => {
const summary = homeHostStatsRead.interpret(reply)
if (!disposed() && summary.accepted) {
setStats((previous) => ({ ...previous, [hostId]: summary.value }))
}
})
.catch(() => {})
}
export function fetchMobileHomeAccounts(
client: RpcClient,
hostId: string,
setSnapshots: HomeAccountsSetter,
disposed: () => boolean
): void {
homeHostAccountsRead
.requestSingleFlight(client, hostId)
.then((reply) => {
const accounts = homeHostAccountsRead.interpret(reply)
if (!disposed() && accounts.accepted) {
const snapshot = decodeAccountsSnapshot(accounts.value)
setSnapshots((previous) => ({ ...previous, [hostId]: snapshot }))
}
})
.catch(() => {})
}
export function fetchMobileHomeTaskProviders(
client: RpcClient,
hostId: string,
setProviders: HomeTaskProvidersSetter,
disposed: () => boolean
): void {
Promise.all([
settingsRead.requestSingleFlight(client, hostId),
taskPreflightRead.requestSingleFlight(client, hostId),
taskLinearStatusRead.requestSingleFlight(client, hostId)
])
.then(([settingsResponse, preflightResponse, linearResponse]) => {
if (disposed()) {
return
}
const settingsResult = settingsRead.interpret(settingsResponse)
const settings = settingsResult.accepted
? // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Preserve the established response shape at this boundary.
((settingsResult.value ?? {}) as HomeTaskSettings)
: {}
const preflightResult = taskPreflightRead.interpret(preflightResponse)
const preflight = preflightResult.accepted ? preflightResult.value : null
const linearResult = taskLinearStatusRead.interpret(linearResponse)
const linear = linearResult.accepted ? linearResult.value : null
const providers = filterAvailableTaskProviders(
normalizeVisibleTaskProviders(settings.visibleTaskProviders),
{
gitlabInstalled: preflight?.glab?.installed === true,
linearConnected: linear?.connected === true
}
)
setProviders((previous) => ({ ...previous, [hostId]: providers }))
})
.catch(() => {
if (!disposed()) {
setProviders((previous) =>
previous[hostId] ? previous : { ...previous, [hostId]: ['github'] }
)
}
})
}
+62 -68
View File
@@ -1,56 +1,53 @@
import AsyncStorage from '@react-native-async-storage/async-storage'
import { useRouter, useFocusEffect } from 'expo-router'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import type { AccountsSnapshot } from '../components/AccountUsage'
import { hasRenderableUsage } from '../components/AccountUsage'
import { loadHomeSnapshot, saveHomeSnapshot } from '../cache/home-snapshot-cache'
import { getCachedWorktrees, setCachedWorktrees } from '../cache/worktree-cache'
import {
loadMobileOnboardingSteps,
mobileOnboardingDestination
} from '../onboarding/mobile-onboarding-plan'
import { totalHomeStats, type HomeStatsRow } from '../stats/home-stats-total'
import type { TaskProvider } from '../../../src/shared/task-providers'
import {
selectConnectableHostProfiles,
sortHostsByLastConnected
} from '../transport/host-catalog-selection'
import { loadHostCatalog } from '../transport/host-store'
import type { HostCatalogEntry, HostProfile } from '../transport/types'
import type { HostCatalogEntry } from '../transport/types'
import { fetchHomeHostWorktreeInfo } from '../worktree/home-host-worktree-fetch'
import type { HomeWorktreeSummary, HostWorktreeInfo } from '../worktree/home-worktree-info'
import {
withoutHomeAgentStates,
type HomeWorktreeSummary,
type HostWorktreeInfo
} from '../worktree/home-worktree-info'
import {
LAST_VISITED_WORKTREE_STORAGE_KEY,
readLastVisitedWorktreeRecord
} from '../worktree/last-visited-worktree-repo'
import { selectHomeJumpBackInRows } from '../worktree/home-jump-back-in'
import { selectHomeResumeCard } from '../worktree/home-resume-card'
import {
fetchMobileHomeAccounts,
fetchMobileHomeStats,
fetchMobileHomeTaskProviders
} from './mobile-home-host-requests'
import { projectHomeHostConnections } from './home-host-connection-projection'
import { useMobileHomeHostConnections } from './use-mobile-home-host-connections'
export function useMobileHomeData() {
const router = useRouter()
const [hostCatalog, setHostCatalog] = useState<HostCatalogEntry[]>([])
const [statsByHost, setStatsByHost] = useState<Record<string, HomeStatsRow>>({})
// Until the catalog read lands, an empty list says nothing; a paired user must not see first-run.
const [hostCatalogLoaded, setHostCatalogLoaded] = useState(false)
const [worktreeInfo, setWorktreeInfo] = useState<Record<string, HostWorktreeInfo>>({})
const [accountsByHost, setAccountsByHost] = useState<Record<string, AccountsSnapshot>>({})
const [taskProvidersByHost, setTaskProvidersByHost] = useState<Record<string, TaskProvider[]>>({})
const [lastVisited, setLastVisited] = useState<{ hostId: string; worktreeId: string } | null>(
null
)
const onboardingCheckedRef = useRef(false)
const hydratedRef = useRef(false)
const focusedRef = useRef(false)
const isHomeFocused = useCallback(() => focusedRef.current, [])
const hosts = useMemo(() => selectConnectableHostProfiles(hostCatalog), [hostCatalog])
const connections = useMobileHomeHostConnections(hosts, hostCatalog, {
setStats: setStatsByHost,
const connections = useMobileHomeHostConnections(
hosts,
hostCatalog,
setWorktreeInfo,
setAccounts: setAccountsByHost,
setTaskProviders: setTaskProvidersByHost
})
isHomeFocused
)
const allClientsRef = useRef(connections.allClients)
useEffect(() => {
@@ -64,10 +61,14 @@ export function useMobileHomeData() {
return
}
setWorktreeInfo((previous) =>
Object.keys(previous).length > 0 ? previous : snapshot.worktreeInfo
)
setAccountsByHost((previous) =>
Object.keys(previous).length > 0 ? previous : snapshot.accountsByHost
Object.keys(previous).length > 0
? previous
: Object.fromEntries(
Object.entries(snapshot.worktreeInfo).map(([hostId, info]) => [
hostId,
withoutHomeAgentStates(info)
])
)
)
for (const [hostId, info] of Object.entries(snapshot.worktreeInfo)) {
if (info.lastActiveWorktree) {
@@ -81,10 +82,10 @@ export function useMobileHomeData() {
}, [])
useEffect(() => {
if (Object.keys(worktreeInfo).length > 0 || Object.keys(accountsByHost).length > 0) {
saveHomeSnapshot({ worktreeInfo, accountsByHost, savedAt: Date.now() })
if (Object.keys(worktreeInfo).length > 0) {
saveHomeSnapshot({ worktreeInfo, savedAt: Date.now() })
}
}, [worktreeInfo, accountsByHost])
}, [worktreeInfo])
useEffect(() => {
allClientsRef.current = connections.allClients
@@ -93,20 +94,28 @@ export function useMobileHomeData() {
useFocusEffect(
useCallback(() => {
let stale = false
void loadHostCatalog().then(async (catalog) => {
if (stale) {
return
}
setHostCatalog(catalog)
if (catalog.length === 0 || onboardingCheckedRef.current) {
return
}
onboardingCheckedRef.current = true
const steps = await loadMobileOnboardingSteps()
if (!stale && steps.length > 0) {
router.replace(mobileOnboardingDestination(steps))
}
})
focusedRef.current = true
void loadHostCatalog()
// A failed read proves nothing about the desktops; keep the ones already shown.
.catch((): HostCatalogEntry[] | null => null)
.then(async (catalog) => {
if (stale) {
return
}
setHostCatalogLoaded(true)
if (!catalog) {
return
}
setHostCatalog(catalog)
if (catalog.length === 0 || onboardingCheckedRef.current) {
return
}
onboardingCheckedRef.current = true
const steps = await loadMobileOnboardingSteps()
if (!stale && steps.length > 0) {
router.replace(mobileOnboardingDestination(steps))
}
})
void AsyncStorage.getItem(LAST_VISITED_WORKTREE_STORAGE_KEY).then((raw) => {
if (!stale) {
setLastVisited(readLastVisitedWorktreeRecord(raw))
@@ -114,27 +123,18 @@ export function useMobileHomeData() {
})
for (const entry of allClientsRef.current) {
if (entry.client.getState() === 'connected') {
fetchMobileHomeStats(entry.client, entry.hostId, setStatsByHost, () => stale)
void fetchHomeHostWorktreeInfo(entry.client, entry.hostId, setWorktreeInfo, () => stale)
fetchMobileHomeAccounts(entry.client, entry.hostId, setAccountsByHost, () => stale)
fetchMobileHomeTaskProviders(
entry.client,
entry.hostId,
setTaskProvidersByHost,
() => stale
)
}
}
return () => {
stale = true
focusedRef.current = false
}
}, [router])
)
const sortedHosts = useMemo(() => sortHostsByLastConnected(hosts), [hosts])
const sortedHostCatalog = useMemo(() => sortHostsByLastConnected(hostCatalog), [hostCatalog])
const hostIds = useMemo(() => hosts.map((host) => host.id), [hosts])
const stats = useMemo(() => totalHomeStats(statsByHost, hostIds), [statsByHost, hostIds])
const resumeCard = useMemo(
() =>
selectHomeResumeCard({
@@ -146,20 +146,16 @@ export function useMobileHomeData() {
}),
[sortedHosts, connections.hostStates, worktreeInfo, lastVisited]
)
const accountsHosts = useMemo(() => {
const items: { host: HostProfile; snapshot: AccountsSnapshot }[] = []
for (const host of sortedHosts) {
const snapshot = accountsByHost[host.id]
if (
connections.hostStates[host.id] === 'connected' &&
snapshot &&
(hasRenderableUsage(snapshot, 'claude') || hasRenderableUsage(snapshot, 'codex'))
) {
items.push({ host, snapshot })
}
}
return items
}, [sortedHosts, connections.hostStates, accountsByHost])
const jumpBackInRows = useMemo(
() =>
selectHomeJumpBackInRows({
hosts: sortedHosts,
hostStates: connections.hostStates,
worktreeInfo,
resumeCard
}),
[sortedHosts, connections.hostStates, worktreeInfo, resumeCard]
)
const connectedHosts = useMemo(
() => sortedHosts.filter((host) => connections.hostStates[host.id] === 'connected'),
[sortedHosts, connections.hostStates]
@@ -171,16 +167,14 @@ export function useMobileHomeData() {
return {
...connections,
accountsHosts,
connectedHosts,
hostCatalog,
hostCatalogLoaded,
hostConnections,
resumeCard,
jumpBackInRows,
router,
setHostCatalog,
sortedHostCatalog,
stats,
taskProvidersByHost,
worktreeInfo
}
}
@@ -1,5 +1,4 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import { decodeAccountsSnapshot } from '../components/AccountUsage'
import { subscribeToDesktopNotifications } from '../notifications/mobile-notifications'
import { usePrimeHosts } from '../transport/host-client-hooks'
import { createHostConnectRefetchGate } from '../transport/host-connect-refetch-gate'
@@ -11,87 +10,104 @@ import {
fetchHomeHostWorktreeInfo,
type HostWorktreeInfoSetter
} from '../worktree/home-host-worktree-fetch'
import { withoutHomeAgentStates } from '../worktree/home-worktree-info'
import {
readMobileHomeLastConnected,
readMobileHomeReconnectAttempts,
reconcileMobileHomeHostStates
} from './mobile-home-connection-state'
import {
fetchMobileHomeStats,
fetchMobileHomeTaskProviders,
type HomeAccountsSetter,
type HomeStatsSetter,
type HomeTaskProvidersSetter
} from './mobile-home-host-requests'
type Setters = {
setStats: HomeStatsSetter
setWorktreeInfo: HostWorktreeInfoSetter
setAccounts: HomeAccountsSetter
setTaskProviders: HomeTaskProvidersSetter
}
// Same pace as the workspace list's poll; agent status notices can arrive many times a second.
const HOME_WORKTREE_REFRESH_MIN_INTERVAL_MS = 3000
const WORKTREE_CHANGE_EVENTS: ReadonlySet<string> = new Set([
'sidebarInputsChanged',
'worktreesChanged',
'reposChanged'
])
function wireMobileHomeHostSubscriptions(
entry: { hostId: string; client: RpcClient; state: ConnectionState },
setters: Setters
setWorktreeInfo: HostWorktreeInfoSetter,
isHomeFocused: () => boolean
): () => void {
let unsubscribeNotifications: (() => void) | null = null
let unsubscribeAccounts: (() => void) | null = null
let unsubscribeClientEvents: (() => void) | null = null
let lastRefreshAt = 0
let trailingRefresh: ReturnType<typeof setTimeout> | null = null
const refetchGate = createHostConnectRefetchGate()
const refreshWorktrees = (): void => {
lastRefreshAt = Date.now()
void fetchHomeHostWorktreeInfo(entry.client, entry.hostId, setWorktreeInfo, () => false)
}
// Home stays mounted under other screens; focusing it again re-reads every desktop anyway.
const refreshOnChange = (): void => {
if (!isHomeFocused() || trailingRefresh) {
return
}
const wait = lastRefreshAt + HOME_WORKTREE_REFRESH_MIN_INTERVAL_MS - Date.now()
if (wait <= 0) {
refreshWorktrees()
return
}
trailingRefresh = setTimeout(() => {
trailingRefresh = null
if (isHomeFocused()) {
refreshWorktrees()
}
}, wait)
}
const wireState = (state: ConnectionState): void => {
const reconnected = refetchGate.observe(state)
if (state === 'connected') {
unsubscribeNotifications ??= subscribeToDesktopNotifications(entry.client, entry.hostId)
unsubscribeAccounts ??= entry.client.subscribe('accounts.subscribe', null, (payload) => {
if (!payload || typeof payload !== 'object') {
return
unsubscribeClientEvents ??= entry.client.subscribe(
'runtime.clientEvents.subscribe',
null,
(payload) => {
if (
payload &&
typeof payload === 'object' &&
'type' in payload &&
typeof payload.type === 'string' &&
WORKTREE_CHANGE_EVENTS.has(payload.type)
) {
refreshOnChange()
}
}
const event = payload as { type?: string; snapshot?: unknown }
if (event.type !== 'ready' && event.type !== 'snapshot') {
return
}
try {
const snapshot = decodeAccountsSnapshot(event.snapshot)
setters.setAccounts((previous) => ({ ...previous, [entry.hostId]: snapshot }))
} catch {
// Keep the last proven snapshot when a mixed-version host publishes malformed data.
}
})
)
if (reconnected) {
fetchMobileHomeStats(entry.client, entry.hostId, setters.setStats, () => false)
void fetchHomeHostWorktreeInfo(
entry.client,
entry.hostId,
setters.setWorktreeInfo,
() => false
)
fetchMobileHomeTaskProviders(
entry.client,
entry.hostId,
setters.setTaskProviders,
() => false
)
refreshWorktrees()
}
return
}
unsubscribeNotifications?.()
unsubscribeNotifications = null
unsubscribeAccounts?.()
unsubscribeAccounts = null
unsubscribeClientEvents?.()
unsubscribeClientEvents = null
// Agents keep moving while we're away; only the reconnect read may show their states again.
setWorktreeInfo((previous) => {
const current = previous[entry.hostId]
const next = current && withoutHomeAgentStates(current)
return next === current ? previous : { ...previous, [entry.hostId]: next }
})
}
wireState(entry.state)
const unsubscribeState = entry.client.onStateChange(wireState)
return () => {
unsubscribeState()
unsubscribeNotifications?.()
unsubscribeAccounts?.()
unsubscribeClientEvents?.()
if (trailingRefresh) {
clearTimeout(trailingRefresh)
}
}
}
export function useMobileHomeHostConnections(
hosts: HostProfile[],
hostCatalog: HostCatalogEntry[],
setters: Setters
setWorktreeInfo: HostWorktreeInfoSetter,
isHomeFocused: () => boolean
) {
const [hostStates, setHostStates] = useState<Record<string, ConnectionState>>({})
const [hostAttempts, setHostAttempts] = useState<Record<string, number>>({})
@@ -104,7 +120,6 @@ export function useMobileHomeHostConnections(
})
const primeHosts = usePrimeHosts()
const subscriptionsRef = useRef(new Map<string, { client: RpcClient; cleanup: () => void }>())
const { setAccounts, setStats, setTaskProviders, setWorktreeInfo } = setters
useEffect(() => {
if (hosts.length > 0) {
@@ -126,16 +141,15 @@ export function useMobileHomeHostConnections(
subscriptionsRef.current.delete(hostId)
}
}
const activeSetters = { setAccounts, setStats, setTaskProviders, setWorktreeInfo }
for (const entry of allClients) {
if (!subscriptionsRef.current.has(entry.hostId)) {
subscriptionsRef.current.set(entry.hostId, {
client: entry.client,
cleanup: wireMobileHomeHostSubscriptions(entry, activeSetters)
cleanup: wireMobileHomeHostSubscriptions(entry, setWorktreeInfo, isHomeFocused)
})
}
}
}, [allClients, setAccounts, setStats, setTaskProviders, setWorktreeInfo])
}, [allClients, isHomeFocused, setWorktreeInfo])
useEffect(() => {
const subscriptions = subscriptionsRef.current
+3 -32
View File
@@ -767,11 +767,6 @@
"moveUp": "Nach oben verschieben",
"reorderHint": "Mit den Aktionen „Nach oben verschieben“ und „Nach unten verschieben“ lässt sich die Reihenfolge ohne Ziehen ändern"
},
"homeQuickActions": {
"newWorkspace": "Arbeitsbereich erstellen",
"pairDesktop": "Desktop koppeln",
"title": "Schnellaktionen"
},
"hostCard": {
"hostActions": "Aktionen für {{title}}",
"openHost": "{{title}} öffnen",
@@ -1302,17 +1297,10 @@
}
},
"home": {
"accountUsageCards": {
"sectionHeading": "Kontonutzung",
"sessionWindow": "5 h",
"systemDefault": "Systemstandard",
"weeklyWindow": "7 Tage"
},
"appUpdateCard": {
"dismissAccessibilityLabel": "Update-Hinweis für Orca {{version}} ausblenden",
"title": "Orca {{version}} ist verfügbar",
"updateAccessibilityLabel": "Orca {{version}} ist verfügbar, zum Aktualisieren antippen",
"updateHint": "Zum Aktualisieren antippen"
"updateAccessibilityLabel": "Orca {{version}} ist verfügbar, zum Aktualisieren antippen"
},
"emptyState": {
"connectedDescription": "Der Desktop erscheint hier. Alles ist Ende-zu-Ende-verschlüsselt.",
@@ -1320,26 +1308,14 @@
"description": "Mit Orca auf dem Computer koppeln, um Agenten im Blick zu behalten, beliebige Terminals zu öffnen und die Arbeit vom Smartphone aus zu steuern.",
"openDesktopDescription": "Einstellungen → Mobil öffnen und einen QR-Code zum Koppeln erstellen.",
"openDesktopTitle": "Orca-Desktop-App öffnen",
"pairDesktop": "Desktop koppeln",
"scanCodeDescription": "Die Schaltfläche oben antippen, um den Scanner zu öffnen. Die Kamera auf den QR-Code auf dem Bildschirm richten.",
"scanCodeTitle": "Code scannen",
"stepsHeading": "So funktioniert’s",
"title": "Desktop verbinden"
},
"listFooter": {
"hostPickerTitle": "Aufgaben auf Host öffnen",
"resumeHeading": "Fortsetzen",
"tasksHeading": "Aufgaben"
"hostPickerTitle": "Aufgaben auf Host öffnen"
},
"listHeader": {
"agentTime": "Agentenzeit",
"agentsSpawned": "Gestartete Agenten",
"daysHours": "{{days}} T {{hours}} Std.",
"desktopsHeading": "Desktop-Computer",
"hoursMinutes": "{{hours}} Std. {{minutes}} Min.",
"minutes": "{{minutes}} Min.",
"prsCreated": "Erstellte PRs",
"title": "Willkommen zurück"
"desktopsHeading": "Desktop-Computer"
},
"screen": {
"pairingCheckFailed": "Kopplung konnte nicht geprüft werden",
@@ -1347,11 +1323,6 @@
"removeHostMessage": "„{{name}}“ entfernen? Eine erneute Kopplung ist später möglich.",
"removeHostTitle": "Host entfernen",
"tryAgain": "Bitte erneut versuchen."
},
"tasksCard": {
"noSources": "Keine Aufgabenquellen verbunden",
"openProviderTasks": "{{provider}}-Aufgaben öffnen",
"title": "Aufgaben"
}
},
"hooks": {
+16 -29
View File
@@ -478,40 +478,24 @@
"appUpdateCard": {
"title": "Orca {{version}} is available",
"updateAccessibilityLabel": "Orca {{version}} is available, tap to update",
"updateHint": "Tap to update",
"dismissAccessibilityLabel": "Dismiss Orca {{version}} update"
},
"accountUsageCards": {
"sectionHeading": "Account usage",
"systemDefault": "System default",
"sessionWindow": "5h",
"weeklyWindow": "7d"
"dismissAccessibilityLabel": "Dismiss Orca {{version}} update",
"update": "Update"
},
"emptyState": {
"openDesktopTitle": "Open Orca desktop",
"openDesktopDescription": "Go to Settings → Mobile and generate a pairing QR code.",
"scanCodeTitle": "Scan the code",
"scanCodeDescription": "Tap the button above to open the scanner. Point at the QR code on your screen.",
"connectedTitle": "You're connected",
"connectedDescription": "Your desktop will appear here. Everything is encrypted end-to-end.",
"title": "Connect your desktop",
"description": "Pair with Orca on your computer to check on your agents, jump into any terminal, and drive work from your phone.",
"pairDesktop": "Pair Desktop",
"stepsHeading": "How it works"
"pairDesktopButton": "Pair desktop",
"scanCodeBelowDescription": "Tap Pair desktop below, then point your camera at the QR code on your screen."
},
"listFooter": {
"resumeHeading": "Resume",
"tasksHeading": "Tasks",
"hostPickerTitle": "Open Tasks On"
},
"listHeader": {
"daysHours": "{{days}}d {{hours}}h",
"hoursMinutes": "{{hours}}h {{minutes}}m",
"minutes": "{{minutes}}m",
"title": "Welcome back",
"agentsSpawned": "Agents spawned",
"agentTime": "Agent time",
"prsCreated": "PRs created",
"desktopsHeading": "Desktops"
},
"screen": {
@@ -521,10 +505,18 @@
"removeHostTitle": "Remove Host",
"removeHostMessage": "Remove \"{{name}}\"? You can re-pair later."
},
"tasksCard": {
"title": "Tasks",
"noSources": "No task sources connected",
"openProviderTasks": "Open {{provider}} tasks"
"bottomBar": {
"connectFirst": "Connect a desktop to start new work.",
"tasks": "Tasks",
"newWorkspace": "New workspace"
},
"desktops": {
"pairDesktop": "Pair a desktop"
},
"jumpBackIn": {
"heading": "Jump back in",
"lastOpened": "Last opened",
"connectToOpen": "Connect {{desktop}} to open"
}
},
"hooks": {
@@ -1028,11 +1020,6 @@
"addNoteOnLine": "Add note on line {{number}}",
"editNoteOnLine": "Edit note on line {{number}}"
},
"homeQuickActions": {
"title": "Quick Actions",
"pairDesktop": "Pair Desktop",
"newWorkspace": "New Workspace"
},
"hostCard": {
"worktreeAccessibilitySummary": "{{summary}}, {{activeCount}} active",
"pairingInvalid": "Pairing invalid",
+3 -32
View File
@@ -809,11 +809,6 @@
"moveUp": "Subir",
"reorderHint": "Usa las acciones \"Subir\" y \"Bajar\" para reordenar sin arrastrar."
},
"homeQuickActions": {
"newWorkspace": "Nuevo espacio de trabajo",
"pairDesktop": "Emparejar escritorio",
"title": "Acciones rápidas"
},
"hostCard": {
"hostActions": "Acciones para {{title}}",
"openHost": "Abrir {{title}}",
@@ -1345,17 +1340,10 @@
}
},
"home": {
"accountUsageCards": {
"sectionHeading": "Uso de la cuenta",
"sessionWindow": "5h",
"systemDefault": "Predeterminado del sistema",
"weeklyWindow": "7d"
},
"appUpdateCard": {
"dismissAccessibilityLabel": "Descartar la actualización de Orca {{version}}",
"title": "Orca {{version}} está disponible",
"updateAccessibilityLabel": "Orca {{version}} está disponible, toca para actualizar",
"updateHint": "Toca para actualizar"
"updateAccessibilityLabel": "Orca {{version}} está disponible, toca para actualizar"
},
"emptyState": {
"connectedDescription": "Tu escritorio aparecerá aquí. Todo está cifrado de extremo a extremo.",
@@ -1363,26 +1351,14 @@
"description": "Empareja tu teléfono con Orca en tu ordenador para supervisar a tus agentes, acceder a cualquier terminal y gestionar el trabajo desde el móvil.",
"openDesktopDescription": "Ve a Ajustes → Móvil y genera un código QR de emparejamiento.",
"openDesktopTitle": "Abre Orca en el escritorio",
"pairDesktop": "Emparejar escritorio",
"scanCodeDescription": "Toca el botón de arriba para abrir el escáner. Apunta al código QR de tu pantalla.",
"scanCodeTitle": "Escanea el código",
"stepsHeading": "Cómo funciona",
"title": "Conecta tu escritorio"
},
"listFooter": {
"hostPickerTitle": "Abrir tareas en",
"resumeHeading": "Reanudar",
"tasksHeading": "Tareas"
"hostPickerTitle": "Abrir tareas en"
},
"listHeader": {
"agentTime": "Tiempo de agentes",
"agentsSpawned": "Agentes iniciados",
"daysHours": "{{days}} d {{hours}} h",
"desktopsHeading": "Escritorios",
"hoursMinutes": "{{hours}} h {{minutes}} min",
"minutes": "{{minutes}} min",
"prsCreated": "PRs creados",
"title": "Bienvenido de nuevo"
"desktopsHeading": "Escritorios"
},
"screen": {
"pairingCheckFailed": "No se pudo comprobar el emparejamiento",
@@ -1390,11 +1366,6 @@
"removeHostMessage": "¿Eliminar \"{{name}}\"? Puedes volver a emparejarlo más tarde.",
"removeHostTitle": "Eliminar host",
"tryAgain": "Por favor, vuelve a intentarlo."
},
"tasksCard": {
"noSources": "No hay fuentes de tareas conectadas",
"openProviderTasks": "Abrir tareas de {{provider}}",
"title": "Tareas"
}
},
"hooks": {
+3 -32
View File
@@ -809,11 +809,6 @@
"moveUp": "Monter",
"reorderHint": "Utilisez les actions « Monter » et « Descendre » pour réordonner sans faire glisser"
},
"homeQuickActions": {
"newWorkspace": "Nouvel espace de travail",
"pairDesktop": "Appairer l'ordinateur",
"title": "Actions rapides"
},
"hostCard": {
"hostActions": "Actions pour {{title}}",
"openHost": "Ouvrir {{title}}",
@@ -1345,17 +1340,10 @@
}
},
"home": {
"accountUsageCards": {
"sectionHeading": "Utilisation du compte",
"sessionWindow": "5 h",
"systemDefault": "Valeur par défaut du système",
"weeklyWindow": "7 j"
},
"appUpdateCard": {
"dismissAccessibilityLabel": "Ignorer la mise à jour Orca {{version}}",
"title": "Orca {{version}} est disponible",
"updateAccessibilityLabel": "Orca {{version}} est disponible, touchez pour mettre à jour",
"updateHint": "Touchez pour mettre à jour"
"updateAccessibilityLabel": "Orca {{version}} est disponible, touchez pour mettre à jour"
},
"emptyState": {
"connectedDescription": "Votre ordinateur apparaîtra ici. Tout est chiffré de bout en bout.",
@@ -1363,26 +1351,14 @@
"description": "Appairez votre téléphone à Orca sur votre ordinateur pour suivre vos agents, accéder à n'importe quel terminal et piloter votre travail depuis votre téléphone.",
"openDesktopDescription": "Allez dans Paramètres → Mobile et générez un QR code d'appairage.",
"openDesktopTitle": "Ouvrir Orca sur l'ordinateur",
"pairDesktop": "Appairer l'ordinateur",
"scanCodeDescription": "Touchez le bouton ci-dessus pour ouvrir le scanner. Visez le QR code affiché à l'écran.",
"scanCodeTitle": "Scanner le code",
"stepsHeading": "Fonctionnement",
"title": "Connectez votre ordinateur"
},
"listFooter": {
"hostPickerTitle": "Ouvrir les tâches sur",
"resumeHeading": "Reprendre",
"tasksHeading": "Tâches"
"hostPickerTitle": "Ouvrir les tâches sur"
},
"listHeader": {
"agentTime": "Temps agent",
"agentsSpawned": "Agents lancés",
"daysHours": "{{days}} j {{hours}} h",
"desktopsHeading": "Ordinateurs",
"hoursMinutes": "{{hours}} h {{minutes}} min",
"minutes": "{{minutes}} min",
"prsCreated": "PRs créées",
"title": "Bon retour"
"desktopsHeading": "Ordinateurs"
},
"screen": {
"pairingCheckFailed": "Impossible de vérifier l'appairage",
@@ -1390,11 +1366,6 @@
"removeHostMessage": "Supprimer « {{name}} » ? Vous pourrez l'appairer à nouveau plus tard.",
"removeHostTitle": "Supprimer l'hôte",
"tryAgain": "Veuillez réessayer."
},
"tasksCard": {
"noSources": "Aucune source de tâches connectée",
"openProviderTasks": "Ouvrir les tâches de {{provider}}",
"title": "Tâches"
}
},
"hooks": {
+3 -32
View File
@@ -763,11 +763,6 @@
"moveUp": "上に移動",
"reorderHint": "「上に移動」「下に移動」の操作で、ドラッグせずに並べ替えられます。"
},
"homeQuickActions": {
"newWorkspace": "新規ワークスペース",
"pairDesktop": "デスクトップのペアリング",
"title": "クイック操作"
},
"hostCard": {
"hostActions": "「{{title}}」の操作",
"openHost": "「{{title}}」を開く",
@@ -1298,17 +1293,10 @@
}
},
"home": {
"accountUsageCards": {
"sectionHeading": "アカウントの使用状況",
"sessionWindow": "5時間",
"systemDefault": "システムのデフォルト",
"weeklyWindow": "7日"
},
"appUpdateCard": {
"dismissAccessibilityLabel": "Orca {{version}} の更新通知を閉じる",
"title": "Orca {{version}} が利用可能です",
"updateAccessibilityLabel": "Orca {{version}} が利用可能です。タップして更新してください。",
"updateHint": "タップして更新"
"updateAccessibilityLabel": "Orca {{version}} が利用可能です。タップして更新してください。"
},
"emptyState": {
"connectedDescription": "デスクトップがここに表示されます。すべての通信はエンドツーエンドで暗号化されます。",
@@ -1316,26 +1304,14 @@
"description": "コンピュータ上の Orca とペアリングすると、スマートフォンから Agent の状況を確認したり、任意のターミナルを操作したりして、作業を進められます。",
"openDesktopDescription": "「設定」→「モバイル」を開き、ペアリング用の QR コードを生成してください。",
"openDesktopTitle": "デスクトップで Orca を開く",
"pairDesktop": "デスクトップのペアリング",
"scanCodeDescription": "上のボタンをタップしてスキャナーを開いてください。画面の QR コードにカメラを向けてください。",
"scanCodeTitle": "コードをスキャン",
"stepsHeading": "使い方",
"title": "デスクトップに接続"
},
"listFooter": {
"hostPickerTitle": "タスクを開くホスト",
"resumeHeading": "再開",
"tasksHeading": "タスク"
"hostPickerTitle": "タスクを開くホスト"
},
"listHeader": {
"agentTime": "Agent 時間",
"agentsSpawned": "Agent が生成されました",
"daysHours": "{{days}}日 {{hours}}時間",
"desktopsHeading": "デスクトップ",
"hoursMinutes": "{{hours}}時間 {{minutes}}分",
"minutes": "{{minutes}}分",
"prsCreated": "作成された PR",
"title": "おかえり"
"desktopsHeading": "デスクトップ"
},
"screen": {
"pairingCheckFailed": "ペアリングを確認できませんでした",
@@ -1343,11 +1319,6 @@
"removeHostMessage": "「{{name}}」を削除しますか?後で再度ペアリングできます。",
"removeHostTitle": "ホストを削除",
"tryAgain": "再試行してください。"
},
"tasksCard": {
"noSources": "タスクの連携先が接続されていません",
"openProviderTasks": "{{provider}} のタスクを開く",
"title": "タスク"
}
},
"hooks": {
+3 -32
View File
@@ -763,11 +763,6 @@
"moveUp": "위로 이동",
"reorderHint": "드래그하지 않고 순서를 바꾸려면 '위로 이동'과 '아래로 이동' 동작을 사용하세요."
},
"homeQuickActions": {
"newWorkspace": "새 워크스페이스",
"pairDesktop": "데스크탑 페어링",
"title": "빠른 작업"
},
"hostCard": {
"hostActions": "{{title}} 관련 작업",
"openHost": "{{title}} 열기",
@@ -1298,17 +1293,10 @@
}
},
"home": {
"accountUsageCards": {
"sectionHeading": "계정 사용량",
"sessionWindow": "5시간",
"systemDefault": "시스템 기본값",
"weeklyWindow": "7일"
},
"appUpdateCard": {
"dismissAccessibilityLabel": "Orca {{version}} 업데이트 알림 닫기",
"title": "Orca {{version}} 업데이트 가능",
"updateAccessibilityLabel": "Orca {{version}} 업데이트가 있습니다. 탭하여 업데이트하세요.",
"updateHint": "탭하여 업데이트하세요"
"updateAccessibilityLabel": "Orca {{version}} 업데이트가 있습니다. 탭하여 업데이트하세요."
},
"emptyState": {
"connectedDescription": "데스크톱이 여기에 표시됩니다. 모든 데이터는 종단 간 암호화로 보호됩니다.",
@@ -1316,26 +1304,14 @@
"description": "컴퓨터의 Orca와 페어링하여 에이전트 상태를 확인하고, 원하는 터미널에 접속하고, 휴대폰으로 작업을 진행하세요.",
"openDesktopDescription": "설정 → 모바일에서 페어링 QR 코드를 생성하세요.",
"openDesktopTitle": "Orca 데스크톱 앱 열기",
"pairDesktop": "데스크탑 페어링",
"scanCodeDescription": "위의 버튼을 탭해 스캐너를 여세요. 데스크톱 화면의 QR 코드에 카메라를 맞추세요.",
"scanCodeTitle": "코드 스캔",
"stepsHeading": "사용 방법",
"title": "데스크톱을 연결하세요"
},
"listFooter": {
"hostPickerTitle": "작업을 열 호스트",
"resumeHeading": "재개",
"tasksHeading": "작업"
"hostPickerTitle": "작업을 열 호스트"
},
"listHeader": {
"agentTime": "Agent 시간",
"agentsSpawned": "시작된 Agents",
"daysHours": "{{days}}일 {{hours}}시간",
"desktopsHeading": "데스크탑",
"hoursMinutes": "{{hours}}시간 {{minutes}}분",
"minutes": "{{minutes}}분",
"prsCreated": "생성된 PR",
"title": "다시 오신 것을 환영합니다"
"desktopsHeading": "데스크탑"
},
"screen": {
"pairingCheckFailed": "페어링 확인 실패",
@@ -1343,11 +1319,6 @@
"removeHostMessage": "\"{{name}}\"을(를) 제거하시겠습니까? 나중에 다시 페어링할 수 있습니다.",
"removeHostTitle": "호스트 제거",
"tryAgain": "다시 시도하세요."
},
"tasksCard": {
"noSources": "연결된 작업 소스 없음",
"openProviderTasks": "{{provider}} 작업 열기",
"title": "작업"
}
},
"hooks": {
+3 -32
View File
@@ -809,11 +809,6 @@
"moveUp": "Mover para cima",
"reorderHint": "Use as ações mover para cima e mover para baixo para reordenar sem arrastar"
},
"homeQuickActions": {
"newWorkspace": "Novo Espaço de Trabalho",
"pairDesktop": "Parear Desktop",
"title": "Ações Rápidas"
},
"hostCard": {
"hostActions": "Ações para {{title}}",
"openHost": "Abrir {{title}}",
@@ -1345,17 +1340,10 @@
}
},
"home": {
"accountUsageCards": {
"sectionHeading": "Uso da conta",
"sessionWindow": "5h",
"systemDefault": "Padrão do sistema",
"weeklyWindow": "7d"
},
"appUpdateCard": {
"dismissAccessibilityLabel": "Descartar atualização do Orca {{version}}",
"title": "Orca {{version}} está disponível",
"updateAccessibilityLabel": "Orca {{version}} está disponível, toque para atualizar",
"updateHint": "Toque para atualizar"
"updateAccessibilityLabel": "Orca {{version}} está disponível, toque para atualizar"
},
"emptyState": {
"connectedDescription": "Seu desktop aparecerá aqui. Tudo é criptografado de ponta a ponta.",
@@ -1363,26 +1351,14 @@
"description": "Pareie com o Orca no seu computador para acompanhar seus agentes, acessar qualquer terminal e gerenciar o trabalho pelo celular.",
"openDesktopDescription": "Vá para Configurações → Mobile e gere um código QR de pareamento.",
"openDesktopTitle": "Abra o Orca no desktop",
"pairDesktop": "Parear Desktop",
"scanCodeDescription": "Toque no botão acima para abrir o leitor. Aponte a câmera para o código QR na tela.",
"scanCodeTitle": "Escaneie o código",
"stepsHeading": "Como funciona",
"title": "Conecte seu desktop"
},
"listFooter": {
"hostPickerTitle": "Abrir tarefas em",
"resumeHeading": "Retomar",
"tasksHeading": "Tarefas"
"hostPickerTitle": "Abrir tarefas em"
},
"listHeader": {
"agentTime": "Tempo de agente",
"agentsSpawned": "Agentes iniciados",
"daysHours": "{{days}}d {{hours}}h",
"desktopsHeading": "Desktops",
"hoursMinutes": "{{hours}}h {{minutes}}min",
"minutes": "{{minutes}}min",
"prsCreated": "PRs criados",
"title": "Bem-vindo de volta"
"desktopsHeading": "Desktops"
},
"screen": {
"pairingCheckFailed": "Não foi possível verificar o pareamento",
@@ -1390,11 +1366,6 @@
"removeHostMessage": "Remover \"{{name}}\"? Você pode parear novamente mais tarde.",
"removeHostTitle": "Remover host",
"tryAgain": "Tente novamente."
},
"tasksCard": {
"noSources": "Nenhuma origem de tarefas conectada",
"openProviderTasks": "Abrir tarefas do {{provider}}",
"title": "Tarefas"
}
},
"hooks": {
+3 -32
View File
@@ -851,11 +851,6 @@
"moveUp": "Переместить вверх",
"reorderHint": "Чтобы изменить порядок без перетаскивания, используйте действия «Переместить вверх» и «Переместить вниз»"
},
"homeQuickActions": {
"newWorkspace": "Новое рабочее пространство",
"pairDesktop": "Подключить компьютер",
"title": "Быстрые действия"
},
"hostCard": {
"hostActions": "Действия для {{title}}",
"openHost": "Открыть {{title}}",
@@ -1388,17 +1383,10 @@
}
},
"home": {
"accountUsageCards": {
"sectionHeading": "Использование учётной записи",
"sessionWindow": "5 ч",
"systemDefault": "Как в системе",
"weeklyWindow": "7 дн."
},
"appUpdateCard": {
"dismissAccessibilityLabel": "Скрыть уведомление об обновлении Orca {{version}}",
"title": "Доступна Orca {{version}}",
"updateAccessibilityLabel": "Доступна Orca {{version}}. Нажмите, чтобы обновить",
"updateHint": "Нажмите, чтобы обновить"
"updateAccessibilityLabel": "Доступна Orca {{version}}. Нажмите, чтобы обновить"
},
"emptyState": {
"connectedDescription": "Здесь появится ваш компьютер. Все данные защищены сквозным шифрованием.",
@@ -1406,26 +1394,14 @@
"description": "Подключитесь к Orca на компьютере, чтобы следить за агентами, открывать любой терминал и управлять работой с телефона.",
"openDesktopDescription": "Откройте «Настройки» → «Мобильный доступ» и создайте QR-код для сопряжения.",
"openDesktopTitle": "Откройте Orca на компьютере",
"pairDesktop": "Подключить компьютер",
"scanCodeDescription": "Нажмите кнопку выше, чтобы открыть сканер. Наведите камеру на QR-код на экране компьютера.",
"scanCodeTitle": "Отсканируйте код",
"stepsHeading": "Как это работает",
"title": "Подключите компьютер"
},
"listFooter": {
"hostPickerTitle": "Открыть задачи на сервере",
"resumeHeading": "Продолжить",
"tasksHeading": "Задачи"
"hostPickerTitle": "Открыть задачи на сервере"
},
"listHeader": {
"agentTime": "Время агентов",
"agentsSpawned": "Запущено агентов",
"daysHours": "{{days}} дн. {{hours}} ч",
"desktopsHeading": "Компьютеры",
"hoursMinutes": "{{hours}} ч {{minutes}} мин",
"minutes": "{{minutes}} мин",
"prsCreated": "Создано PR",
"title": "С возвращением"
"desktopsHeading": "Компьютеры"
},
"screen": {
"pairingCheckFailed": "Не удалось проверить сопряжение",
@@ -1433,11 +1409,6 @@
"removeHostMessage": "Удалить «{{name}}»? Позже его можно подключить снова.",
"removeHostTitle": "Удалить сервер",
"tryAgain": "Попробуйте ещё раз."
},
"tasksCard": {
"noSources": "Источники задач не подключены",
"openProviderTasks": "Открыть задачи {{provider}}",
"title": "Задачи"
}
},
"hooks": {
+3 -32
View File
@@ -767,11 +767,6 @@
"moveUp": "Yukarı taşı",
"reorderHint": "Sürüklemeden yeniden sıralamak için yukarı taşı ve aşağı taşı eylemlerini kullanın"
},
"homeQuickActions": {
"newWorkspace": "Yeni Çalışma Alanı",
"pairDesktop": "Masaüstünü Eşleştir",
"title": "Hızlı İşlemler"
},
"hostCard": {
"hostActions": "{{title}} için işlemler",
"openHost": "Aç: {{title}}",
@@ -1302,17 +1297,10 @@
}
},
"home": {
"accountUsageCards": {
"sectionHeading": "Hesap kullanımı",
"sessionWindow": "5 sa",
"systemDefault": "Sistem varsayılanı",
"weeklyWindow": "7g"
},
"appUpdateCard": {
"dismissAccessibilityLabel": "Orca {{version}} güncellemesini gizle",
"title": "Orca {{version}} sürümü mevcut",
"updateAccessibilityLabel": "Orca {{version}} sürümü mevcut, güncellemek için dokunun",
"updateHint": "Güncellemek için dokunun"
"updateAccessibilityLabel": "Orca {{version}} sürümü mevcut, güncellemek için dokunun"
},
"emptyState": {
"connectedDescription": "Masaüstünüz burada görünecek. Her şey uçtan uca şifrelenir.",
@@ -1320,26 +1308,14 @@
"description": "Ajanlarınızı takip etmek, istediğiniz terminale erişmek ve işlerinizi telefonunuzdan yönetmek için bilgisayarınızdaki Orca ile eşleştirin.",
"openDesktopDescription": "Ayarlar → Mobil bölümüne gidin ve eşleştirme için bir QR kodu oluşturun.",
"openDesktopTitle": "Orca masaüstü uygulamasını açın",
"pairDesktop": "Masaüstünü Eşleştir",
"scanCodeDescription": "Tarayıcıyı açmak için yukarıdaki düğmeye dokunun. Kamerayı ekranınızdaki QR koduna doğrultun.",
"scanCodeTitle": "Kodu tarayın",
"stepsHeading": "Nasıl çalışır",
"title": "Masaüstünüzü bağlayın"
},
"listFooter": {
"hostPickerTitle": "Görevlerin Açılacağı Sunucu",
"resumeHeading": "Devam et",
"tasksHeading": "Görevler"
"hostPickerTitle": "Görevlerin Açılacağı Sunucu"
},
"listHeader": {
"agentTime": "Ajan süresi",
"agentsSpawned": "Başlatılan ajanlar",
"daysHours": "{{days}} g {{hours}} sa",
"desktopsHeading": "Masaüstleri",
"hoursMinutes": "{{hours}} sa {{minutes}} dk",
"minutes": "{{minutes}} dk",
"prsCreated": "Oluşturulan PR'lar",
"title": "Tekrar hoş geldiniz"
"desktopsHeading": "Masaüstleri"
},
"screen": {
"pairingCheckFailed": "Eşleştirme kontrol edilemedi",
@@ -1347,11 +1323,6 @@
"removeHostMessage": "“{{name}}” kaldırılsın mı? Daha sonra yeniden eşleştirebilirsiniz.",
"removeHostTitle": "Sunucuyu Kaldır",
"tryAgain": "Lütfen tekrar deneyin."
},
"tasksCard": {
"noSources": "Bağlı görev kaynağı yok",
"openProviderTasks": "{{provider}} görevlerini aç",
"title": "Görevler"
}
},
"hooks": {
+3 -32
View File
@@ -763,11 +763,6 @@
"moveUp": "上移",
"reorderHint": "使用「上移」和「下移」動作即可調整順序,無須拖曳"
},
"homeQuickActions": {
"newWorkspace": "新工作區",
"pairDesktop": "配對電腦",
"title": "快速操作"
},
"hostCard": {
"hostActions": "{{title}} 的操作",
"openHost": "開啟 {{title}}",
@@ -1298,17 +1293,10 @@
}
},
"home": {
"accountUsageCards": {
"sectionHeading": "帳戶用量",
"sessionWindow": "5 小時",
"systemDefault": "系統預設",
"weeklyWindow": "7 天"
},
"appUpdateCard": {
"dismissAccessibilityLabel": "關閉 Orca {{version}} 更新提示",
"title": "Orca {{version}} 已推出",
"updateAccessibilityLabel": "Orca {{version}} 已推出,點一下即可更新",
"updateHint": "點一下即可更新"
"updateAccessibilityLabel": "Orca {{version}} 已推出,點一下即可更新"
},
"emptyState": {
"connectedDescription": "您的電腦會顯示在這裡。所有資料皆採端對端加密。",
@@ -1316,26 +1304,14 @@
"description": "與電腦上的 Orca 配對,即可用手機查看代理程式狀態、開啟任何終端機,並推進工作。",
"openDesktopDescription": "前往「設定」→「行動版」,產生配對用的 QR Code。",
"openDesktopTitle": "開啟 Orca 桌面版",
"pairDesktop": "配對電腦",
"scanCodeDescription": "點一下上方按鈕開啟掃描器,對準電腦螢幕上的 QR Code。",
"scanCodeTitle": "掃描 QR Code",
"stepsHeading": "使用方式",
"title": "連線至您的電腦"
},
"listFooter": {
"hostPickerTitle": "選擇開啟任務的主機",
"resumeHeading": "繼續",
"tasksHeading": "任務"
"hostPickerTitle": "選擇開啟任務的主機"
},
"listHeader": {
"agentTime": "代理程式時間",
"agentsSpawned": "已啟動代理程式",
"daysHours": "{{days}} 天 {{hours}} 小時",
"desktopsHeading": "電腦",
"hoursMinutes": "{{hours}} 小時 {{minutes}} 分鐘",
"minutes": "{{minutes}} 分鐘",
"prsCreated": "已建立 PR",
"title": "歡迎回來"
"desktopsHeading": "電腦"
},
"screen": {
"pairingCheckFailed": "無法檢查配對狀態",
@@ -1343,11 +1319,6 @@
"removeHostMessage": "要移除「{{name}}」嗎?之後可重新配對。",
"removeHostTitle": "移除主機",
"tryAgain": "請再試一次。"
},
"tasksCard": {
"noSources": "尚未連結任務來源",
"openProviderTasks": "開啟 {{provider}} 任務",
"title": "任務"
}
},
"hooks": {
+3 -32
View File
@@ -763,11 +763,6 @@
"moveUp": "上移",
"reorderHint": "使用“上移”和“下移”操作调整顺序,无需拖动"
},
"homeQuickActions": {
"newWorkspace": "新工作区",
"pairDesktop": "配对桌面",
"title": "快捷操作"
},
"hostCard": {
"hostActions": "{{title}} 的操作",
"openHost": "打开 {{title}}",
@@ -1298,17 +1293,10 @@
}
},
"home": {
"accountUsageCards": {
"sectionHeading": "账户使用情况",
"sessionWindow": "5小时",
"systemDefault": "系统默认",
"weeklyWindow": "7天"
},
"appUpdateCard": {
"dismissAccessibilityLabel": "关闭 Orca {{version}} 更新提示",
"title": "Orca {{version}} 已可用",
"updateAccessibilityLabel": "Orca {{version}} 已可用,点按更新",
"updateHint": "点按更新"
"updateAccessibilityLabel": "Orca {{version}} 已可用,点按更新"
},
"emptyState": {
"connectedDescription": "桌面将显示在这里。所有数据均采用端到端加密。",
@@ -1316,26 +1304,14 @@
"description": "与电脑上的 Orca 配对,即可在手机上查看智能体、进入任意终端并推进工作。",
"openDesktopDescription": "前往“设置” → “移动端”,生成配对二维码。",
"openDesktopTitle": "打开 Orca 桌面应用",
"pairDesktop": "配对桌面",
"scanCodeDescription": "点按上方按钮打开扫码器,然后对准屏幕上的二维码。",
"scanCodeTitle": "扫描二维码",
"stepsHeading": "使用方法",
"title": "连接桌面"
},
"listFooter": {
"hostPickerTitle": "选择打开任务的主机",
"resumeHeading": "继续",
"tasksHeading": "任务"
"hostPickerTitle": "选择打开任务的主机"
},
"listHeader": {
"agentTime": "智能体时间",
"agentsSpawned": "已启动智能体",
"daysHours": "{{days}} 天 {{hours}} 小时",
"desktopsHeading": "台式机",
"hoursMinutes": "{{hours}} 小时 {{minutes}} 分钟",
"minutes": "{{minutes}} 分钟",
"prsCreated": "已创建 PR",
"title": "欢迎回来"
"desktopsHeading": "台式机"
},
"screen": {
"pairingCheckFailed": "无法检查配对状态",
@@ -1343,11 +1319,6 @@
"removeHostMessage": "要移除“{{name}}”吗?之后可重新配对。",
"removeHostTitle": "移除主机",
"tryAgain": "请重试。"
},
"tasksCard": {
"noSources": "未连接任务来源",
"openProviderTasks": "打开 {{provider}} 任务",
"title": "任务"
}
},
"hooks": {
-80
View File
@@ -1,80 +0,0 @@
import { describe, expect, it } from 'vitest'
import { totalHomeStats, type HomeStatsSummary } from './home-stats-total'
function stats(overrides: Partial<HomeStatsSummary> = {}): HomeStatsSummary {
return {
totalAgentsSpawned: 10,
totalPRsCreated: 2,
totalAgentTimeMs: 60_000,
firstEventAt: 1_700_000_000_000,
...overrides
}
}
describe('totalHomeStats', () => {
it('has nothing to show before any host answers', () => {
expect(totalHomeStats({}, ['host-1'])).toBeNull()
})
it('passes a single host through unchanged', () => {
expect(totalHomeStats({ 'host-1': stats() }, ['host-1'])).toEqual(stats())
})
it('sums every desktop instead of letting the last reply win', () => {
const total = totalHomeStats(
{
'host-1': stats(),
'host-2': stats({ totalAgentsSpawned: 5, totalPRsCreated: 1, totalAgentTimeMs: 30_000 })
},
['host-1', 'host-2']
)
expect(total).toMatchObject({
totalAgentsSpawned: 15,
totalPRsCreated: 3,
totalAgentTimeMs: 90_000
})
})
it('drops a removed desktop from the total', () => {
const byHost = {
'host-1': stats(),
'host-2': stats({ totalAgentsSpawned: 5, totalPRsCreated: 1, totalAgentTimeMs: 30_000 })
}
expect(totalHomeStats(byHost, ['host-1'])).toEqual(stats())
expect(totalHomeStats(byHost, [])).toBeNull()
})
it('keeps the earliest known first event', () => {
const total = totalHomeStats(
{
'host-1': stats({ firstEventAt: 2_000 }),
'host-2': stats({ firstEventAt: null }),
'host-3': stats({ firstEventAt: 1_000 })
},
['host-1', 'host-2', 'host-3']
)
expect(total?.firstEventAt).toBe(1_000)
})
it('reports no first event when no host has one', () => {
expect(
totalHomeStats({ 'host-1': stats({ firstEventAt: null }) }, ['host-1'])?.firstEventAt
).toBeNull()
})
it('ignores a malformed reply instead of poisoning the header', () => {
const total = totalHomeStats(
{
'host-1': stats(),
'host-2': null as unknown as HomeStatsSummary,
'host-3': { totalAgentsSpawned: 'lots' } as unknown as HomeStatsSummary
},
['host-1', 'host-2', 'host-3']
)
expect(total).toEqual(stats())
})
})
-46
View File
@@ -1,46 +0,0 @@
export type HomeStatsSummary = {
totalAgentsSpawned: number
totalPRsCreated: number
totalAgentTimeMs: number
firstEventAt: number | null
}
// Sum paired hosts only: process-lifetime cached replies outlive unpairing and a shared slot would show whichever host replied last.
// Wire rows and fields may be absent or malformed; consumer guards supply all defaults.
export type HomeStatsRow = Partial<HomeStatsSummary> | null | undefined
export function totalHomeStats(
byHost: Record<string, HomeStatsRow>,
hostIds: readonly string[]
): HomeStatsSummary | null {
const hosts = hostIds.filter((id) => id in byHost).map((id) => byHost[id])
if (hosts.length === 0) {
return null
}
const total: HomeStatsSummary = {
totalAgentsSpawned: 0,
totalPRsCreated: 0,
totalAgentTimeMs: 0,
firstEventAt: null
}
for (const host of hosts) {
// One malformed host reply must not crash or NaN the combined usage header.
if (!host || typeof host !== 'object') {
continue
}
total.totalAgentsSpawned += finiteOrZero(host.totalAgentsSpawned)
total.totalPRsCreated += finiteOrZero(host.totalPRsCreated)
total.totalAgentTimeMs += finiteOrZero(host.totalAgentTimeMs)
if (typeof host.firstEventAt === 'number' && Number.isFinite(host.firstEventAt)) {
total.firstEventAt =
total.firstEventAt === null
? host.firstEventAt
: Math.min(total.firstEventAt, host.firstEventAt)
}
}
return total
}
function finiteOrZero(value: number | undefined): number {
return typeof value === 'number' && Number.isFinite(value) ? value : 0
}
@@ -198,7 +198,7 @@ describe('what the pin still admits', () => {
expect(drift.join('\n')).toContain(
`identical: pinned ${tally.identical}, ran ${moved.identical}`
)
expect(drift.join('\n')).toContain('result-absent-settlement: pinned 342, ran 341')
expect(drift.join('\n')).toContain('result-absent-settlement: pinned 337, ran 336')
})
it('goes red on a class that grew and on the corpus losing a golden', () => {
@@ -116,13 +116,13 @@ export const BRIDGED_PARITY_EXCLUSIONS: Readonly<Partial<Record<BridgedParityCla
// Pin strict History verdicts and unchanged legacy counts in both directions.
export const BRIDGED_PARITY_BASELINE: Readonly<Record<BridgedParityClass | 'identical', number>> = {
identical: 394,
identical: 387,
'reply-meta-required': 0,
'result-absent-settlement': 342,
'result-absent-settlement': 337,
'result-absent-observation': 3,
'result-absent-stream-release': 6,
'params-undefined': 27,
'write-ordinal': 13,
'write-ordinal': 12,
unclassified: 0
}
@@ -175,8 +175,7 @@ export const BRIDGED_PARITY_MEMBERS: Readonly<
'matrix-live-worktree-name-runtime.clientevents.subscribe-2-1',
'matrix-live-worktree-name-worktree.show-1',
'matrix-live-worktree-name-worktree.show-2',
'matrix-live-worktree-name-worktree.show-3',
'settings-home-coalesced'
'matrix-live-worktree-name-worktree.show-3'
]
}
@@ -433,19 +433,19 @@ direct and relay transports.
The frame plumbing is method-agnostic, so what stops a site is its consumer rather than the runner.
Blur is unrecorded across all ten subscribing sites: `useFocusEffect` is substituted as `useEffect`,
so a route's focus cleanup is recorded at unmount and an unsubscribe only a blur would reach is not
— driving focus needs a substitute, and no recording reads one yet. Four of the nine probes pin
— driving focus needs a substitute, and no recording reads one yet. Three of the eight probes pin
behaviour with no demonstrated mutation — the two mixed reject/refusal new-tab orders
and the home-providers and resume-metadata refresh refusals; they are frozen observations, not
and the resume-metadata refresh refusal; they are frozen observations, not
proven defect detectors. `settings.resume-metadata` projects `{}` as its state, so its probe
observes only sender calls and settlements.
### Recorded finding: a refused refresh is not handled the same way twice
The five refuse-after-data probes record a `settings.get` read refusing a _refresh_ after a
success: home providers and task hydration read through `settingsRead`, workspace context, resume
metadata and repo metadata through `optionalSettingsRead`. Which one a site uses does not change
The four refuse-after-data probes record a `settings.get` read refusing a _refresh_ after a
success: task hydration reads through `settingsRead`, workspace context, resume metadata and repo
metadata through `optionalSettingsRead`. Which one a site uses does not change
what these probes record — the two share an acceptance and differ only in how they read a null
result, and a refusal never reaches the reader. Four call sites retain what they had.
result, and a refusal never reaches the reader. Three call sites retain what they had.
`use-mobile-tasks-runtime-hydration.tsx` does not: it publishes `{}`, so a refused refresh wipes
the runtime task settings. That divergence is recorded, not repaired —
`settings-task-hydration-refuse-after-data.json` is the observation, and changing the behaviour is
@@ -524,7 +524,7 @@ list is the count; a number in this paragraph would be one more thing that canno
for a session that is already live. Closing it needs an adapter that can attach a terminal
mid-scenario.
The original settings slice coverage maps nine host-RPC callers in
The original settings slice coverage maps eight host-RPC callers in
`settings-recording-coverage.json`; device-preference entries are excluded by coordinator
instruction. Later manifest additions require new scenarios and remain uncovered until
those recordings land. This runner does not certify native storage or transport skew.
@@ -1,44 +0,0 @@
import type { operationModuleLoader } from '../operation-module-loader'
import type { MountAdapter } from '../recording-scenario'
const HOST = 'host-1'
// Load the decoder through AccountUsage so recordings verify the screen module works under the mount loader.
export function homeAccountsMountAdapters(
modules: ReturnType<typeof operationModuleLoader>
): Record<string, MountAdapter> {
return {
'home.host-accounts': (context) => {
const fetchAccounts = modules.load<typeof import('../../../home/mobile-home-host-requests')>(
'mobile/src/home/mobile-home-host-requests.ts'
).fetchMobileHomeAccounts
let snapshots: Record<string, unknown> = {}
let disposed = false
return {
action(name) {
if (name === 'unmount') {
disposed = true
return
}
if (name !== 'load') {
throw new Error(`Unknown home accounts action: ${name}`)
}
return fetchAccounts(
context.client,
HOST,
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the recorder observes the published map as data, not as a decoded snapshot.
((update: (value: Record<string, unknown>) => Record<string, unknown>) => {
snapshots = update(snapshots)
context.effect('accounts', snapshots)
}) as Parameters<typeof fetchAccounts>[2],
() => disposed
)
},
state: () => ({ ...snapshots }),
dispose: () => {
disposed = true
}
}
}
}
}
@@ -4,8 +4,6 @@ import { hookMount, performHookAction } from '../hook-mount'
import { observableModel, projectObservable } from '../observable-model'
import type { operationModuleLoader } from '../operation-module-loader'
const HOST = 'host-1'
export function hostScreenMountAdapters(
modules: ReturnType<typeof operationModuleLoader>
): Record<string, MountAdapter> {
@@ -47,34 +45,6 @@ export function hostScreenMountAdapters(
state: () => projectObservable({ view: state.view }),
dispose: hook.unmount
}
},
'home.host-stats': (context) => {
const fetchStats = modules.load(
'mobile/src/home/mobile-home-host-requests.ts'
).fetchMobileHomeStats
let stats: Record<string, unknown> = {}
let disposed = false
return {
action(name) {
if (name === 'unmount') {
disposed = true
return
}
return fetchStats(
context.client,
HOST,
(update: (value: Record<string, unknown>) => Record<string, unknown>) => {
stats = update(stats)
context.effect('stats', stats)
},
() => disposed
)
},
state: () => ({ ...stats }),
dispose: () => {
disposed = true
}
}
}
}
}
@@ -23,7 +23,6 @@ import { fileInventoryMountAdapters } from './file-inventory-mount-adapters'
import { fileTapOpenMountAdapters } from './file-tap-open-mount-adapters'
import { fileRequestMountAdapters } from './file-request-mount-adapters'
import { githubPrMountAdapters } from './github-pr-mount-adapters'
import { homeAccountsMountAdapters } from './home-accounts-mount-adapters'
import { hostScreenMountAdapters } from './host-screen-mount-adapters'
import { hostWorktreeActionMountAdapters } from './host-worktree-action-mount-adapters'
import { hostedReviewMountAdapters } from './hosted-review-mount-adapters'
@@ -119,7 +118,6 @@ export const MOUNTED_OPERATION_MODULES: readonly MountedOperationModule[] = [
{ source: 'file-tap-open-mount-adapters.ts', mounts: fileTapOpenMountAdapters },
{ source: 'file-request-mount-adapters.ts', mounts: fileRequestMountAdapters },
{ source: 'github-pr-mount-adapters.ts', mounts: githubPrMountAdapters },
{ source: 'home-accounts-mount-adapters.ts', mounts: homeAccountsMountAdapters },
{ source: 'host-screen-mount-adapters.ts', mounts: hostScreenMountAdapters },
{
source: 'host-worktree-action-mount-adapters.ts',
@@ -76,34 +76,6 @@ export function settingsMountAdapters(
dispose: hook.unmount
}
},
'settings.home-providers': (context) => {
const load = modules.load(
'mobile/src/home/mobile-home-host-requests.ts'
).fetchMobileHomeTaskProviders
let providers: unknown = {}
let disposed = false
return {
action(name) {
if (name === 'unmount') {
disposed = true
return
}
load(
context.client,
'host-1',
(update: (value: unknown) => unknown) => {
providers = update(providers)
context.effect('providers', providers)
},
() => disposed
)
},
state: () => providers,
dispose: () => {
disposed = true
}
}
},
'settings.resume-metadata': ({ client }) => {
const load = modules.load<typeof ResumeMetadata>(
'mobile/src/agent-history/mobile-agent-history-resume-metadata.ts'
@@ -25,7 +25,6 @@ export type DerivedGolden = {
const SIBLING_SCHEDULE_BASES = [
'b3',
'settings-new-tab-ssh',
'settings-home-providers-fulfilled',
'settings-workspace-context-fulfilled',
'settings-resume-metadata-fulfilled',
'settings-task-hydration-fulfilled',
@@ -53,13 +53,6 @@ export const OPERATION_MUTATIONS = {
return response
}),`
},
// Decodes the reply envelope instead of the accepted snapshot, so the Home card publishes nothing
// where a host answered.
'home-accounts-envelope': {
file: 'mobile-home-host-requests.ts',
before: 'const snapshot = decodeAccountsSnapshot(accounts.value)',
after: 'const snapshot = decodeAccountsSnapshot(reply)'
},
// Offset-zero chunk writes preserve length; digest and decoded-byte observations must detect corruption.
'mobile-web-bundle-chunk-placement': {
file: 'mobile-web-bundle-fetch.ts',
@@ -110,12 +103,6 @@ export const OPERATION_MUTATIONS = {
after:
'(settingsRes.value.result as NewWorktreeRuntimeSettings & { visibleTaskProviders?: unknown })'
},
// Treats any successful linear.status reply as a connected Linear account.
'home-providers-linear': {
file: 'mobile-home-host-requests.ts',
before: 'linearConnected: linear?.connected === true',
after: 'linearConnected: linear !== null'
},
// Read the wrong host-platform field so rows display an unreported platform.
'repo-metadata-platform': {
file: 'host-screen-reply-schema.ts',
@@ -24,14 +24,12 @@ const mutants: Record<string, Mutation> = {
b3: 'order',
'settings-bot-overrides-fulfilled': 'bot-overrides-envelope',
'settings-workspace-context-fulfilled': 'workspace-context-envelope',
'settings-home-providers-fulfilled': 'home-providers-linear',
'settings-repo-metadata-fulfilled': 'repo-metadata-platform',
'settings-task-hydration-fulfilled': 'task-hydration-envelope',
'settings-task-write': 'task-preferences-optimistic',
'settings-workspace-submit-fulfilled': 'workspace-submit-envelope',
'settings-task-workspace-fulfilled': 'task-workspace-envelope',
'native-chat-write-delivery-unknown': 'native-chat-send-delivery-unknown',
'home-host-accounts': 'home-accounts-envelope',
'notifications-display-test-accepted': 'push-test-envelope',
'tasks-route-repo-list': 'task-screen-repo-envelope',
'linear-select-workspace': 'linear-workspace-context-reload',
@@ -103,6 +103,7 @@ describe('fetchHomeHostWorktreeInfo', () => {
totalWorktrees: 12,
activeCount: 2,
lastActiveWorktree: expect.objectContaining({ worktreeId: 'wt-0' }),
attention: expect.any(Array),
catalogUnavailable: true,
staleCounts: true,
countsProvenAt: expect.any(Number)
@@ -6,6 +6,7 @@ import {
type HomeWorktreeSummary,
type HostWorktreeInfo
} from './home-worktree-info'
import { selectHomeAttentionWorktrees } from './home-jump-back-in'
import { pickResumeWorktree } from './resume-worktree'
import { isWorktreeActive } from './workspace-list-sections'
import { worktreeCatalogRead } from './worktree-catalog-operations'
@@ -44,7 +45,7 @@ export function fetchHomeHostWorktreeInfo(
markUnavailable()
return
}
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: `worktrees` is a salvaged member, so the `?? []` is what makes it an array; the rows stay opaque because three screens project a row differently. This card reads `status` and the resume pick, both through their own guards, and the `worktree-home-catalog` golden records the row it is given as `{worktreeId, displayName, repo, status}`.
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: `worktrees` is a salvaged member, so the `?? []` is what makes it an array; the rows stay opaque because three screens project a row differently. Home reads `status`, `sidebarStatus` and the resume pick, both through their own guards, and the `worktree-home-catalog` golden records the row it is given as `{worktreeId, displayName, repo, status}`.
const worktrees = (catalog.value.worktrees ?? []) as HomeWorktreeSummary[]
setCachedWorktrees(hostId, worktrees, { proven: true })
// Count what the list shows: archived rows are hidden, and active is the sleeping filter's rule.
@@ -58,6 +59,7 @@ export function fetchHomeHostWorktreeInfo(
totalWorktrees: listed.length,
activeCount: listed.filter(isWorktreeActive).length,
lastActiveWorktree: lastActive,
attention: selectHomeAttentionWorktrees(listed),
countsProvenAt: Date.now()
}
}))
@@ -0,0 +1,86 @@
import { describe, expect, it } from 'vitest'
import { selectHomeAttentionWorktrees, selectHomeJumpBackInRows } from './home-jump-back-in'
import type { HomeWorktreeSummary, HostWorktreeInfo } from './home-worktree-info'
const NOW = 1_000_000_000
function row(id: string, fields: Partial<HomeWorktreeSummary>): HomeWorktreeSummary {
return {
worktreeId: id,
repo: 'orca',
branch: id,
displayName: id,
liveTerminalCount: 1,
...fields
}
}
function info(hostId: string, rows: HomeWorktreeSummary[]): HostWorktreeInfo {
return {
hostId,
totalWorktrees: rows.length,
activeCount: 0,
lastActiveWorktree: null,
attention: selectHomeAttentionWorktrees(rows),
countsProvenAt: NOW
}
}
describe('home jump back in', () => {
it('puts agents waiting on the user first, then unseen finished work, then running work', () => {
const rows = selectHomeAttentionWorktrees([
row('running', { sidebarStatus: 'working', lastOutputAt: NOW }),
row('finished-seen', { sidebarStatus: 'done', unread: false }),
row('finished-unseen', { sidebarStatus: 'done', unread: true, lastOutputAt: NOW - 5 }),
row('blocked', { sidebarStatus: 'permission', lastOutputAt: NOW - 10 }),
row('archived', { sidebarStatus: 'permission', isArchived: true }),
row('idle', { status: 'inactive', hasHostSidebarActivity: false })
])
expect(rows.map((worktree) => worktree.worktreeId)).toEqual([
'blocked',
'finished-unseen',
'running'
])
})
it('claims no agent state for a desktop that is offline or whose last read failed', () => {
const blocked = [row('blocked', { sidebarStatus: 'permission' })]
const rows = selectHomeJumpBackInRows({
hosts: [{ id: 'live' }, { id: 'offline' }, { id: 'failed' }],
hostStates: { live: 'connected', offline: 'disconnected', failed: 'connected' },
worktreeInfo: {
live: info('live', blocked),
offline: info('offline', blocked),
failed: { ...info('failed', blocked), catalogUnavailable: true }
},
resumeCard: null
})
expect(rows.map((entry) => entry.hostId)).toEqual(['live'])
})
it('adds the last-opened workspace after the agents, unless it is already listed', () => {
const resumeWorktree = row('resume', {})
const base = {
hosts: [{ id: 'host' }],
hostStates: { host: 'connected' as const }
}
const appended = selectHomeJumpBackInRows({
...base,
worktreeInfo: { host: info('host', [row('blocked', { sidebarStatus: 'permission' })]) },
resumeCard: { hostId: 'host', worktree: resumeWorktree, actionable: true }
})
expect(appended.map((entry) => [entry.worktree.worktreeId, entry.lastOpened])).toEqual([
['blocked', false],
['resume', true]
])
const deduped = selectHomeJumpBackInRows({
...base,
worktreeInfo: { host: info('host', [row('resume', { sidebarStatus: 'working' })]) },
resumeCard: { hostId: 'host', worktree: resumeWorktree, actionable: true }
})
expect(deduped.map((entry) => [entry.worktree.worktreeId, entry.lastOpened])).toEqual([
['resume', false]
])
})
})
+125
View File
@@ -0,0 +1,125 @@
import type { WorktreeStatus } from '../../../src/shared/worktree-status-rollup'
import type { ConnectionState } from '../transport/types'
import type { HomeResumeCard } from './home-resume-card'
import type {
HomeAttentionWorktree,
HomeWorktreeSummary,
HostWorktreeInfo
} from './home-worktree-info'
import { getWorktreeDotStatus } from './workspace-list-ordering'
const ATTENTION_ROWS_PER_HOST = 6
const ATTENTION_ROWS_SHOWN = 3
// Lower comes first: agents blocked on the user, then finished work they haven't seen, then running work.
function attentionRank(dotStatus: WorktreeStatus, unread: boolean): number | null {
if (dotStatus === 'permission' || dotStatus === 'failed') {
return 0
}
if (dotStatus === 'done' && unread) {
return 1
}
if (dotStatus === 'working' || dotStatus === 'monitoring') {
return 2
}
return null
}
function byRankThenRecent(
a: { rank: number; lastOutputAt?: number },
b: { rank: number; lastOutputAt?: number }
): number {
return a.rank - b.rank || (b.lastOutputAt ?? 0) - (a.lastOutputAt ?? 0)
}
/** The rows of one desktop's list that "Jump back in" may show, using the workspace list's dot status. */
export function selectHomeAttentionWorktrees(
rows: readonly HomeWorktreeSummary[]
): HomeAttentionWorktree[] {
const ranked: (HomeAttentionWorktree & { rank: number })[] = []
for (const row of rows) {
if (row.isArchived === true) {
continue
}
const dotStatus = getWorktreeDotStatus(row)
const rank = attentionRank(dotStatus, row.unread === true)
if (rank === null) {
continue
}
ranked.push({
rank,
dotStatus,
worktreeId: row.worktreeId,
repo: row.repo,
branch: row.branch,
displayName: row.displayName,
lastOutputAt: typeof row.lastOutputAt === 'number' ? row.lastOutputAt : undefined,
workingMode: row.workingMode
})
}
return ranked
.sort(byRankThenRecent)
.slice(0, ATTENTION_ROWS_PER_HOST)
.map(({ rank: _rank, ...worktree }) => worktree)
}
export type HomeJumpBackInRow = Readonly<{
hostId: string
worktree: HomeAttentionWorktree
/** The resume target rather than an agent that needs the user. */
lastOpened: boolean
actionable: boolean
}>
export function selectHomeJumpBackInRows(input: {
hosts: readonly { id: string }[]
hostStates: Readonly<Record<string, ConnectionState>>
worktreeInfo: Readonly<Record<string, HostWorktreeInfo>>
resumeCard: HomeResumeCard | null
}): HomeJumpBackInRow[] {
const candidates: (HomeJumpBackInRow & { rank: number; lastOutputAt?: number })[] = []
for (const host of input.hosts) {
const info = input.worktreeInfo[host.id]
// Agent states are shown only from a connected desktop's list read this session.
if (input.hostStates[host.id] !== 'connected' || !info?.attention || info.catalogUnavailable) {
continue
}
for (const worktree of info.attention) {
candidates.push({
hostId: host.id,
worktree,
lastOpened: false,
actionable: true,
rank: attentionRank(worktree.dotStatus, true) ?? 2,
lastOutputAt: worktree.lastOutputAt
})
}
}
const rows: HomeJumpBackInRow[] = candidates
.sort(byRankThenRecent)
.slice(0, ATTENTION_ROWS_SHOWN)
.map(({ rank: _rank, lastOutputAt: _lastOutputAt, ...row }) => row)
const resume = input.resumeCard
if (
resume &&
!rows.some(
(row) =>
row.hostId === resume.hostId && row.worktree.worktreeId === resume.worktree.worktreeId
)
) {
rows.push({
hostId: resume.hostId,
worktree: {
worktreeId: resume.worktree.worktreeId,
repo: resume.worktree.repo,
branch: resume.worktree.branch,
displayName: resume.worktree.displayName,
lastOutputAt: resume.worktree.lastOutputAt,
dotStatus: 'inactive'
},
lastOpened: true,
actionable: resume.actionable
})
}
return rows
}
+9 -3
View File
@@ -59,13 +59,19 @@ function hostHistoryCard(
// Only a proven catalog can establish deletion; cold/loading snapshots leave navigation to the session’s later check.
export function isResumeTargetConfirmedMissing(
card: HomeResumeCard,
provenWorktrees: readonly { worktreeId: string }[] | null
card: Readonly<{ worktree: Readonly<{ worktreeId: string }> }>,
provenWorktrees: readonly unknown[] | null
): boolean {
if (!provenWorktrees || isSyntheticWorkspaceRoute(card.worktree.worktreeId)) {
return false
}
return !provenWorktrees.some((worktree) => worktree.worktreeId === card.worktree.worktreeId)
return !provenWorktrees.some(
(worktree) =>
typeof worktree === 'object' &&
worktree !== null &&
'worktreeId' in worktree &&
worktree.worktreeId === card.worktree.worktreeId
)
}
export function selectHomeResumeCard(input: HomeResumeCardInput): HomeResumeCard | null {
+21
View File
@@ -1,3 +1,6 @@
import type { AgentWorkingMode } from '../../../src/shared/agent-status-types'
import type { WorktreeStatus } from '../../../src/shared/worktree-status-rollup'
export type HomeWorktreeSummary = {
worktreeId: string
repo: string
@@ -10,13 +13,22 @@ export type HomeWorktreeSummary = {
isArchived?: boolean
isActive?: boolean
lastOutputAt?: number
sidebarStatus?: string
workingMode?: AgentWorkingMode
}
/** A workspace whose agents Home surfaces under "Jump back in", with the list's dot status. */
export type HomeAttentionWorktree = Pick<
HomeWorktreeSummary,
'worktreeId' | 'repo' | 'branch' | 'displayName' | 'lastOutputAt' | 'workingMode'
> & { dotStatus: WorktreeStatus }
export type HostWorktreeInfo = {
hostId: string
totalWorktrees: number
activeCount: number
lastActiveWorktree: HomeWorktreeSummary | null
attention?: HomeAttentionWorktree[]
catalogUnavailable?: boolean
// The counts are the last proven ones, kept across a failed refresh.
staleCounts?: boolean
@@ -27,6 +39,15 @@ export type HostWorktreeInfo = {
// Persisted counts may be days old; bound their live label without discarding history.
export const HOME_WORKTREE_COUNTS_LIVE_MAX_AGE_MS = 10 * 60_000
/** Agent states are live facts; a saved copy or a dropped connection can't vouch for them. */
export function withoutHomeAgentStates(info: HostWorktreeInfo): HostWorktreeInfo {
if (!info.attention) {
return info
}
const { attention: _attention, ...rest } = info
return rest
}
export function markHomeWorktreeCatalogUnavailable(
current: HostWorktreeInfo | undefined,
hostId: string
@@ -90,8 +90,13 @@ export function sortWorktrees(
)
}
type WorktreeStatusInputs = Pick<
Worktree,
'hasHostSidebarActivity' | 'status' | 'liveTerminalCount' | 'sidebarStatus'
>
export function getWorktreeStatus(
w: Worktree
w: WorktreeStatusInputs
): 'working' | 'active' | 'permission' | 'done' | 'inactive' {
// Match desktop sidebar activity; retained/background PTYs alone must not imply an active workspace.
if (w.hasHostSidebarActivity === false) {
@@ -127,6 +132,6 @@ function isHostVerdictDotStatus(value: string | undefined): value is WorktreeSta
return value !== undefined && HOST_VERDICT_DOT_STATUSES.has(value)
}
export function getWorktreeDotStatus(w: Worktree): WorktreeStatus {
export function getWorktreeDotStatus(w: WorktreeStatusInputs): WorktreeStatus {
return isHostVerdictDotStatus(w.sidebarStatus) ? w.sidebarStatus : getWorktreeStatus(w)
}
+135 -311
View File
@@ -48,6 +48,7 @@
--m-status-green: #22c55e;
--m-status-amber: #f59e0b;
--m-status-red: #ef4444;
--m-agent-done: #10b981;
--m-mono: 'Menlo', 'SF Mono', monospace;
position: relative;
@@ -1084,6 +1085,7 @@
/* Paint containment prevents transformed slides leaking past Chromium’s rounded overflow clipping. */
contain: paint;
isolation: isolate;
container: mp-phone-screen / inline-size;
}
.mobile-page-root .mp-screen-slide {
@@ -1191,115 +1193,122 @@
padding: 0 16px 16px;
}
.mobile-page-root .mp-greeting {
padding: 6px 4px 10px;
}
.mobile-page-root .mp-greeting-title {
.mobile-page-root .mp-home-heading {
margin: 20px 0 7px;
padding: 0 4px;
color: var(--m-text-primary);
font-size: 22px;
font-weight: 800;
letter-spacing: 0;
}
.mobile-page-root .mp-stat-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
margin-bottom: 16px;
}
.mobile-page-root .mp-stat-card {
border: 1px solid var(--m-border);
border-radius: 10px;
background: rgba(26, 26, 26, 0.6);
padding: 8px 10px;
}
.mobile-page-root .mp-stat-value {
color: var(--m-text-primary);
font-size: 17px;
font-weight: 700;
letter-spacing: 0;
}
.mobile-page-root .mp-stat-label {
margin-top: 1px;
color: var(--m-text-muted);
font-size: 10px;
font-weight: 500;
white-space: nowrap;
}
.mobile-page-root .mp-section-label {
margin: 0 0 6px;
padding-left: 4px;
color: var(--m-text-muted);
font-size: 10px;
font-size: 13px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.mobile-page-root .mp-host-card {
display: flex;
align-items: center;
min-height: 60px;
padding: 10px 12px;
gap: 12px;
border: 1px solid var(--m-border);
border-radius: 14px;
.mobile-page-root .mp-home-heading.is-first {
margin-top: 4px;
}
.mobile-page-root .mp-home-group {
overflow: hidden;
border-radius: 16px;
background: var(--m-bg-panel);
}
.mobile-page-root .mp-host-card + .mp-host-card {
margin-top: 6px;
.mobile-page-root .mp-home-row {
position: relative;
display: flex;
align-items: flex-start;
padding-left: 14px;
}
.mobile-page-root .mp-host-icon {
display: grid;
width: 38px;
height: 38px;
place-items: center;
border-radius: 11px;
background: var(--m-bg-raised);
color: var(--m-text-primary);
.mobile-page-root .mp-home-row + .mp-home-row::before,
.mobile-page-root .mp-home-row + .mp-home-action-row::before {
content: '';
position: absolute;
top: 0;
right: 0;
left: 34px;
border-top: 1px solid var(--m-border);
}
.mobile-page-root .mp-host-icon svg {
width: 18px;
.mobile-page-root .mp-home-row + .mp-home-action-row::before {
left: 14px;
}
/* Status column; separators inset to the text after it. */
.mobile-page-root .mp-home-status {
display: flex;
width: 20px;
height: 18px;
stroke-width: 1.75;
margin-top: 10px;
align-items: center;
flex: 0 0 auto;
}
.mobile-page-root .mp-host-icon.is-dim {
color: var(--m-text-secondary);
}
.mobile-page-root .mp-host-main {
.mobile-page-root .mp-home-text {
flex: 1;
min-width: 0;
padding: 10px 0;
}
.mobile-page-root .mp-host-name {
.mobile-page-root .mp-home-title,
.mobile-page-root .mp-home-subtitle {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.mobile-page-root .mp-home-title {
color: var(--m-text-primary);
font-size: 14px;
font-weight: 600;
line-height: 18px;
}
.mobile-page-root .mp-host-name.is-dim {
.mobile-page-root .mp-home-subtitle {
margin-top: 1px;
color: var(--m-text-secondary);
font-size: 12px;
line-height: 16px;
}
.mobile-page-root .mp-host-meta {
.mobile-page-root .mp-home-subtitle .is-waiting {
color: var(--m-status-red);
}
.mobile-page-root .mp-home-subtitle .is-done {
color: var(--m-agent-done);
}
.mobile-page-root .mp-home-trailing {
display: flex;
height: 18px;
margin-top: 10px;
padding: 0 10px 0 8px;
align-items: center;
gap: 6px;
margin-top: 2px;
color: var(--m-text-secondary);
font-size: 11px;
white-space: nowrap;
gap: 4px;
color: var(--m-text-muted);
font-size: 12px;
font-variant-numeric: tabular-nums;
}
.mobile-page-root .mp-home-trailing svg {
width: 14px;
height: 14px;
stroke-width: 2;
}
.mobile-page-root .mp-home-action-row {
position: relative;
display: flex;
min-height: 42px;
padding: 0 14px;
align-items: center;
gap: 8px;
color: var(--m-accent-blue);
font-size: 14px;
}
.mobile-page-root .mp-home-action-row svg {
width: 16px;
height: 16px;
stroke-width: 2.2;
}
.mobile-page-root .mp-status-dot {
@@ -1321,69 +1330,6 @@
background: var(--m-text-muted);
}
.mobile-page-root .mp-chevron-right {
color: var(--m-text-muted);
}
.mobile-page-root .mp-chevron-right svg {
width: 16px;
height: 16px;
stroke-width: 1.75;
}
.mobile-page-root .mp-resume-card {
display: flex;
align-items: center;
padding: 10px 12px;
gap: 12px;
border: 1px solid var(--m-border);
border-radius: 14px;
background: var(--m-bg-panel);
}
.mobile-page-root .mp-resume-icon {
display: grid;
width: 38px;
height: 38px;
place-items: center;
border-radius: 11px;
background: var(--m-bg-raised);
color: var(--m-text-secondary);
}
.mobile-page-root .mp-resume-icon svg {
width: 16px;
height: 16px;
stroke-width: 1.75;
}
.mobile-page-root .mp-resume-title {
color: var(--m-text-primary);
font-size: 13px;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.mobile-page-root .mp-resume-sub {
display: flex;
align-items: center;
gap: 6px;
margin-top: 2px;
color: var(--m-text-secondary);
font-size: 11px;
min-width: 0;
}
/* Why: keep long repo/branch labels inside the phone frame (matches RN numberOfLines={1}). */
.mobile-page-root .mp-resume-sub > span:not(.mp-repo-dot) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
.mobile-page-root .mp-repo-dot {
width: 7px;
height: 7px;
@@ -1391,183 +1337,61 @@
flex: 0 0 auto;
}
.mobile-page-root .mp-quick-actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-bottom: 8px;
}
.mobile-page-root .mp-quick-action {
/* Pinned like the phone's bottom bar; the bright fill marks the primary action. */
.mobile-page-root .mp-home-bottom-bar {
display: flex;
gap: 8px;
padding: 8px 12px 22px;
border-top: 1px solid var(--m-border);
background: var(--m-bg-base);
}
.mobile-page-root .mp-home-bar-button {
display: flex;
min-height: 40px;
flex: 1;
align-items: center;
gap: 10px;
padding: 10px 12px;
border: 1px solid var(--m-border);
border-radius: 14px;
background: var(--m-bg-panel);
}
.mobile-page-root .mp-quick-action-icon {
display: grid;
width: 28px;
height: 28px;
place-items: center;
border-radius: 9px;
background: rgba(255, 255, 255, 0.04);
color: var(--m-text-secondary);
}
.mobile-page-root .mp-quick-action-icon svg {
width: 16px;
height: 16px;
stroke-width: 1.75;
}
.mobile-page-root .mp-quick-action-label {
color: var(--m-text-secondary);
font-size: 12px;
justify-content: center;
min-width: 0;
padding: 0 8px;
gap: 5px;
border-radius: 12px;
font-size: 13px;
font-weight: 600;
white-space: nowrap;
}
.mobile-page-root .mp-task-home-card {
display: flex;
align-items: center;
padding: 10px 12px;
gap: 12px;
border: 1px solid var(--m-border);
border-radius: 14px;
background: var(--m-bg-panel);
.mobile-page-root .mp-home-bar-button svg {
width: 15px;
height: 15px;
stroke-width: 2.2;
}
.mobile-page-root .mp-task-home-icon {
display: grid;
width: 38px;
height: 38px;
place-items: center;
border-radius: 11px;
background: var(--m-bg-raised);
color: var(--m-text-secondary);
}
.mobile-page-root .mp-task-home-icon svg {
width: 16px;
height: 16px;
stroke-width: 1.75;
}
.mobile-page-root .mp-task-home-title {
color: var(--m-text-primary);
font-size: 13px;
font-weight: 600;
}
.mobile-page-root .mp-task-home-subtitle {
margin-top: 2px;
color: var(--m-text-secondary);
font-size: 11px;
}
.mobile-page-root .mp-task-home-providers {
display: flex;
align-items: center;
gap: 2px;
margin-left: 8px;
}
.mobile-page-root .mp-task-home-provider-button {
display: grid;
width: 34px;
height: 34px;
place-items: center;
border-radius: 6px;
color: var(--m-text-secondary);
}
.mobile-page-root .mp-task-home-provider-button svg {
width: 22px;
height: 22px;
stroke-width: 1.75;
}
.mobile-page-root .mp-accounts-card {
background: var(--m-bg-panel);
border: 1px solid var(--m-border);
border-radius: 14px;
padding: 10px 12px;
display: flex;
flex-direction: column;
gap: 8px;
}
.mobile-page-root .mp-accounts-row {
display: flex;
align-items: center;
gap: 10px;
}
.mobile-page-root .mp-accounts-icon {
display: grid;
width: 32px;
height: 32px;
place-items: center;
border-radius: 9px;
background: var(--m-bg-raised);
color: var(--m-text-primary);
}
.mobile-page-root .mp-accounts-icon svg {
width: 18px;
height: 18px;
}
.mobile-page-root .mp-accounts-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.mobile-page-root .mp-accounts-email {
color: var(--m-text-primary);
font-size: 13px;
font-weight: 600;
}
.mobile-page-root .mp-accounts-bars {
display: flex;
gap: 12px;
margin-top: 4px;
}
.mobile-page-root .mp-usage-bar {
flex: 1;
display: flex;
align-items: center;
gap: 6px;
}
.mobile-page-root .mp-usage-bar-label {
color: var(--m-text-muted);
font-size: 11px;
font-weight: 600;
min-width: 16px;
}
.mobile-page-root .mp-usage-bar-track {
flex: 1;
height: 4px;
border-radius: 999px;
background: var(--m-bg-raised);
.mobile-page-root .mp-home-bar-button > span {
overflow: hidden;
text-overflow: ellipsis;
}
.mobile-page-root .mp-usage-bar-fill {
height: 100%;
background: var(--m-text-secondary);
border-radius: inherit;
/* The narrow-layout frame can't fit icon and label in half its width. */
@container mp-phone-screen (max-width: 320px) {
.mobile-page-root .mp-home-bar-button {
padding: 0 4px;
font-size: 12px;
}
.mobile-page-root .mp-home-bar-button svg {
display: none;
}
}
.mobile-page-root .mp-home-bar-button.is-secondary {
background: var(--m-bg-raised);
color: var(--m-text-primary);
}
.mobile-page-root .mp-home-bar-button.is-primary {
background: var(--m-text-primary);
color: var(--m-bg-base);
}
.mobile-page-root .mp-wl-chrome {
@@ -1730,7 +1554,7 @@
}
.mobile-page-root .mp-wl-dot.is-green {
background: #10b981;
background: var(--m-agent-done);
}
.mobile-page-root .mp-wl-dot.is-muted {
@@ -0,0 +1,19 @@
import { defineFixture } from '../../../component-fixtures/component-fixture'
import { HomeSlide } from './HomeSlide'
function PhoneFrame({ width }: { width: number }): React.JSX.Element {
return (
<div className="mobile-page-root" style={{ height: 'auto', overflow: 'visible' }}>
<div className="mp-phone-frame" style={{ width }}>
<div className="mp-phone-screen">
<HomeSlide tapping={false} />
</div>
</div>
</div>
)
}
// The carousel's largest frame and the narrow-layout frame.
export const large = defineFixture({ width: 440, render: () => <PhoneFrame width={420} /> })
export const narrow = defineFixture({ width: 310, render: () => <PhoneFrame width={290} /> })
@@ -1,8 +1,30 @@
import { ClaudeIcon, OpenAIIcon } from '../../status-bar/icons'
import { cn } from '../../../lib/utils'
import { translate } from '@/i18n/i18n'
type AgentIndicator = 'waiting' | 'done' | 'working' | 'idle'
type JumpBackInRowProps = {
indicator: AgentIndicator
name: string
status: string
desktop: string
time: string
}
const AGENT_DOT: Record<Exclude<AgentIndicator, 'working'>, string> = {
waiting: 'is-red',
done: 'is-green',
idle: 'is-muted'
}
// Waiting and done ask something of the user, so only they tint the status word (mobile parity).
const STATUS_TONE: Partial<Record<AgentIndicator, string>> = {
waiting: 'is-waiting',
done: 'is-done'
}
export function HomeSlide({ tapping }: { tapping: boolean }): React.JSX.Element {
const macbookPro = translate('auto.components.mobile.slides.HomeSlide.19c212e25e', 'MacBook Pro')
return (
<div className="mp-device-screen">
<div className="mp-app-topbar">
@@ -22,226 +44,129 @@ export function HomeSlide({ tapping }: { tapping: boolean }): React.JSX.Element
</div>
<div className="mp-scroll-region">
<div className="mp-greeting">
<div className="mp-greeting-title">
{translate('auto.components.mobile.slides.HomeSlide.c0e2e9dcd9', 'Welcome back')}
</div>
<div className="mp-home-heading is-first">
{translate('auto.components.mobile.slides.HomeSlide.dc1f3f5535', 'Jump back in')}
</div>
<div className="mp-stat-row">
<Stat
value="1,284"
label={translate(
'auto.components.mobile.slides.HomeSlide.00a6903322',
'Agents spawned'
<div className="mp-home-group">
<JumpBackInRow
indicator="waiting"
name="fix-auth-redirect"
status={translate(
'auto.components.mobile.slides.HomeSlide.c2a0215163',
'Waiting for input'
)}
desktop={macbookPro}
time={translate('auto.components.mobile.slides.HomeSlide.0c14dac908', '2m')}
/>
<Stat
value="142h"
label={translate('auto.components.mobile.slides.HomeSlide.4a40af029b', 'Agent time')}
<JumpBackInRow
indicator="done"
name="tasks-sheet-polish"
status={translate('auto.components.mobile.slides.HomeSlide.77c0dae863', 'Done')}
desktop={translate('auto.components.mobile.slides.HomeSlide.b5308d5e2c', 'M1 Mini')}
time={translate('auto.components.mobile.slides.HomeSlide.3166f865af', '14m')}
/>
<Stat
value="96"
label={translate('auto.components.mobile.slides.HomeSlide.156db8a68a', 'PRs created')}
<JumpBackInRow
indicator="working"
name="relay-reconnect-backoff"
status={translate('auto.components.mobile.slides.HomeSlide.609517273b', 'Working')}
desktop={macbookPro}
time={translate('auto.components.mobile.slides.HomeSlide.005afbad3d', 'now')}
/>
<JumpBackInRow
indicator="idle"
name="feat/mobile-page"
status={translate('auto.components.mobile.slides.HomeSlide.e986b282af', 'Last opened')}
desktop={macbookPro}
time={translate('auto.components.mobile.slides.HomeSlide.8a3ebdbdac', '1h')}
/>
</div>
<div className="mp-section-label">
<div className="mp-home-heading">
{translate('auto.components.mobile.slides.HomeSlide.2f1a1d10c4', 'Desktops')}
</div>
<div className={cn('mp-host-card', tapping && 'is-tapping')}>
<div className="mp-host-icon">
<DesktopIcon />
</div>
<div className="mp-host-main">
<div className="mp-host-name">
{translate('auto.components.mobile.slides.HomeSlide.19c212e25e', 'MacBook Pro')}
</div>
<div className="mp-host-meta">
<div className="mp-home-group">
{/* The carousel's next slide is this desktop's worktree list, so the tap lands here. */}
<div className={cn('mp-home-row', tapping && 'is-tapping')}>
<div className="mp-home-status">
<span className="mp-status-dot is-green" />
<span>
</div>
<div className="mp-home-text">
<div className="mp-home-title">{macbookPro}</div>
<div className="mp-home-subtitle">
{translate(
'auto.components.mobile.slides.HomeSlide.0bc1881bc4',
'Connected · 40 worktrees · 5 active'
'auto.components.mobile.slides.HomeSlide.04a94cadee',
'40 worktrees · 5 active'
)}
</span>
</div>
</div>
<div className="mp-home-trailing">
<ChevronIcon />
</div>
</div>
<div className="mp-chevron-right">
<ChevronIcon />
</div>
</div>
<div className="mp-host-card">
<div className="mp-host-icon is-dim">
<DesktopIcon />
</div>
<div className="mp-host-main">
<div className="mp-host-name is-dim">
{translate('auto.components.mobile.slides.HomeSlide.091355da3d', 'M1 Mini · home')}
</div>
<div className="mp-host-meta">
<div className="mp-home-row">
<div className="mp-home-status">
<span className="mp-status-dot is-muted" />
<span>
</div>
<div className="mp-home-text">
<div className="mp-home-title">
{translate('auto.components.mobile.slides.HomeSlide.b5308d5e2c', 'M1 Mini')}
</div>
<div className="mp-home-subtitle">
{translate('auto.components.mobile.slides.HomeSlide.cf3f98fa3f', 'Disconnected')}
</span>
</div>
</div>
<div className="mp-home-trailing">
<ChevronIcon />
</div>
</div>
<div className="mp-chevron-right">
<ChevronIcon />
<div className="mp-home-action-row">
<PlusIcon />
{translate('auto.components.mobile.slides.HomeSlide.aefd110ea5', 'Pair a desktop')}
</div>
</div>
</div>
<div className="mp-section-label" style={{ marginTop: 14 }}>
{translate('auto.components.mobile.slides.HomeSlide.c791677f2f', 'Resume')}
<div className="mp-home-bottom-bar">
<div className="mp-home-bar-button is-secondary">
<ListTodoIcon />
<span>{translate('auto.components.mobile.slides.HomeSlide.a4c3f7b7aa', 'Tasks')}</span>
</div>
<div className="mp-resume-card">
<div className="mp-resume-icon">
<ResumeIcon />
</div>
<div className="mp-host-main">
<div className="mp-resume-title">
{translate('auto.components.mobile.slides.HomeSlide.25d6e8a491', 'feat/mobile-page')}
</div>
<div className="mp-resume-sub">
<span className="mp-repo-dot" style={{ background: '#3b82f6' }} />
<span>
{translate(
'auto.components.mobile.slides.HomeSlide.d33d7a9c29',
// Why: plain spaces (not &nbsp;) — React text nodes render HTML entities literally.
'orca · feat/mobile-page'
)}
</span>
</div>
</div>
<div className="mp-chevron-right">
<ChevronIcon />
</div>
</div>
<div className="mp-section-label" style={{ marginTop: 10 }}>
{translate('auto.components.mobile.slides.HomeSlide.a4c3f7b7aa', 'Tasks')}
</div>
<div className="mp-task-home-card">
<div className="mp-task-home-icon">
<ListTodoIcon />
</div>
<div className="mp-host-main">
<div className="mp-task-home-title">
{translate('auto.components.mobile.slides.HomeSlide.a4c3f7b7aa', 'Tasks')}
</div>
<div className="mp-task-home-subtitle">
{translate('auto.components.mobile.slides.HomeSlide.d047197480', 'GitHub · Linear')}
</div>
</div>
<div
className="mp-task-home-providers"
aria-label={translate(
'auto.components.mobile.slides.HomeSlide.0bad5b07c8',
'GitHub and Linear'
)}
>
<div className="mp-task-home-provider-button">
<GithubIcon />
</div>
<div className="mp-task-home-provider-button">
<LinearIcon />
</div>
</div>
<div className="mp-chevron-right">
<ChevronIcon />
</div>
</div>
<div className="mp-section-label" style={{ marginTop: 14 }}>
{translate('auto.components.mobile.slides.HomeSlide.0b00c98506', 'Quick Actions')}
</div>
<div className="mp-quick-actions">
<div className="mp-quick-action">
<div className="mp-quick-action-icon">
<QrSmallIcon />
</div>
<div className="mp-quick-action-label">
{translate('auto.components.mobile.slides.HomeSlide.4405f3c440', 'Pair Desktop')}
</div>
</div>
<div className="mp-quick-action">
<div className="mp-quick-action-icon">
<PlusIcon />
</div>
<div className="mp-quick-action-label">
{translate('auto.components.mobile.slides.HomeSlide.e27fdaee51', 'New Workspace')}
</div>
</div>
</div>
<div className="mp-section-label" style={{ marginTop: 14 }}>
{translate('auto.components.mobile.slides.HomeSlide.8a350a4784', 'Account usage')}
</div>
<div className="mp-accounts-card">
<AccountRow
icon={<ClaudeIcon size={18} />}
email="claude@stably.ai"
sessionPct={42}
weekPct={18}
/>
<AccountRow
icon={<OpenAIIcon size={18} />}
email="codex@stably.ai"
sessionPct={67}
weekPct={31}
/>
<div className="mp-home-bar-button is-primary">
<PlusIcon />
<span>
{translate('auto.components.mobile.slides.HomeSlide.d08ee9c466', 'New workspace')}
</span>
</div>
</div>
</div>
)
}
function Stat({ value, label }: { value: string; label: string }): React.JSX.Element {
function JumpBackInRow({
indicator,
name,
status,
desktop,
time
}: JumpBackInRowProps): React.JSX.Element {
return (
<div className="mp-stat-card">
<div className="mp-stat-value">{value}</div>
<div className="mp-stat-label">{label}</div>
</div>
)
}
function AccountRow({
icon,
email,
sessionPct,
weekPct
}: {
icon: React.ReactNode
email: string
sessionPct: number
weekPct: number
}): React.JSX.Element {
return (
<div className="mp-accounts-row">
<div className="mp-accounts-icon">{icon}</div>
<div className="mp-accounts-info">
<div className="mp-accounts-email">{email}</div>
<div className="mp-accounts-bars">
<UsageBar
label={translate('auto.components.mobile.slides.HomeSlide.a3d5476811', '5h')}
pct={sessionPct}
/>
<UsageBar
label={translate('auto.components.mobile.slides.HomeSlide.a7d9e2c44d', '7d')}
pct={weekPct}
/>
<div className="mp-home-row">
<div className="mp-home-status">
{indicator === 'working' ? (
<div className="mp-wl-spinner" />
) : (
<div className={cn('mp-wl-dot', AGENT_DOT[indicator])} />
)}
</div>
<div className="mp-home-text">
<div className="mp-home-title">{name}</div>
<div className="mp-home-subtitle">
<span className={STATUS_TONE[indicator]}>{status}</span> · {desktop}
</div>
</div>
</div>
)
}
function UsageBar({ label, pct }: { label: string; pct: number }): React.JSX.Element {
return (
<div className="mp-usage-bar">
<div className="mp-usage-bar-label">{label}</div>
<div className="mp-usage-bar-track">
<div className="mp-usage-bar-fill" style={{ width: `${pct}%` }} />
<div className="mp-home-trailing">
{time}
<ChevronIcon />
</div>
</div>
)
@@ -266,16 +191,6 @@ function SettingsIcon(): React.JSX.Element {
)
}
function DesktopIcon(): React.JSX.Element {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="2" y="3" width="20" height="14" rx="2" />
<path d="M8 21h8" />
<path d="M12 17v4" />
</svg>
)
}
function ChevronIcon(): React.JSX.Element {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
@@ -284,15 +199,6 @@ function ChevronIcon(): React.JSX.Element {
)
}
function ResumeIcon(): React.JSX.Element {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="m4 17 6-6-6-6" />
<path d="M12 19h8" />
</svg>
)
}
function ListTodoIcon(): React.JSX.Element {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
@@ -305,37 +211,6 @@ function ListTodoIcon(): React.JSX.Element {
)
}
function GithubIcon(): React.JSX.Element {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
<path d="M9 18c-4.51 2-5-2-7-2" />
</svg>
)
}
function LinearIcon(): React.JSX.Element {
return (
<svg viewBox="0 0 100 100" fill="currentColor" aria-hidden>
<path d="M1.225 61.523c-.187-.738.708-1.235 1.246-.697l36.703 36.703c.538.538.041 1.433-.697 1.246C20.6 94.16 5.84 79.4 1.225 61.523ZM.002 46.811a.997.997 0 0 0 .291.749l52.147 52.147a.998.998 0 0 0 .749.291 50.328 50.328 0 0 0 9.235-1.119c.667-.149.904-.972.422-1.454L1.575 37.154c-.482-.482-1.305-.245-1.454.422A50.328 50.328 0 0 0 .002 46.81Zm4.528-18.34a.998.998 0 0 0 .195 1.144l64.66 64.66a.998.998 0 0 0 1.144.195 50.45 50.45 0 0 0 5.913-3.46.999.999 0 0 0 .14-1.518L9.51 22.418a.999.999 0 0 0-1.518.14 50.45 50.45 0 0 0-3.46 5.913Zm10.435-13.075a.999.999 0 0 0 .002 1.41l68.226 68.226a.999.999 0 0 0 1.41.002c19.292-19.477 19.234-50.97-.176-70.378-19.410-19.410-50.901-19.468-70.378-.176-1.061 1.044.916 1.916.916 1.916Z" />
</svg>
)
}
function QrSmallIcon(): React.JSX.Element {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="3" y="3" width="7" height="7" rx="1" />
<rect x="14" y="3" width="7" height="7" rx="1" />
<rect x="3" y="14" width="7" height="7" rx="1" />
<rect x="14" y="14" width="3" height="3" />
<rect x="18" y="14" width="3" height="3" />
<rect x="14" y="18" width="3" height="3" />
<rect x="18" y="18" width="3" height="3" />
</svg>
)
}
function PlusIcon(): React.JSX.Element {
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
-17
View File
@@ -15093,27 +15093,10 @@
},
"slides": {
"HomeSlide": {
"a7d9e2c44d": "7 Tage",
"a3d5476811": "5 h",
"8a350a4784": "Kontonutzung",
"e27fdaee51": "Arbeitsbereich erstellen",
"4405f3c440": "Desktop koppeln",
"0b00c98506": "Schnellaktionen",
"0bad5b07c8": "GitHub und Linear",
"d047197480": "GitHub · Linear",
"a4c3f7b7aa": "Aufgaben",
"d33d7a9c29": "orca · feat/mobile-page",
"25d6e8a491": "feat/mobile-page",
"c791677f2f": "Fortsetzen",
"cf3f98fa3f": "Getrennt",
"091355da3d": "M1 Mini · zu Hause",
"0bc1881bc4": "Verbunden · 40 Worktrees · 5 aktiv",
"19c212e25e": "MacBook Pro",
"2f1a1d10c4": "Desktop-Computer",
"156db8a68a": "Erstellte PRs",
"4a40af029b": "Agentenzeit",
"00a6903322": "Gestartete Agenten",
"c0e2e9dcd9": "Willkommen zurück",
"af761a0c0d": "Einstellungen",
"5d94e8ddcc": "Orca"
},
+14 -18
View File
@@ -15337,29 +15337,25 @@
},
"slides": {
"HomeSlide": {
"a7d9e2c44d": "7d",
"a3d5476811": "5h",
"8a350a4784": "Account usage",
"e27fdaee51": "New Workspace",
"4405f3c440": "Pair Desktop",
"0b00c98506": "Quick Actions",
"0bad5b07c8": "GitHub and Linear",
"d047197480": "GitHub · Linear",
"a4c3f7b7aa": "Tasks",
"d33d7a9c29": "orca · feat/mobile-page",
"25d6e8a491": "feat/mobile-page",
"c791677f2f": "Resume",
"cf3f98fa3f": "Disconnected",
"091355da3d": "M1 Mini · home",
"0bc1881bc4": "Connected · 40 worktrees · 5 active",
"19c212e25e": "MacBook Pro",
"2f1a1d10c4": "Desktops",
"156db8a68a": "PRs created",
"4a40af029b": "Agent time",
"00a6903322": "Agents spawned",
"c0e2e9dcd9": "Welcome back",
"af761a0c0d": "Settings",
"5d94e8ddcc": "Orca"
"5d94e8ddcc": "Orca",
"dc1f3f5535": "Jump back in",
"c2a0215163": "Waiting for input",
"0c14dac908": "2m",
"77c0dae863": "Done",
"3166f865af": "14m",
"609517273b": "Working",
"005afbad3d": "now",
"e986b282af": "Last opened",
"8a3ebdbdac": "1h",
"04a94cadee": "40 worktrees · 5 active",
"b5308d5e2c": "M1 Mini",
"aefd110ea5": "Pair a desktop",
"d08ee9c466": "New workspace"
},
"TerminalSlide": {
"0bb39f8fe6": "Send",
-17
View File
@@ -14951,27 +14951,10 @@
},
"slides": {
"HomeSlide": {
"a7d9e2c44d": "7d",
"a3d5476811": "5h",
"8a350a4784": "Uso de la cuenta",
"e27fdaee51": "Nuevo espacio de trabajo",
"4405f3c440": "Emparejar escritorio",
"0b00c98506": "Acciones rápidas",
"0bad5b07c8": "GitHub y Linear",
"d047197480": "GitHub · Linear",
"a4c3f7b7aa": "Tareas",
"d33d7a9c29": "orca  ·  feat/mobile-page",
"25d6e8a491": "feat/mobile-page",
"c791677f2f": "Reanudar",
"cf3f98fa3f": "Desconectado",
"091355da3d": "M1 Mini · inicio",
"0bc1881bc4": "Conectado · 40 worktrees · 5 activos",
"19c212e25e": "MacBook Pro",
"2f1a1d10c4": "Escritorios",
"156db8a68a": "PRs creados",
"4a40af029b": "Tiempo de agentes",
"00a6903322": "Agentes iniciados",
"c0e2e9dcd9": "Bienvenido de nuevo",
"af761a0c0d": "Ajustes",
"5d94e8ddcc": "Orca"
},
-17
View File
@@ -15175,27 +15175,10 @@
},
"slides": {
"HomeSlide": {
"a7d9e2c44d": "7 j",
"a3d5476811": "5 h",
"8a350a4784": "Utilisation du compte",
"e27fdaee51": "Nouvel espace de travail",
"4405f3c440": "Appairer l'ordinateur",
"0b00c98506": "Actions rapides",
"0bad5b07c8": "GitHub et Linear",
"d047197480": "GitHub · Linear",
"a4c3f7b7aa": "Tâches",
"d33d7a9c29": "orca · feat/mobile-page",
"25d6e8a491": "feat/mobile-page",
"c791677f2f": "Reprendre",
"cf3f98fa3f": "Déconnecté",
"091355da3d": "M1 Mini · accueil",
"0bc1881bc4": "Connecté · 40 worktrees · 5 actifs",
"19c212e25e": "MacBook Pro",
"2f1a1d10c4": "Ordinateurs",
"156db8a68a": "PRs créées",
"4a40af029b": "Temps agent",
"00a6903322": "Agents lancés",
"c0e2e9dcd9": "Bon retour",
"af761a0c0d": "Paramètres",
"5d94e8ddcc": "Orca"
},
-17
View File
@@ -14576,27 +14576,10 @@
},
"slides": {
"HomeSlide": {
"a7d9e2c44d": "7日",
"a3d5476811": "5時間",
"8a350a4784": "アカウントの使用状況",
"e27fdaee51": "新規ワークスペース",
"4405f3c440": "デスクトップのペアリング",
"0b00c98506": "クイック操作",
"0bad5b07c8": "GitHub と Linear",
"d047197480": "GitHub · Linear",
"a4c3f7b7aa": "タスク",
"d33d7a9c29": "orca · feat/mobile-page",
"25d6e8a491": "feat/mobile-page",
"c791677f2f": "再開",
"cf3f98fa3f": "切断済み",
"091355da3d": "M1ミニ・ホーム",
"0bc1881bc4": "接続済み · 40 ワークツリー · 5 アクティブ",
"19c212e25e": "MacBook Pro",
"2f1a1d10c4": "デスクトップ",
"156db8a68a": "作成された PR",
"4a40af029b": "Agent 時間",
"00a6903322": "Agent が生成されました",
"c0e2e9dcd9": "おかえり",
"af761a0c0d": "設定",
"5d94e8ddcc": "Orca"
},
-17
View File
@@ -14576,27 +14576,10 @@
},
"slides": {
"HomeSlide": {
"a7d9e2c44d": "7일",
"a3d5476811": "5시간",
"8a350a4784": "계정 사용량",
"e27fdaee51": "새 워크스페이스",
"4405f3c440": "데스크탑 페어링",
"0b00c98506": "빠른 작업",
"0bad5b07c8": "GitHub 및 Linear",
"d047197480": "GitHub · Linear",
"a4c3f7b7aa": "작업",
"d33d7a9c29": "orca  ·  feat/mobile-page",
"25d6e8a491": "feat/mobile-page",
"c791677f2f": "재개",
"cf3f98fa3f": "연결이 끊김",
"091355da3d": "M1 미니 · 집",
"0bc1881bc4": "연결됨 · 워크트리 40개 · 활성 5개",
"19c212e25e": "맥북 프로",
"2f1a1d10c4": "데스크탑",
"156db8a68a": "생성된 PR",
"4a40af029b": "Agent 시간",
"00a6903322": "시작된 Agents",
"c0e2e9dcd9": "다시 오신 것을 환영합니다",
"af761a0c0d": "설정",
"5d94e8ddcc": "Orca"
},
-17
View File
@@ -16419,27 +16419,10 @@
"mobile": {
"slides": {
"HomeSlide": {
"a7d9e2c44d": "7d",
"a3d5476811": "5h",
"8a350a4784": "Uso da conta",
"e27fdaee51": "Novo Espaço de Trabalho",
"4405f3c440": "Parear Desktop",
"0b00c98506": "Ações Rápidas",
"0bad5b07c8": "GitHub e Linear",
"d047197480": "GitHub · Linear",
"a4c3f7b7aa": "Tarefas",
"d33d7a9c29": "orca · feat/mobile-page",
"25d6e8a491": "feat/mobile-page",
"c791677f2f": "Retomar",
"cf3f98fa3f": "Desconectado",
"091355da3d": "M1 Mini · casa",
"0bc1881bc4": "Conectado · 40 worktrees · 5 ativas",
"19c212e25e": "MacBook Pro",
"2f1a1d10c4": "Desktops",
"156db8a68a": "PRs criados",
"4a40af029b": "Tempo de agente",
"00a6903322": "Agentes iniciados",
"c0e2e9dcd9": "Bem-vindo de volta",
"af761a0c0d": "Configurações",
"5d94e8ddcc": "Orca"
},
-17
View File
@@ -15563,27 +15563,10 @@
},
"slides": {
"HomeSlide": {
"a7d9e2c44d": "7 дн.",
"a3d5476811": "5 ч",
"8a350a4784": "Использование учётной записи",
"e27fdaee51": "Новое рабочее пространство",
"4405f3c440": "Подключить компьютер",
"0b00c98506": "Быстрые действия",
"0bad5b07c8": "GitHub и Linear",
"d047197480": "GitHub · Linear",
"a4c3f7b7aa": "Задачи",
"d33d7a9c29": "orca · feat/mobile-page",
"25d6e8a491": "feat/mobile-page",
"c791677f2f": "Продолжить",
"cf3f98fa3f": "Отключено",
"091355da3d": "M1 Mini · home",
"0bc1881bc4": "Подключено · рабочих деревьев: 40 · активных: 5",
"19c212e25e": "MacBook Pro",
"2f1a1d10c4": "Компьютеры",
"156db8a68a": "Создано PR",
"4a40af029b": "Время агентов",
"00a6903322": "Запущено агентов",
"c0e2e9dcd9": "С возвращением",
"af761a0c0d": "Настройки",
"5d94e8ddcc": "Orca"
},
-17
View File
@@ -15079,27 +15079,10 @@
},
"slides": {
"HomeSlide": {
"a7d9e2c44d": "7g",
"a3d5476811": "5 sa",
"8a350a4784": "Hesap kullanımı",
"e27fdaee51": "Yeni Çalışma Alanı",
"4405f3c440": "Masaüstünü Eşleştir",
"0b00c98506": "Hızlı İşlemler",
"0bad5b07c8": "GitHub ve Linear",
"d047197480": "GitHub · Linear",
"a4c3f7b7aa": "Görevler",
"d33d7a9c29": "orca · feat/mobile-page",
"25d6e8a491": "feat/mobile-page",
"c791677f2f": "Devam Et",
"cf3f98fa3f": "Bağlantı Kesildi",
"091355da3d": "M1 Mini · ev",
"0bc1881bc4": "Bağlandı · 40 worktree · 5 etkin",
"19c212e25e": "MacBook Pro",
"2f1a1d10c4": "Masaüstleri",
"156db8a68a": "Oluşturulan PR'lar",
"4a40af029b": "Ajan süresi",
"00a6903322": "Oluşturulan ajanlar",
"c0e2e9dcd9": "Tekrar hoş geldiniz",
"af761a0c0d": "Ayarlar",
"5d94e8ddcc": "Orca"
},
@@ -14881,27 +14881,10 @@
},
"slides": {
"HomeSlide": {
"a7d9e2c44d": "7 天",
"a3d5476811": "5 小時",
"8a350a4784": "帳戶用量",
"e27fdaee51": "新工作區",
"4405f3c440": "配對電腦",
"0b00c98506": "快速操作",
"0bad5b07c8": "GitHub 和 Linear",
"d047197480": "GitHub · Linear",
"a4c3f7b7aa": "任務",
"d33d7a9c29": "orca · feat/mobile-page",
"25d6e8a491": "feat/mobile-page",
"c791677f2f": "恢復",
"cf3f98fa3f": "已斷開連線",
"091355da3d": "M1 Mini · 家",
"0bc1881bc4": "已連線 · 40 個工作樹 · 5 個使用中",
"19c212e25e": "MacBook Pro",
"2f1a1d10c4": "電腦",
"156db8a68a": "已建立 PR",
"4a40af029b": "代理程式時間",
"00a6903322": "已建立的代理程式",
"c0e2e9dcd9": "歡迎回來",
"af761a0c0d": "設定",
"5d94e8ddcc": "Orca"
},
-17
View File
@@ -14576,27 +14576,10 @@
},
"slides": {
"HomeSlide": {
"a7d9e2c44d": "7天",
"a3d5476811": "5小时",
"8a350a4784": "账户使用情况",
"e27fdaee51": "新工作区",
"4405f3c440": "配对桌面",
"0b00c98506": "快捷操作",
"0bad5b07c8": "GitHub 和 Linear",
"d047197480": "GitHub · Linear",
"a4c3f7b7aa": "任务",
"d33d7a9c29": "Orca / mobile页面",
"25d6e8a491": "mobile页面",
"c791677f2f": "恢复",
"cf3f98fa3f": "已断开连接",
"091355da3d": "M1迷你·家",
"0bc1881bc4": "已连接 · 40 个工作树 · 5 个活动树",
"19c212e25e": "MacBook Pro",
"2f1a1d10c4": "台式机",
"156db8a68a": "已创建 PR",
"4a40af029b": "智能体时间",
"00a6903322": "已启动智能体",
"c0e2e9dcd9": "欢迎回来",
"af761a0c0d": "设置",
"5d94e8ddcc": "Orca"
},