Merge main while retaining SQL chat readers

This commit is contained in:
Brennan Benson
2026-11-10 10:49:29 -08:00
3590 changed files with 29546 additions and 61681 deletions
@@ -29,7 +29,6 @@ on:
- 'src/main/codex-usage/scanner-incremental-append.test.ts'
- 'src/main/usage/jsonl-file-reader.test.ts'
- 'src/main/usage/jsonl-file-snapshot.test.ts'
- 'src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-queue-retention.test.ts'
- 'src/main/claude-usage/scanner-incremental-append.test.ts'
- 'src/main/claude-usage/store-checkpoint-persistence.test.ts'
+4
View File
@@ -512,6 +512,10 @@ jobs:
if: needs.code_paths.outputs.static_analysis == 'true'
run: bun config/scripts/localization/source-extraction/audit-localization-coverage.mjs --check
- name: Verify startup and worktree localization coverage
if: needs.code_paths.outputs.static_analysis == 'true'
run: bun run verify:startup-worktree-localization-coverage
- name: Verify mobile localization
if: needs.code_paths.outputs.static_analysis == 'true'
run: bun config/scripts/localization/catalog-validation/verify-mobile-localization.mjs
+3
View File
@@ -121,6 +121,9 @@ validation-screenshots/
test-results/
playwright-report/
# Claude Code settings are machine-local.
**/.claude/settings.local.json
# Agent skills are installed per machine.
/.claude/skills/
/.agents/skills/
+2 -4
View File
@@ -292,11 +292,9 @@ Pair with your desktop app to monitor and steer your agents from your phone.
- **Discord:** Join the community on **[Discord](https://discord.gg/fzjDKHxv8Q)**.
- **Twitter / X:** Follow **[@orca_build](https://x.com/orca_build)** for updates and announcements.
- **WeChat:** Scan to join an Orca community WeChat group. If one group is full, join another.
- **WeChat:** Scan to join an Orca community WeChat group.
<img src="docs/assets/wechat-qr-group14.jpg" alt="WeChat QR code for the Orca community" width="160" />
<img src="docs/assets/wechat-qr-group15.jpg" alt="WeChat QR code for the Orca community" width="160" />
<img src="docs/assets/wechat-qr-group16.jpg" alt="WeChat QR code for the Orca community" width="160" />
<img src="docs/assets/wechat-qr.jpg" alt="WeChat QR code for the Orca community" width="160" />
- **Feedback &amp; Ideas:** We ship fast. Missing something? [Request a new feature](https://github.com/stablyai/orca/issues).
- **Privacy:** See the [privacy &amp; telemetry docs](https://www.onorca.dev/docs/telemetry) for what anonymous usage data Orca collects and how to opt out.
@@ -1,5 +1,5 @@
// Later run-all.ts checks against the same host: rebind, orphan grace, silence,
// forced resets and pre-upgrade refusals.
// forced resets.
import WebSocket from 'ws'
import { GRACE_MS, ORIGIN, PING_MS, PORT, SILENCE_MS, makeControl, openPhone, record, sleep, waitFor, type Ctx, type Control } from './run-all-harness'
@@ -89,12 +89,4 @@ export async function runLifecycleChecks(ctx: Ctx, main: Control, mainAck: Ack):
back.client.closeNow()
}
// Bad JWT -> 401 before upgrade; malformed host id -> 429.
{
const bad = new WebSocket(`ws://127.0.0.1:${PORT}/v1/host/control`, { headers: { authorization: 'Bearer nope' } })
const status = await new Promise<number>((res) => bad.on('unexpected-response', (_q, r) => res(r.statusCode ?? 0)))
const badHost = new WebSocket(`ws://127.0.0.1:${PORT}/v1/connect/short`)
const s2 = await new Promise<number>((res) => badHost.on('unexpected-response', (_q, r) => res(r.statusCode ?? 0)))
record('R11/R5 ES256 JWKS rejects bad token (401); malformed host id 429', status === 401 && s2 === 429, `${status},${s2}`)
}
}
-5
View File
@@ -100,11 +100,6 @@ async function runTests(ctx: Ctx): Promise<void> {
const mainAck = await main.client.connect()
const transport = main.bindTransport(mainAck.generation)
// Alarm-driven app ping, answered by the shipped client's pong handler.
await sleep(PING_MS * 3 + 300)
record('R2/R5 alarm-driven JSON ping parsed by shipped client, pong keeps control alive',
main.events.pings >= 2 && main.client.isLive(), `pings=${main.events.pings} live=${main.client.isLive()}`)
// Phone connect -> conn-open (strict, shipped parser) -> host data -> relay-hello (shipped parser).
const phone = openPhone(ctx)
const helloOk = await waitFor(() => phone.hello !== null, 5000)
@@ -0,0 +1,14 @@
id: claude-chat-entrypoint-callers
snapshots:
"import { withOrcaClaudeChatEntrypoint as stamp } from './claude-chat-entrypoint'":
labels:
- source: withOrcaClaudeChatEntrypoint
style: primary
start: 9
end: 37
'withOrcaClaudeChatEntrypoint(env, platform)':
labels:
- source: withOrcaClaudeChatEntrypoint
style: primary
start: 0
end: 28
@@ -0,0 +1,20 @@
id: claude-chat-entrypoint-owner
snapshots:
"const env = { CLAUDE_CODE_ENTRYPOINT: 'orca' }":
labels:
- source: CLAUDE_CODE_ENTRYPOINT
style: primary
start: 14
end: 36
const identity = 'sdk-cli':
labels:
- source: sdk-cli
style: primary
start: 18
end: 25
"env['claude_code_entrypoint'] = 'other'":
labels:
- source: claude_code_entrypoint
style: primary
start: 5
end: 27
@@ -0,0 +1,14 @@
id: claude-entrypoint-key-owner
snapshots:
"env[CLAUDE_ENTRYPOINT_ENV] = 'orca'":
labels:
- source: CLAUDE_ENTRYPOINT_ENV
style: primary
start: 4
end: 25
"import { CLAUDE_ENTRYPOINT_ENV as key } from './claude-chat-entrypoint'":
labels:
- source: CLAUDE_ENTRYPOINT_ENV
style: primary
start: 9
end: 30
@@ -0,0 +1,6 @@
id: claude-chat-entrypoint-callers
valid:
- 'stripInheritedClaudeSessionEnv(env)'
invalid:
- 'withOrcaClaudeChatEntrypoint(env, platform)'
- "import { withOrcaClaudeChatEntrypoint as stamp } from './claude-chat-entrypoint'"
@@ -0,0 +1,8 @@
id: claude-chat-entrypoint-owner
valid:
- 'const env = withOrcaClaudeChatEntrypoint(configured, platform)'
- "const binary = 'orca'"
invalid:
- "const env = { CLAUDE_CODE_ENTRYPOINT: 'orca' }"
- "env['claude_code_entrypoint'] = 'other'"
- "const identity = 'sdk-cli'"
@@ -0,0 +1,6 @@
id: claude-entrypoint-key-owner
valid:
- "const binary = 'orca'"
invalid:
- "env[CLAUDE_ENTRYPOINT_ENV] = 'orca'"
- "import { CLAUDE_ENTRYPOINT_ENV as key } from './claude-chat-entrypoint'"
@@ -0,0 +1,117 @@
id: claude-chat-entrypoint-owner
language: typescript
severity: error
message: Keep the Claude identity literal in claude-chat-entrypoint.ts.
files: ['src/**']
ignores:
- '**/*.test.*'
- '**/*.test-fixtures.*'
- '**/__fixtures__/**'
- 'src/shared/agent-launch/startup/claude-chat-entrypoint.ts'
rule:
all:
- any:
- kind: string_fragment
- kind: property_identifier
- kind: identifier
- regex: '(?i)^(CLAUDE_CODE_ENTRYPOINT|sdk-cli)$'
---
id: claude-chat-entrypoint-owner-tsx
language: tsx
severity: error
message: Keep the Claude identity literal in claude-chat-entrypoint.ts.
files: ['src/**']
ignores:
- '**/*.test.*'
- '**/*.test-fixtures.*'
- '**/__fixtures__/**'
- 'src/shared/agent-launch/startup/claude-chat-entrypoint.ts'
rule:
all:
- any:
- kind: string_fragment
- kind: property_identifier
- kind: identifier
- regex: '(?i)^(CLAUDE_CODE_ENTRYPOINT|sdk-cli)$'
---
id: claude-chat-entrypoint-callers
language: typescript
severity: error
message: Only structured native chat and its own probes may apply the chat identity.
files: ['src/**']
ignores:
- '**/*.test.*'
- '**/*.test-fixtures.*'
- '**/__fixtures__/**'
- 'src/shared/agent-launch/startup/claude-chat-entrypoint.ts'
- 'src/main/claude/claude-stream-json-connection.ts'
- 'src/main/claude/claude-agent-sdk-process-spawn.ts'
- 'src/main/claude/structured/launch/claude-structured-child-env.ts'
- 'src/main/claude/claude-model-catalog-probe.ts'
rule:
all:
- any:
- kind: string_fragment
- kind: property_identifier
- kind: identifier
- regex: '^withOrcaClaudeChatEntrypoint$'
---
id: claude-chat-entrypoint-callers-tsx
language: tsx
severity: error
message: Only structured native chat and its own probes may apply the chat identity.
files: ['src/**']
ignores:
- '**/*.test.*'
- '**/*.test-fixtures.*'
- '**/__fixtures__/**'
- 'src/shared/agent-launch/startup/claude-chat-entrypoint.ts'
- 'src/main/claude/claude-stream-json-connection.ts'
- 'src/main/claude/claude-agent-sdk-process-spawn.ts'
- 'src/main/claude/structured/launch/claude-structured-child-env.ts'
- 'src/main/claude/claude-model-catalog-probe.ts'
rule:
all:
- any:
- kind: string_fragment
- kind: property_identifier
- kind: identifier
- regex: '^withOrcaClaudeChatEntrypoint$'
---
id: claude-entrypoint-key-owner
language: typescript
severity: error
message: Only the chat identity owner and inherited-session isolation may access this key directly.
files: ['src/**']
ignores:
- '**/*.test.*'
- '**/*.test-fixtures.*'
- '**/__fixtures__/**'
- 'src/shared/agent-launch/startup/claude-chat-entrypoint.ts'
- 'src/shared/agent-launch/startup/claude-parent-session-env.ts'
rule:
all:
- any:
- kind: string_fragment
- kind: property_identifier
- kind: identifier
- regex: '^CLAUDE_ENTRYPOINT_ENV$'
---
id: claude-entrypoint-key-owner-tsx
language: tsx
severity: error
message: Only the chat identity owner and inherited-session isolation may access this key directly.
files: ['src/**']
ignores:
- '**/*.test.*'
- '**/*.test-fixtures.*'
- '**/__fixtures__/**'
- 'src/shared/agent-launch/startup/claude-chat-entrypoint.ts'
- 'src/shared/agent-launch/startup/claude-parent-session-env.ts'
rule:
all:
- any:
- kind: string_fragment
- kind: property_identifier
- kind: identifier
- regex: '^CLAUDE_ENTRYPOINT_ENV$'
+1 -8
View File
@@ -30,8 +30,6 @@
3 test-no-in-test-import-actual:src/renderer/src/components/terminal-pane/snapshots/remote-snapshot-alt-screen-host-chain.test.ts
1 test-no-in-test-import-actual:src/renderer/src/hooks/useIpcEvents-agent-status-pane-teardown.test.ts
1 test-no-in-test-import-actual:src/renderer/src/hooks/useIpcEvents-agent-status-snapshot-hydration.test.ts
1 test-no-in-test-import-actual:src/renderer/src/hooks/useIpcEvents-rate-limit-hydration.test.ts
1 test-no-in-test-import-actual:src/renderer/src/lib/agent-session/launch/new-tab/agent-launch-caller-routing.test.ts
1 test-no-polling-loop:src/main/browser/cookies/browser-cookie-import-concurrency.test.ts
1 test-no-polling-loop:src/main/claude/claude-stop-ack-race.test.ts
1 test-no-polling-loop:src/main/claude/claude-stream-json-connection.test.ts
@@ -256,7 +254,6 @@
1 test-no-real-time-wait:src/main/windows/windows-process-tree-command-line-patch.test.ts
2 test-no-real-time-wait:src/main/windows/windows-pty-job.win32.test.ts
1 test-no-real-time-wait:src/main/worker-thread-child-process/worker-thread-child-process.test.ts
1 test-no-real-time-wait:src/main/workspaces/creation/preparation/worktree-create-preparation-cancellation.test.ts
1 test-no-real-time-wait:src/main/workspaces/removal/local-worktree-removal-recovery-live.test.ts
2 test-no-real-time-wait:src/main/wsl/execution/wsl-command-loop.test.ts
2 test-no-real-time-wait:src/main/wsl/orcad/wsl-orcad-real-distro-lane.test.ts
@@ -272,7 +269,6 @@
1 test-no-real-time-wait:src/renderer/src/components/terminal-pane/viewport/terminal-fit-restore.test.ts
1 test-no-real-time-wait:src/renderer/src/hooks/runtime-project-refresh-scheduler.test.ts
1 test-no-real-time-wait:src/renderer/src/lib/agent-session/resume/cli-fork/native-chat-cli-fork-preparation.test.ts
1 test-no-real-time-wait:src/renderer/src/lib/orca-yaml-trust-prompt-slot-eviction.test.ts
2 test-no-real-time-wait:src/renderer/src/store/slices/workspace-cleanup/workspace-cleanup-removal-preflight.test.ts
2 test-no-real-time-wait:src/shared/__tests__/process-host/windows-tree-kill-late-taskkill.win32.test.ts
1 test-no-real-time-wait:src/shared/darwin-process-table-capture.test.ts
@@ -637,7 +633,7 @@
1 test-no-wall-clock:src/renderer/src/components/native-chat/session/structured/delivery/NativeChatMessageList.unsent-message.test.tsx
3 test-no-wall-clock:src/renderer/src/components/native-chat/surfaces/integration/NativeChatMessageList.test.tsx
2 test-no-wall-clock:src/renderer/src/components/native-chat/tools/tasks/NativeChatStructuredSessionStatus.test.tsx
3 test-no-wall-clock:src/renderer/src/components/sidebar/workspace-card/review/use-workspace-reference-details.test.tsx
1 test-no-wall-clock:src/renderer/src/components/sidebar/workspace-card/review/use-workspace-reference-details.test.tsx
1 test-no-wall-clock:src/renderer/src/components/sidebar/worktree-list/listing/WorktreeList.lineage-agent-expansion-coupling.test.tsx
2 test-no-wall-clock:src/renderer/src/components/task-page/jira/task-page-jira-cache-selectors.test.ts
2 test-no-wall-clock:src/renderer/src/components/terminal-pane/pty-connection/emulation/fish-color-scheme-child-stdin.node-pty.test.ts
@@ -647,13 +643,11 @@
1 test-no-wall-clock:src/renderer/src/components/worktree-jump/WorktreeJumpPalette.recent-tabs.behavior.test.tsx
4 test-no-wall-clock:src/renderer/src/components/worktree-jump/WorktreeJumpPalette.recent-tabs.test.tsx
2 test-no-wall-clock:src/renderer/src/hooks/ipc-events/agent-status-terminal-title-tab-write.test.ts
1 test-no-wall-clock:src/renderer/src/hooks/useIpcEvents-agent-status-hook-titles.test.ts
1 test-no-wall-clock:src/renderer/src/lib/paired-runtime-closed-tab-resume.test.ts
2 test-no-wall-clock:src/renderer/src/lib/use-tab-agent-observed-signal-dispatch.test.tsx
2 test-no-wall-clock:src/renderer/src/lib/wake-sleeping-agents-live-unmounted-pane.test.ts
1 test-no-wall-clock:src/renderer/src/lib/workspace/ownership/workspace-terminal-host-authority.test.ts
1 test-no-wall-clock:src/renderer/src/lib/workspace/activation/worktree-activation-emptied-workspace-reseed.test.ts
2 test-no-wall-clock:src/renderer/src/runtime/native-chat/tab-retirement/structured-agent-session-tab-retirement.test.ts
2 test-no-wall-clock:src/renderer/src/store/github/pull-requests/github-pr-branch-linked-pr-divergence.test.ts
6 test-no-wall-clock:src/renderer/src/store/slices/jira/jira.test.ts
5 test-no-wall-clock:src/renderer/src/store/slices/worktrees/metadata/worktree-meta-hosted-review.test.ts
@@ -677,7 +671,6 @@
2 test-no-wall-clock:tests/e2e/cross-version-wire/release-source-retention.unit.test.ts
1 test-no-wall-clock:tests/e2e/host-cold-park-remote-subscriber.unit.test.ts
2 test-no-wall-clock:tests/e2e/omp-child-session-recovery.unit.test.ts
1 test-no-wall-clock:tests/e2e/structured-session-tab-close-regressions.unit.test.ts
2 test-timeout-needs-reason:mobile/src/terminal/document/native-document-bundle.test.ts
1 test-timeout-needs-reason:mobile/src/test-support/rpc-recording/family-recordings.test.ts
1 test-timeout-needs-reason:mobile/src/test-support/rpc-recording/mutants/family-mutants.test.ts
+9 -9
View File
@@ -19,10 +19,10 @@
1 src/renderer/src/components/activity/use-agent-pane-thread-store-data.ts
1 src/renderer/src/components/activity/use-agent-pane-threads.ts
4 src/renderer/src/components/agent/AgentSettingsDialog.tsx
1 src/renderer/src/components/automations/automation-host-client.ts
1 src/renderer/src/components/automations/hosts/automation-host-client.ts
1 src/renderer/src/components/automations/automations-page-fixtures.ts
1 src/renderer/src/components/automations/use-automations-page-controller.ts
1 src/renderer/src/components/automations/use-automations-page-destination-form.ts
1 src/renderer/src/components/automations/authoring/use-automations-page-destination-form.ts
1 src/renderer/src/components/browser-pane/ReopenBrowserPageOnServerButton.tsx
1 src/renderer/src/components/browser-pane/browser-reopen-on-server.ts
1 src/renderer/src/components/browser-pane/stream-remote/use-remote-browser-page-stream.ts
@@ -204,11 +204,11 @@
1 src/renderer/src/hooks/ipc-events/runtime-environment-subscription-selection.ts
1 src/renderer/src/hooks/ipc-events/settings-sidebar-ipc-bridge.ts
1 src/renderer/src/hooks/ipc-events/terminal-request-ipc-bridge.ts
1 src/renderer/src/hooks/use-active-skill-discovery-runtime-target.ts
1 src/renderer/src/hooks/skills/discovery/use-active-skill-discovery-runtime-target.ts
2 src/renderer/src/hooks/use-integration-provider-status-refresh.ts
1 src/renderer/src/hooks/useActiveProjectSkillRuntime.ts
1 src/renderer/src/hooks/skills/runtime/useActiveProjectSkillRuntime.ts
1 src/renderer/src/hooks/useEditorExternalWatch.ts
1 src/renderer/src/hooks/useInstalledAgentSkills.ts
1 src/renderer/src/hooks/skills/discovery/useInstalledAgentSkills.ts
1 src/renderer/src/hooks/useIpcEvents.ts
4 src/renderer/src/hooks/useIssueMetadata.ts
2 src/renderer/src/hooks/useLinearIssuePublication.ts
@@ -230,11 +230,11 @@
1 src/renderer/src/lib/workspace/ownership/runtime-session-mirror-owners.ts
1 src/renderer/src/lib/runtime-session-mirror-targets.ts
1 src/renderer/src/lib/workspace/ownership/single-runtime-legacy-owner.ts
1 src/renderer/src/lib/skill-runtime-command.ts
1 src/renderer/src/lib/terminal-close-route.ts
4 src/renderer/src/lib/terminal-worktree-route.ts # resolveTerminalPtyOwner: teardown, close and PTY identity read spawn's attribution (round 3 F8), which retired the deprecated resolver's readers
1 src/renderer/src/lib/skills/runtime/skill-runtime-command.ts
1 src/renderer/src/lib/workspace/ownership/terminal-close-route.ts
4 src/renderer/src/lib/workspace/ownership/terminal-worktree-route.ts # resolveTerminalPtyOwner: teardown, close and PTY identity read spawn's attribution (round 3 F8), which retired the deprecated resolver's readers
3 src/renderer/src/lib/unread-badge-count-selector.ts
1 src/renderer/src/lib/web-runtime-worktree-terminal-after-wake.ts
1 src/renderer/src/runtime/hosted-terminals/restoration/web-runtime-worktree-terminal-after-wake.ts
1 src/renderer/src/lib/work-items/linear/linear-issue-url-lookup.ts
1 src/renderer/src/lib/work-items/linear/linear-issue-write.ts
1 src/renderer/src/lib/workspace/activation/worktree-activation.ts
+1 -1
View File
@@ -1,7 +1,7 @@
# JSX mapped straight over a host collection, per file, outside components/host-picker.
# This is a RATCHET: counts may only go DOWN. Draw host lists with HostCombobox or
# useBoundedHostRows. Prune after a fix: pnpm check:host-list-render-ratchet --prune
1 src/renderer/src/components/automations/AutomationRunsDashboard.tsx
1 src/renderer/src/components/automations/runs/AutomationRunsDashboard.tsx
1 src/renderer/src/components/right-sidebar/ai-vault/panel/AiVaultPanelControls.tsx
1 src/renderer/src/components/right-sidebar/source-control/ai/SourceControlAgentActionDialogForm.tsx
1 src/renderer/src/components/settings/browser/profiles/BrowserSessionCookiesSection.tsx
-1
View File
@@ -554,7 +554,6 @@
2 src/main/worker-threads/forked-child-entry-path.ts
4 src/main/workspace-hooks/hooks.ts
10 src/main/workspace-hooks/issue-command-file.ts
4 src/main/workspace-hooks/worktree-runner-script.ts
1 src/main/workspaces/catalog/folder-repo-git-conversion.ts
2 src/main/workspaces/catalog/folder-upgrade-worktree-path.ts
3 src/main/workspaces/execution-host/project-runtime-git-router.ts
@@ -560,21 +560,21 @@
"count": 1
},
{
"filePath": "mobile/src/transport/hosts/mobile-relay-host-overlay.ts",
"filePath": "mobile/src/host-catalog/mobile-relay-host-overlay.ts",
"kind": "object-property:message",
"text": "Expected exactly one endpoint for configured relay metadata",
"dynamic": false,
"count": 1
},
{
"filePath": "mobile/src/transport/hosts/mobile-relay-host-overlay.ts",
"filePath": "mobile/src/host-catalog/mobile-relay-host-overlay.ts",
"kind": "object-property:message",
"text": "Relay host identity mismatch",
"dynamic": false,
"count": 1
},
{
"filePath": "mobile/src/transport/hosts/mobile-relay-host-overlay.ts",
"filePath": "mobile/src/host-catalog/mobile-relay-host-overlay.ts",
"kind": "object-property:message",
"text": "Relay identity and endpoint must coexist",
"dynamic": false,
+32 -32
View File
@@ -28,27 +28,27 @@
1 src/renderer/src/components/artifacts/ArtifactPublishButton.tsx
2 src/renderer/src/components/artifacts/ArtifactsPage.tsx
2 src/renderer/src/components/artifacts/useArtifactPagination.ts
2 src/renderer/src/components/automations/AutomationEditorPromptEditor.tsx
1 src/renderer/src/components/automations/AutomationHostFilterNotice.tsx
1 src/renderer/src/components/automations/AutomationProjectCombobox.tsx
1 src/renderer/src/components/automations/AutomationRunHistory.tsx
1 src/renderer/src/components/automations/AutomationRunsDashboard.tsx
1 src/renderer/src/components/automations/AutomationRunsTable.tsx
2 src/renderer/src/components/automations/authoring/AutomationEditorPromptEditor.tsx
1 src/renderer/src/components/automations/hosts/AutomationHostFilterNotice.tsx
1 src/renderer/src/components/automations/authoring/AutomationProjectCombobox.tsx
1 src/renderer/src/components/automations/runs/AutomationRunHistory.tsx
1 src/renderer/src/components/automations/runs/AutomationRunsDashboard.tsx
1 src/renderer/src/components/automations/runs/AutomationRunsTable.tsx
1 src/renderer/src/components/automations/AutomationsPageSurface.tsx
1 src/renderer/src/components/automations/use-automation-agent-picker-entries.ts
2 src/renderer/src/components/automations/use-automation-create-destination.ts
4 src/renderer/src/components/automations/use-automation-host-catalog.ts
4 src/renderer/src/components/automations/use-automation-list-search.ts
2 src/renderer/src/components/automations/use-automation-migrate-action.ts
1 src/renderer/src/components/automations/use-automation-runs-dashboard.ts
1 src/renderer/src/components/automations/use-automations-page-list-state.ts
1 src/renderer/src/components/automations/authoring/use-automation-agent-picker-entries.ts
2 src/renderer/src/components/automations/authoring/use-automation-create-destination.ts
4 src/renderer/src/components/automations/hosts/use-automation-host-catalog.ts
4 src/renderer/src/components/automations/listing/use-automation-list-search.ts
2 src/renderer/src/components/automations/authoring/use-automation-migrate-action.ts
1 src/renderer/src/components/automations/runs/use-automation-runs-dashboard.ts
1 src/renderer/src/components/automations/listing/use-automations-page-list-state.ts
1 src/renderer/src/components/automations/use-automations-page-local-state.ts
1 src/renderer/src/components/automations/use-automations-page-refresh.ts
1 src/renderer/src/components/automations/use-automations-page-setup-state.ts
1 src/renderer/src/components/automations/authoring/use-automations-page-setup-state.ts
1 src/renderer/src/components/automations/use-automations-page-store-state.ts
1 src/renderer/src/components/automations/use-external-automation-scope-retention.ts
2 src/renderer/src/components/automations/use-scoped-external-automations.ts
16 src/renderer/src/components/automations/use-selected-automation-run-history.ts
1 src/renderer/src/components/automations/external/use-external-automation-scope-retention.ts
2 src/renderer/src/components/automations/external/use-scoped-external-automations.ts
16 src/renderer/src/components/automations/runs/use-selected-automation-run-history.ts
7 src/renderer/src/components/browser-extension-install-dialog.tsx
1 src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.tsx
2 src/renderer/src/components/browser-pane/annotate/MarkupOverlay.tsx
@@ -542,16 +542,16 @@
4 src/renderer/src/components/sidebar/worktree-list/viewport/use-row-measurement.ts
1 src/renderer/src/components/sidebar/worktree-list/viewport/use-scroll-to-top.ts
1 src/renderer/src/components/sidebar/worktree-list/viewport/use-virtualizer.ts
1 src/renderer/src/components/skills/SkillBundleInstallFlow.tsx
2 src/renderer/src/components/skills/SkillInstallDialog.tsx
4 src/renderer/src/components/skills/SkillInstallManagementDialog.tsx
1 src/renderer/src/components/skills/SkillShareDialog.tsx
1 src/renderer/src/components/skills/SkillSharedLinkRow.tsx
1 src/renderer/src/components/skills/installation/SkillBundleInstallFlow.tsx
2 src/renderer/src/components/skills/installation/SkillInstallDialog.tsx
4 src/renderer/src/components/skills/installation/SkillInstallManagementDialog.tsx
1 src/renderer/src/components/skills/sharing/SkillShareDialog.tsx
1 src/renderer/src/components/skills/sharing/SkillSharedLinkRow.tsx
1 src/renderer/src/components/skills/SkillsPage.tsx
1 src/renderer/src/components/skills/setup/WslAgentSkillSetupPanel.tsx
2 src/renderer/src/components/skills/use-owned-skill-shares.ts
1 src/renderer/src/components/skills/use-reviewed-skill-plan.ts
1 src/renderer/src/components/skills/use-skill-delete-flow.ts
2 src/renderer/src/components/skills/sharing/use-owned-skill-shares.ts
1 src/renderer/src/components/skills/freshness/use-reviewed-skill-plan.ts
1 src/renderer/src/components/skills/deletion/use-skill-delete-flow.ts
1 src/renderer/src/components/sparse/SparseCheckoutPresetSelect.tsx
1 src/renderer/src/components/ssh-host-recovery/SshHostMoveAnywayAction.tsx
2 src/renderer/src/components/star-nag/StarNagAgentValueMomentObserver.tsx
@@ -651,7 +651,7 @@
2 src/renderer/src/components/terminal-pane/mobile-driver/MobileDriverOverlay.tsx
1 src/renderer/src/components/terminal-pane/mobile-driver/TerminalPaneMobileDriverPortals.tsx
1 src/renderer/src/components/terminal-pane/mounting/use-terminal-pane-mount-lifecycle.ts
2 src/renderer/src/components/terminal-pane/parking/lifecycle/use-terminal-tab-cold-parking.ts
1 src/renderer/src/components/terminal-pane/parking/lifecycle/use-terminal-tab-cold-parking.ts
1 src/renderer/src/components/terminal-pane/parking/retention/terminal-pending-startup-park-presence.ts
1 src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/recovery/TerminalPaneRecoveryPortals.tsx
2 src/renderer/src/components/terminal-pane/session-fork/TerminalAgentSessionForkDialog.tsx
@@ -693,21 +693,21 @@
1 src/renderer/src/hooks/composer-state/workspace-identity-state.ts
3 src/renderer/src/hooks/linear-issue-committed-state.ts
1 src/renderer/src/hooks/use-audio-capture.ts
1 src/renderer/src/hooks/use-bundled-skill-install.ts
1 src/renderer/src/hooks/skills/installation/use-bundled-skill-install.ts
2 src/renderer/src/hooks/useDetectedAgents.ts
2 src/renderer/src/hooks/useGitHubTaskItems.ts
2 src/renderer/src/hooks/useImmediateMutation.ts
6 src/renderer/src/hooks/useInstalledAgentSkills.ts
6 src/renderer/src/hooks/skills/discovery/useInstalledAgentSkills.ts
1 src/renderer/src/hooks/useIssueMetadata.ts
3 src/renderer/src/hooks/useLinearIssuePublication.ts
1 src/renderer/src/hooks/useMacTccAttributionSeveredNotice.ts
1 src/renderer/src/hooks/useSettingsNavigationMetadata.ts
4 src/renderer/src/hooks/useSkillFreshness.ts
4 src/renderer/src/hooks/skills/freshness/useSkillFreshness.ts
1 src/renderer/src/hooks/useTaskPageGitHubWorkItemMutation.ts
6 src/renderer/src/hooks/useVirtualizedScrollAnchor.ts
1 src/renderer/src/lib/host-chat-program-acquisition.ts
1 src/renderer/src/lib/quick-open-file-history.ts
2 src/renderer/src/lib/worker-auto-close.ts
1 src/renderer/src/lib/agent-session/launch/inventory/host-chat-program-acquisition.ts
1 src/renderer/src/lib/files/search/quick-open-file-history.ts
2 src/renderer/src/runtime/agent-orchestration/worker-auto-close.ts
1 src/renderer/src/runtime/native-chat/naming/structured-chat-name-observation.ts
1 src/renderer/src/store/plugins/appearance/plugin-icon-themes.ts
5 src/renderer/src/store/retained-views/app-state-view.ts
+3 -3
View File
@@ -85,9 +85,9 @@
1 src/renderer/src/components/terminal-pane/render-health/terminal-render-desync-sentinel.ts
1 src/renderer/src/hooks/use-now.ts
1 src/renderer/src/lib/agent-session/hibernation/agent-hibernation-coordinator.ts
1 src/renderer/src/lib/crash-diagnostics.ts
1 src/renderer/src/lib/window-visibility-interval.ts
1 src/renderer/src/lib/worker-auto-close.ts
1 src/renderer/src/diagnostics/crash-reporting/crash-diagnostics.ts
1 src/renderer/src/lib/window-activity/window-visibility-interval.ts
1 src/renderer/src/runtime/agent-orchestration/worker-auto-close.ts
1 src/renderer/src/runtime/hosts/wsl/wsl-refused-read-watch.ts
1 src/shared/remote-runtime/control/remote-runtime-shared-control-app-heartbeat.ts
1 src/shared/remote-runtime/remote-runtime-socket-liveness.ts
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -2,7 +2,7 @@
# This is a RATCHET: counts may only go DOWN. Use useTranslate()/useIntlLocale() from
# @/i18n/use-translate. Prune after a fix: pnpm check:render-path-translate-ratchet --prune
2 src/renderer/src/components/agent/AgentLaunchPromptComposer.tsx
5 src/renderer/src/components/automations/use-external-automation-actions.ts
5 src/renderer/src/components/automations/external/use-external-automation-actions.ts
1 src/renderer/src/components/diff-comments/useDiffCommentDecorator.tsx
2 src/renderer/src/components/editor/markdown-preview/render-tree/use-markdown-preview-components.tsx
1 src/renderer/src/components/tab-group/useTabGroupCreationCommands.ts
+1 -1
View File
@@ -17,7 +17,7 @@
4 src/renderer/src/components/terminal-pane/render-health/recovery/terminal-recovery-ledger-test-store.ts
3 src/renderer/src/components/worktree-jump/worktree-jump-palette-status-inputs.ts
1 src/renderer/src/lib/agent-session/hibernation/agent-hibernation-coordinator.ts
4 src/renderer/src/lib/floating-workspace-terminal-actions.ts
4 src/renderer/src/components/floating-terminal/floating-workspace-terminal-actions.ts
1 src/renderer/src/lib/pane-manager/client-hosted-browser-row-state.ts
4 src/renderer/src/lib/workspace/persistence/session-write-subscriber.ts
1 src/renderer/src/lib/workspace/persistence/workspace-session-browser-tabs.ts
@@ -1,4 +1,4 @@
import { readFile } from 'node:fs/promises'
import { readFile, readdir } from 'node:fs/promises'
import { readRouteSnapshot } from '../../mobile-web-app/mobile-web-app-route-snapshot.mjs'
import { MOBILE_WEB_APP_DEPENDENCY_PATH } from '../../mobile-web-app/mobile-web-app-bundle-dependencies.mjs'
import { existsSync, realpathSync } from 'node:fs'
@@ -457,6 +457,32 @@ export async function mobileWebAppModuleClosure(entryModules, { absWorkingDir }
// One lazily imported chunk per UI language; the page fetches only the active one after init.
const LOCALE_CATALOG_INPUT = /(^|\/)src\/i18n\/locales\/[^/]+\.json$/
const localeCatalogDir = join(mobileDir, 'src', 'i18n', 'locales')
// The same catalogs as JSON for the host to serve one at a time, to shells that skip the chunks;
// English is the page's inline fallback, so it has none.
export async function readLocaleCatalogs(directory = localeCatalogDir) {
const names = (await readdir(directory))
.filter((name) => name.endsWith('.json') && name !== 'en.json')
.sort()
return Promise.all(
names.map(async (name) => {
// Minify: the host sends and the phone keeps the parsed object, not the source layout.
const bytes = Buffer.from(
JSON.stringify(JSON.parse(await readFile(join(directory, name), 'utf8'))),
'utf8'
)
const sha256 = sha256Hex(bytes)
return {
locale: name.slice(0, -'.json'.length),
bytes,
path: `catalogs/${sha256}.json`,
sha256,
byteLength: bytes.byteLength
}
})
)
}
function localeCatalogOutputs(metafile) {
return new Set(
@@ -586,11 +612,13 @@ export async function buildMobileWebAppBundle({
desktopVersion,
protocolWindow,
{ script, chunks, images, catalogChunks, entryStaticBytes, chunkCount, routeChunks, routeKeys },
catalogs,
scrollbarStyle
] = await Promise.all([
readDesktopVersion(),
readProtocolWindow(),
bundleMobileWebApp({ appDir }),
readLocaleCatalogs(),
readFile(join(projectDir, 'src/shared/styles/scrollbar-sleek.css'), 'utf8').then((style) =>
// The shared desktop stylesheet can be checked out as CRLF on Windows.
style.replace(/\r\n/g, '\n')
@@ -598,9 +626,14 @@ export async function buildMobileWebAppBundle({
])
// Names already hash final bytes, so hashedAsset must reproduce them.
const scriptAsset = hashedAsset(script, 'js')
const catalogChunkSet = new Set(catalogChunks)
const written = [
scriptAsset,
...[...chunks, ...images].map(({ name, bytes }) => hashedAsset(bytes, extname(name).slice(1)))
...[...chunks, ...images].map((output) => ({
...hashedAsset(output.bytes, extname(output.name).slice(1)),
// Shells that read catalogs from the host skip these; older shells download them as before.
...(catalogChunkSet.has(output) ? { kind: 'catalog' } : {})
}))
]
// Root-absolute URLs survive nested routes; CSP allows only about: base. ESM entry/chunks load under existing self policy.
@@ -624,6 +657,7 @@ export async function buildMobileWebAppBundle({
const { manifest } = await writeMobileWebBundleTree({
outDir,
written: [indexAsset, ...written],
catalogs,
desktopVersion,
protocolWindow,
routes: resolveMobileWebPageRoutes(routeKeys, pageRoutes),
@@ -133,6 +133,8 @@ async function compressWithBrotli(written) {
export async function writeMobileWebBundleTree({
outDir,
written,
// Host-only JSON outside `assets` and buildId, served one at a time over RPC.
catalogs = [],
desktopVersion,
protocolWindow,
// Empty route list makes shell render every screen natively.
@@ -143,14 +145,15 @@ export async function writeMobileWebBundleTree({
}) {
const brotliCopies = brotli ? await compressWithBrotli(written) : new Map()
const assets = written
.map(({ path, sha256, byteLength, contentType }) => {
.map(({ path, sha256, byteLength, contentType, kind }) => {
const copy = brotliCopies.get(path)
return {
path,
sha256,
byteLength,
contentType,
...(copy === undefined ? {} : { brotliByteLength: copy.byteLength })
...(copy === undefined ? {} : { brotliByteLength: copy.byteLength }),
...(kind === undefined ? {} : { kind })
}
})
.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0))
@@ -164,14 +167,27 @@ export async function writeMobileWebBundleTree({
entrypoint: MOBILE_WEB_BUNDLE_ENTRYPOINT,
totalBytes: assets.reduce((total, asset) => total + asset.byteLength, 0),
assets,
routes
routes,
...(catalogs.length === 0
? {}
: {
catalogs: catalogs.map(({ locale, path, sha256, byteLength }) => ({
locale,
path,
sha256,
byteLength
}))
})
}
// Clear entire output or stale unreferenced assets ship inside asar.
await rm(outDir, { recursive: true, force: true })
await mkdir(join(outDir, 'assets'), { recursive: true })
for (const asset of written) {
await writeFile(join(outDir, asset.path), asset.bytes)
if (catalogs.length > 0) {
await mkdir(join(outDir, 'catalogs'), { recursive: true })
}
for (const file of [...written, ...catalogs]) {
await writeFile(join(outDir, file.path), file.bytes)
}
for (const [path, copy] of brotliCopies) {
await writeFile(join(outDir, mobileWebBundleBrotliPath(path)), copy)
@@ -29,6 +29,8 @@ export function mobileWebAppBundleMaxAssets(routeCount, imageCount, catalogChunk
// fetched only for the active language, never before the first route. Code keeps its own fence.
export const MOBILE_WEB_APP_BUNDLE_MAX_CATALOG_CHUNKS = 10
export const MOBILE_WEB_APP_BUNDLE_MAX_CATALOG_BYTES = 3 * 1024 * 1024
// The host sends a catalog's JSON in one reply, which must fit a 1 MiB relay frame once encrypted.
export const MOBILE_WEB_APP_HOST_CATALOG_MAX_BYTES = 384 * 1024
// Our own fence on the page's code, well inside the phone contract (32 MiB total, 10 MiB per asset in
// manifest-contract.ts); it fails the build before the code weighs more than any one asset may.
@@ -185,6 +187,23 @@ export async function verifyMobileWebAppBundle({ bundleDir = defaultBundleDir }
`${String(MOBILE_WEB_APP_BUNDLE_MAX_CATALOG_BYTES)} bytes`
)
}
// buildId leaves the host's JSON catalogs out, so a stale copy shows only here.
const hostCatalogsOf = (built) => JSON.stringify(built.catalogs ?? [])
if (hostCatalogsOf(first.manifest) !== hostCatalogsOf(second.manifest)) {
fail('host language catalogs are not reproducible')
}
if (hostCatalogsOf(first.manifest) !== hostCatalogsOf(manifest)) {
fail(`${bundleDir} is stale: its host language catalogs differ from a fresh build's`)
}
const oversized = (manifest.catalogs ?? []).find(
(catalog) => catalog.byteLength > MOBILE_WEB_APP_HOST_CATALOG_MAX_BYTES
)
if (oversized) {
fail(
`the ${oversized.locale} host catalog is ${String(oversized.byteLength)} bytes, over the ` +
`${String(MOBILE_WEB_APP_HOST_CATALOG_MAX_BYTES)} one reply carries`
)
}
const budgetBytes = mobileWebAppBundleBudgetBytes(manifest) - first.catalogBytes
if (budgetBytes > MOBILE_WEB_APP_BUNDLE_MAX_TOTAL_BYTES) {
fail(
+55 -197
View File
@@ -4622,11 +4622,9 @@
"src/preload/usage-provider-api.test.ts": 46,
"src/renderer/src/app-shell/app-command-handlers-tab-rename.test.ts": 1786,
"src/renderer/src/app-shell/shutdown-checkpoint-persist.test.ts": 65,
"src/renderer/src/app-shell/shutdown-checkpoint-restart-lifecycle.test.ts": 210,
"src/renderer/src/app-shell/startup-window-parity.test.ts": 2306,
"src/renderer/src/app-shell/use-profile-state-save-delay-notice.sonner.test.tsx": 355,
"src/renderer/src/app-shell/use-profile-state-save-delay-notice.test.tsx": 293,
"src/renderer/src/app-shell/use-visible-hosted-review-refresh.test.tsx": 572,
"src/renderer/src/app-shell/visible-review-reporting.test.ts": 1872,
"src/renderer/src/app-shell/workspace-view-cross-client-sync.test.tsx": 1170,
"src/renderer/src/app-shell/worktree-creation-chat-handoff.test.tsx": 2824,
@@ -4641,12 +4639,8 @@
"src/renderer/src/components/JiraIssueWorkspace.optimistic-edit.test.tsx": 2340,
"src/renderer/src/components/JiraIssueWorkspace.status-queue.test.tsx": 1852,
"src/renderer/src/components/JiraIssueWorkspace.transition-source.test.tsx": 1843,
"src/renderer/src/components/native-chat-recovery/integration/NativeChatRemoteResume.test.tsx": 8774,
"src/renderer/src/components/native-chat-recovery/tree/NativeChatResumeOnRestartGroups.selection.test.tsx": 2633,
"src/renderer/src/components/native-chat-recovery/tree/NativeChatResumeOnRestartGroups.test.tsx": 3472,
"src/renderer/src/components/NewWorkspaceComposerCard.quick-file-drop.test.tsx": 4557,
"src/renderer/src/components/NewWorkspaceComposerCard.set-location.test.tsx": 1301,
"src/renderer/src/components/QuickOpen.selection.test.tsx": 1046,
"src/renderer/src/components/SelectedTextCopyMenu.test.tsx": 329,
"src/renderer/src/components/TerminalSearch.test.tsx": 458,
"src/renderer/src/components/UpdateCard.error-card.test.tsx": 2851,
@@ -4675,56 +4669,46 @@
"src/renderer/src/components/artifacts/ArtifactPublishButton.test.tsx": 706,
"src/renderer/src/components/artifacts/ArtifactsPage.test.tsx": 2052,
"src/renderer/src/components/artifacts/artifact-publish-flow.test.ts": 114,
"src/renderer/src/components/automations/AutomationEditorPromptEditor.test.tsx": 396,
"src/renderer/src/components/automations/authoring/AutomationEditorPromptEditor.test.tsx": 396,
"src/renderer/src/components/automations/AutomationPromptDisclosure.test.tsx": 893,
"src/renderer/src/components/automations/AutomationRunsTable.test.tsx": 668,
"src/renderer/src/components/automations/AutomationSchedulePicker.test.ts": 431,
"src/renderer/src/components/automations/AutomationTimeField.test.tsx": 654,
"src/renderer/src/components/automations/authoring/AutomationSchedulePicker.test.ts": 431,
"src/renderer/src/components/automations/AutomationsPage.behavior.test.tsx": 3389,
"src/renderer/src/components/automations/AutomationsPage.edit-destination.test.tsx": 2932,
"src/renderer/src/components/automations/AutomationsPage.run-navigation.test.tsx": 3056,
"src/renderer/src/components/automations/ExternalAutomationManagers.test.tsx": 852,
"src/renderer/src/components/automations/automation-captured-owner.test.ts": 379,
"src/renderer/src/components/automations/automation-create-projects.test.ts": 42,
"src/renderer/src/components/automations/hosts/automation-captured-owner.test.ts": 379,
"src/renderer/src/components/automations/automation-host-cache-controller.test.ts": 411,
"src/renderer/src/components/automations/automation-host-cache-error.test.ts": 391,
"src/renderer/src/components/automations/automation-host-cache-health.test.ts": 52,
"src/renderer/src/components/automations/automation-host-cache.test.ts": 79,
"src/renderer/src/components/automations/automation-host-catalog-generation.test.ts": 258,
"src/renderer/src/components/automations/automation-host-client.test.ts": 374,
"src/renderer/src/components/automations/hosts/automation-host-cache.test.ts": 79,
"src/renderer/src/components/automations/automation-host-diagnostics.test.ts": 460,
"src/renderer/src/components/automations/automation-host-filter-resolution.test.ts": 256,
"src/renderer/src/components/automations/automation-host-health.test.ts": 47,
"src/renderer/src/components/automations/automation-host-invalidation-window-events.test.ts": 83,
"src/renderer/src/components/automations/automation-host-invalidation.test.ts": 85,
"src/renderer/src/components/automations/automation-host-list-rows.test.ts": 419,
"src/renderer/src/components/automations/automation-host-recovery.test.ts": 51,
"src/renderer/src/components/automations/hosts/automation-host-list-rows.test.ts": 419,
"src/renderer/src/components/automations/automation-host-scale-gates.test.ts": 638,
"src/renderer/src/components/automations/automation-host-scheduler.test.ts": 439,
"src/renderer/src/components/automations/automation-legacy-schedule-editing.test.ts": 449,
"src/renderer/src/components/automations/automation-list-focus-recovery.test.ts": 48,
"src/renderer/src/components/automations/automation-list-row-identity.test.ts": 626,
"src/renderer/src/components/automations/automation-list-search.test.ts": 75,
"src/renderer/src/components/automations/hosts/automation-host-scheduler.test.ts": 439,
"src/renderer/src/components/automations/authoring/automation-legacy-schedule-editing.test.ts": 449,
"src/renderer/src/components/automations/automation-list-view.test.ts": 257,
"src/renderer/src/components/automations/automation-run-context.test.ts": 58,
"src/renderer/src/components/automations/automation-run-history-keyboard-navigation.test.ts": 434,
"src/renderer/src/components/automations/automation-run-open-target.test.ts": 442,
"src/renderer/src/components/automations/runs/automation-run-history-keyboard-navigation.test.ts": 434,
"src/renderer/src/components/automations/runs/automation-run-open-target.test.ts": 442,
"src/renderer/src/components/automations/automation-run-output-snapshot-equivalence.test.ts": 158,
"src/renderer/src/components/automations/automation-run-output-snapshot.test.ts": 96,
"src/renderer/src/components/automations/automation-setup-decision.test.ts": 58,
"src/renderer/src/components/automations/authoring/automation-setup-decision.test.ts": 58,
"src/renderer/src/components/automations/automation-usage-model.test.ts": 200,
"src/renderer/src/components/automations/external-automation-list-entries.test.ts": 57,
"src/renderer/src/components/automations/external-automation-scope-gating.test.ts": 67,
"src/renderer/src/components/automations/external/external-automation-scope-gating.test.ts": 67,
"src/renderer/src/components/automations/external-automation-scope-keys.test.ts": 53,
"src/renderer/src/components/automations/use-automation-host-catalog-readoption.test.tsx": 802,
"src/renderer/src/components/automations/use-automation-host-catalog.test.tsx": 920,
"src/renderer/src/components/automations/use-automation-list-focus-recovery.test.tsx": 229,
"src/renderer/src/components/automations/use-automation-list-search.test.tsx": 679,
"src/renderer/src/components/automations/use-automation-runs-dashboard-payload.test.tsx": 6692,
"src/renderer/src/components/automations/use-automation-runs-dashboard.test.tsx": 854,
"src/renderer/src/components/automations/hosts/use-automation-host-catalog-readoption.test.tsx": 802,
"src/renderer/src/components/automations/hosts/use-automation-host-catalog.test.tsx": 920,
"src/renderer/src/components/automations/listing/use-automation-list-search.test.tsx": 679,
"src/renderer/src/components/automations/runs/use-automation-runs-dashboard-payload.test.tsx": 6692,
"src/renderer/src/components/automations/runs/use-automation-runs-dashboard.test.tsx": 854,
"src/renderer/src/components/automations/use-selected-automation-run-history.test.tsx": 2104,
"src/renderer/src/components/browser-favicon.test.tsx": 333,
"src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.dead-guest.test.tsx": 6219,
"src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.drag-focus.test.tsx": 5826,
"src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.failure-remount.test.tsx": 3085,
"src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.markup.test.tsx": 4575,
@@ -4821,7 +4805,6 @@
"src/renderer/src/components/dashboard-popout/preview-terminal-box-fit-lifecycle.test.ts": 192,
"src/renderer/src/components/dashboard-popout/preview-terminal-ime-bridge-kitty-bytes.test.ts": 479,
"src/renderer/src/components/dashboard-popout/remote-runtime-terminal-preview-api.test.ts": 81,
"src/renderer/src/components/dashboard-popout/use-agent-dashboard-telemetry.test.ts": 2108,
"src/renderer/src/components/dashboard-popout/useDashboardSnapshot.test.tsx": 432,
"src/renderer/src/components/dashboard/AgentDashboardDrawer.focus.test.tsx": 2567,
"src/renderer/src/components/dashboard/AgentDashboardDrawer.test.tsx": 1981,
@@ -4843,14 +4826,11 @@
"src/renderer/src/components/diff-comments/diff-comment-popover-outside-click.test.tsx": 1889,
"src/renderer/src/components/diff-comments/useDiffCommentDecorator.lifecycle.test.tsx": 2669,
"src/renderer/src/components/diff-comments/useDiffCommentDecorator.range-selection.test.tsx": 1782,
"src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.captured-failure.test.tsx": 2412,
"src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.copy.test.tsx": 3108,
"src/renderer/src/components/editor/check-details/panel/CheckRunDetailsPanel.log-outcomes.test.tsx": 3230,
"src/renderer/src/components/editor/DiagramViewerModal.test.tsx": 2539,
"src/renderer/src/components/editor/diff/sections/DiffSectionBody.word-wrap-lifecycle.test.tsx": 3036,
"src/renderer/src/components/editor/diff/monaco-view/DiffViewer.word-wrap-lifecycle.test.tsx": 2499,
"src/renderer/src/components/editor/EditorConflictReviewSurface.live-record.test.tsx": 1193,
"src/renderer/src/components/editor/EditorContent.monaco-lifecycle.test.tsx": 3162,
"src/renderer/src/components/editor/EditorEditFileSurface.monaco-containment.test.tsx": 2666,
"src/renderer/src/components/editor/EditorFileLoadErrorView.test.tsx": 660,
"src/renderer/src/components/editor/EditorFileLocationMenuItems.test.tsx": 846,
@@ -4862,17 +4842,13 @@
"src/renderer/src/components/editor/notebook/document/IpynbRendering.test.tsx": 2143,
"src/renderer/src/components/editor/markdown-preview/surface/MarkdownPreview.link-routing.interaction.test.tsx": 1127,
"src/renderer/src/components/editor/MediaViewer.test.tsx": 308,
"src/renderer/src/components/editor/MermaidViewer.test.tsx": 3079,
"src/renderer/src/components/editor/MonacoEditor.owner-view-state.test.tsx": 7073,
"src/renderer/src/components/editor/PdfFind.test.tsx": 581,
"src/renderer/src/components/editor/PluginBytesCustomEditorSurface.test.tsx": 641,
"src/renderer/src/components/editor/PluginEditorNeedsDesktopNotice.test.tsx": 2107,
"src/renderer/src/components/editor/PluginFileTypeSuggestion.test.tsx": 2062,
"src/renderer/src/components/editor/rich-markdown/instance/RichMarkdownEditor.focus-leave.test.tsx": 4927,
"src/renderer/src/components/editor/rich-markdown/instance/RichMarkdownErrorBoundary.lazy-chunk.test.tsx": 367,
"src/renderer/src/components/editor/ai-vault-session-log-content.test.tsx": 1899,
"src/renderer/src/components/editor/check-details/actions/check-annotation-path.test.ts": 53,
"src/renderer/src/components/editor/check-details/reading/check-details-reading-viewport.test.ts": 259,
"src/renderer/src/components/editor/model-lifetime/retention/closed-editor-model-lifetime.test.ts": 7921,
"src/renderer/src/components/editor/model-lifetime/retention/closed-editor-model-shell.test.tsx": 4492,
"src/renderer/src/components/editor/model-lifetime/retention/closed-editor-tab-disposal.test.ts": 121,
@@ -4976,7 +4952,6 @@
"src/renderer/src/components/editor/monaco-programmatic-sync.test.ts": 46,
"src/renderer/src/components/editor/pdf-orca-media-range-transport.test.ts": 282,
"src/renderer/src/components/editor/pdf-view-position.test.ts": 74,
"src/renderer/src/components/editor/pdf-viewer-failed-load-recovery.test.tsx": 2363,
"src/renderer/src/components/editor/pdf-viewer-zoom-ownership.test.tsx": 789,
"src/renderer/src/components/editor/plugin-custom-editor-host.test.ts": 480,
"src/renderer/src/components/editor/rich-markdown/raw-source/raw-markdown-html-line-break.test.ts": 2188,
@@ -5047,17 +5022,13 @@
"src/renderer/src/components/editor/use-monaco-editor-mount.cursor-owner.test.tsx": 1332,
"src/renderer/src/components/editor/use-monaco-editor-mount.stale-cache.test.tsx": 877,
"src/renderer/src/components/editor/use-monaco-reveal-scheduler.test.tsx": 273,
"src/renderer/src/components/editor/use-pdf-viewer-find-shortcut.test.tsx": 907,
"src/renderer/src/components/editor/use-preserve-section-during-external-edit.test.tsx": 268,
"src/renderer/src/components/editor/rich-markdown/tables/use-rich-markdown-table-context-menu.test.tsx": 2238,
"src/renderer/src/components/editor/useEditorPanelContentState.test.tsx": 3214,
"src/renderer/src/components/editor/external-changes/useEditorPanelExternalContentEvents.test.tsx": 2018,
"src/renderer/src/components/editor/useEditorPanelFloatingTabContentState.test.tsx": 889,
"src/renderer/src/components/editor/useEditorPanelHostUnresolvedContentState.test.tsx": 1952,
"src/renderer/src/components/editor/useEditorPanelVisibilityContentState.test.tsx": 2006,
"src/renderer/src/components/editor/notebook/kernel/useIpynbCellExecution.test.tsx": 695,
"src/renderer/src/components/editor/notebook/document/useIpynbDocumentEditing.test.tsx": 2192,
"src/renderer/src/components/editor/notebook/document/useIpynbScrollRestoration.test.tsx": 323,
"src/renderer/src/components/editor/rich-markdown/links/useLinkBubble.test.tsx": 493,
"src/renderer/src/components/editor/rich-markdown/images/useLocalImagePick.test.tsx": 1319,
"src/renderer/src/components/editor/document-images/useLocalImageSrc.test.ts": 736,
@@ -5081,11 +5052,7 @@
"src/renderer/src/components/emulator-pane/emulator-screen-stream-content.test.tsx": 408,
"src/renderer/src/components/emulator-pane/use-emulator-pane-session.test.tsx": 1968,
"src/renderer/src/components/emulator-pane/use-mobile-emulator-tab-intro-actions.test.tsx": 1828,
"src/renderer/src/components/error-boundaries/RecoverableRenderErrorBoundary.lazy-chunk.test.tsx": 451,
"src/renderer/src/components/error-boundaries/RecoverableRenderErrorBoundary.never-landed-reload.test.tsx": 449,
"src/renderer/src/components/feature-tips/ChatUiHereCornerCard.test.tsx": 2798,
"src/renderer/src/components/feature-tips/ChatUiHereFeatureTipVisual.test.tsx": 1093,
"src/renderer/src/components/feature-tips/ChatUiHereTip.test.tsx": 6788,
"src/renderer/src/components/feature-tips/feature-tip-modal-state.test.ts": 244,
"src/renderer/src/components/feature-wall/FeatureWallSetupWorkflowActions.test.tsx": 2028,
"src/renderer/src/components/feature-wall/FullDiskAccessSetupPrompt.test.ts": 466,
@@ -5135,15 +5102,9 @@
"src/renderer/src/components/mobile/WindowsFirewallNotice.test.tsx": 691,
"src/renderer/src/components/mobile/paired-mobile-devices.test.ts": 192,
"src/renderer/src/components/mobile/use-mobile-page-paired-devices.test.ts": 475,
"src/renderer/src/components/native-chat-recovery/offers/native-chat-launch-resume-decision.test.tsx": 1844,
"src/renderer/src/components/native-chat-recovery/integration/native-chat-resume-bulk-progress.test.tsx": 3261,
"src/renderer/src/components/native-chat-recovery/offers/native-chat-resume-failed-chat-watch.test.ts": 1954,
"src/renderer/src/components/native-chat-recovery/failures/native-chat-resume-failure-guidance.test.ts": 240,
"src/renderer/src/components/native-chat-recovery/integration/native-chat-resume-finished-run.test.tsx": 4173,
"src/renderer/src/components/native-chat-recovery/tree/native-chat-resume-on-restart-grouping.test.ts": 117,
"src/renderer/src/components/native-chat-recovery/integration/native-chat-resume-result-view.test.tsx": 2723,
"src/renderer/src/components/native-chat-recovery/runs/native-chat-resume-run.test.ts": 65,
"src/renderer/src/components/native-chat-recovery/integration/native-chat-resume-skipped-progress.test.tsx": 3009,
"src/renderer/src/components/native-chat/messages/rows/NativeChatAgentMessageCard.test.tsx": 3700,
"src/renderer/src/components/native-chat/tools/tasks/NativeChatBackgroundTaskRun.test.tsx": 242,
"src/renderer/src/components/native-chat/tools/tasks/NativeChatBackgroundTasksStatus.test.tsx": 782,
@@ -5195,9 +5156,7 @@
"src/renderer/src/components/native-chat/selection-quotes/NativeChatSelectionQuote.test.tsx": 2370,
"src/renderer/src/components/native-chat/surfaces/integration/NativeChatStructuredSession.approval-and-start-failure.test.tsx": 2922,
"src/renderer/src/components/native-chat/surfaces/integration/NativeChatStructuredSession.availability-notice.test.tsx": 2764,
"src/renderer/src/components/native-chat/surfaces/integration/NativeChatStructuredSession.history-read-state.test.tsx": 4919,
"src/renderer/src/components/native-chat/surfaces/integration/NativeChatStructuredSession.host-outage.test.tsx": 3552,
"src/renderer/src/components/native-chat/surfaces/integration/NativeChatStructuredSession.notice-card.test.tsx": 2911,
"src/renderer/src/components/native-chat/surfaces/integration/NativeChatStructuredSession.refusal-notices.test.tsx": 3014,
"src/renderer/src/components/native-chat/surfaces/integration/NativeChatStructuredSession.test.tsx": 4459,
"src/renderer/src/components/native-chat/tools/tasks/NativeChatStructuredSessionStatus.test.tsx": 583,
@@ -5238,10 +5197,8 @@
"src/renderer/src/components/native-chat/attachments/intake/native-chat-composer-drop-scope.test.tsx": 1409,
"src/renderer/src/components/native-chat/attachments/intake/native-chat-composer-ime-file-drop.test.tsx": 1326,
"src/renderer/src/components/native-chat/drafts/native-chat-composer-prompt-recall.test.tsx": 1724,
"src/renderer/src/components/native-chat/composer/focus/native-chat-composer-reveal-focus.test.tsx": 526,
"src/renderer/src/components/native-chat/attachments/intake/native-chat-composer-workspace-file-drop.test.tsx": 1166,
"src/renderer/src/components/native-chat/drafts/native-chat-draft-retention.test.ts": 1415,
"src/renderer/src/components/native-chat/attachments/intake/native-chat-element-file-drop.test.tsx": 2057,
"src/renderer/src/components/native-chat/attachments/intake/native-chat-external-attachments-destination.test.tsx": 951,
"src/renderer/src/components/native-chat/links/actions/native-chat-file-link-existence.test.ts": 1937,
"src/renderer/src/components/native-chat/pane/actions/native-chat-header-ownership.test.tsx": 2796,
@@ -5256,7 +5213,6 @@
"src/renderer/src/components/native-chat/native-chat-new-rows-marker.test.tsx": 581,
"src/renderer/src/components/native-chat/pane/routing/native-chat-open-wake.test.ts": 1807,
"src/renderer/src/components/native-chat/pane/ownership/native-chat-owner-selectors.test.tsx": 525,
"src/renderer/src/components/native-chat/attachments/intake/native-chat-pane-file-drop.test.tsx": 439,
"src/renderer/src/components/native-chat/paste/native-chat-paste-routing.test.tsx": 2417,
"src/renderer/src/components/native-chat/session/terminal/delivery/native-chat-pending.test.ts": 273,
"src/renderer/src/components/native-chat/composer/editor/native-chat-prompt-document-text.test.ts": 219,
@@ -5304,7 +5260,6 @@
"src/renderer/src/components/native-chat/conversation/projection/use-native-chat-context-clear-floor.test.tsx": 271,
"src/renderer/src/components/native-chat/pane/actions/use-native-chat-context-menu.targets.test.tsx": 3071,
"src/renderer/src/components/native-chat/attachments/intake/use-native-chat-external-attachments.test.tsx": 797,
"src/renderer/src/components/native-chat/viewport/find/use-native-chat-find.test.tsx": 1458,
"src/renderer/src/components/native-chat/session/status/use-native-chat-hook-status.test.ts": 1927,
"src/renderer/src/components/native-chat/host-access/use-native-chat-host-outage.test.tsx": 2131,
"src/renderer/src/components/native-chat/drafts/use-native-chat-launch-draft-adoption.test.tsx": 319,
@@ -5340,8 +5295,6 @@
"src/renderer/src/components/new-workspace/DirectWorkItemSetupDialog.test.tsx": 5296,
"src/renderer/src/components/new-workspace/NewWorkspaceComposerNameSection.test.tsx": 2493,
"src/renderer/src/components/new-workspace/ProjectCombobox.dialog-handoff.test.tsx": 1043,
"src/renderer/src/components/new-workspace/ProjectCombobox.test.tsx": 848,
"src/renderer/src/components/new-workspace/ProjectComboboxRow.test.tsx": 415,
"src/renderer/src/components/new-workspace/RunTargetCombobox.host-identity.test.tsx": 856,
"src/renderer/src/components/new-workspace/SetProjectLocationDialog.host-identity.test.tsx": 1094,
"src/renderer/src/components/new-workspace/SetProjectLocationDialog.test.tsx": 1387,
@@ -5355,7 +5308,6 @@
"src/renderer/src/components/new-workspace/use-recent-project-ids.test.ts": 1730,
"src/renderer/src/components/new-workspace/use-smart-workspace-name-field-foundation.source.test.tsx": 2290,
"src/renderer/src/components/new-workspace/work-item-source-selection.test.ts": 1736,
"src/renderer/src/components/onboarding/OnboardingInlineCommandTerminal.command-finished.test.tsx": 681,
"src/renderer/src/components/onboarding/OnboardingLanguageLoading.test.tsx": 2167,
"src/renderer/src/components/onboarding/onboarding-feature-setup.test.ts": 76,
"src/renderer/src/components/pet/pet-blob-cache.test.ts": 64,
@@ -5419,7 +5371,6 @@
"src/renderer/src/components/right-sidebar/ai-vault/scanning/ai-vault-panel-session-list-request.test.tsx": 3044,
"src/renderer/src/components/right-sidebar/ai-vault/search/ai-vault-search-session.test.ts": 201,
"src/renderer/src/components/right-sidebar/ai-vault/accounts/ai-vault-session-account-render.test.tsx": 2885,
"src/renderer/src/components/right-sidebar/ai-vault/scanning/ai-vault-session-conditional-refresh-hook.test.tsx": 2370,
"src/renderer/src/components/right-sidebar/ai-vault/session-list/ai-vault-session-filters.test.ts": 105,
"src/renderer/src/components/right-sidebar/ai-vault/scope/ai-vault-session-projects.test.ts": 117,
"src/renderer/src/components/right-sidebar/ai-vault/scanning/ai-vault-session-refresh.test.ts": 2034,
@@ -5435,7 +5386,6 @@
"src/renderer/src/components/right-sidebar/checks-panel/gitlab-review-client.test.ts": 321,
"src/renderer/src/components/right-sidebar/checks-panel/comments/review-comment-markdown.test.tsx": 2171,
"src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-panel-manual-refresh-recovery.test.tsx": 2909,
"src/renderer/src/components/right-sidebar/checks-panel/refresh/use-checks-panel-polling.test.tsx": 2189,
"src/renderer/src/lib/repository-refresh/coalesced-poll-runner.test.ts": 61,
"src/renderer/src/components/right-sidebar/file-explorer/mutations/file-explorer-delete-undo-precondition.test.tsx": 2023,
"src/renderer/src/components/right-sidebar/file-explorer/tree/file-explorer-dir-pages.test.ts": 1767,
@@ -5457,7 +5407,6 @@
"src/renderer/src/components/right-sidebar/plugin-panels/health/plugin-panel-watchdog.test.ts": 116,
"src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comments-ai-launch-ack.test.ts": 118,
"src/renderer/src/components/right-sidebar/checks-panel/comments/pr-comments-list-selection.test.tsx": 3290,
"src/renderer/src/components/right-sidebar/right-sidebar-titlebar-drag-regions.render.test.tsx": 1044,
"src/renderer/src/components/right-sidebar/source-control/ai/runSourceControlAgentActionStart.test.ts": 1917,
"src/renderer/src/components/right-sidebar/source-control/review/source-control-create-pr-intent-flow.test.ts": 71,
"src/renderer/src/components/right-sidebar/source-control/review/source-control-manual-review-url.test.ts": 77,
@@ -5485,7 +5434,6 @@
"src/renderer/src/components/right-sidebar/file-explorer/mutations/useFileDeletion.save-refusal.test.tsx": 840,
"src/renderer/src/components/right-sidebar/file-explorer/mutations/useFileDeletion.trash-failure.test.tsx": 774,
"src/renderer/src/components/right-sidebar/file-explorer/mutations/useFileExplorerImport.committed-scope.test.tsx": 588,
"src/renderer/src/components/right-sidebar/file-explorer/mutations/useFileExplorerImport.remote.test.tsx": 2054,
"src/renderer/src/components/right-sidebar/file-explorer/tree/useFileExplorerTree.lifecycle.test.tsx": 1925,
"src/renderer/src/components/right-sidebar/file-explorer/tree/useFileExplorerTree.paged-dirs.test.tsx": 2484,
"src/renderer/src/components/right-sidebar/file-explorer/tree/useFileExplorerTree.scoped-root.test.tsx": 2131,
@@ -5495,10 +5443,7 @@
"src/renderer/src/components/right-sidebar/hosted-review/provider-actions/useHostedReviewStackParent.test.tsx": 288,
"src/renderer/src/components/right-sidebar/source-control/listing/useSourceControlSubmoduleStatus.test.tsx": 2076,
"src/renderer/src/components/settings/accounts/AccountsPane.owner-scope.test.tsx": 4057,
"src/renderer/src/components/skills/setup/AgentSkillSetupPanel.freshness.test.tsx": 15418,
"src/renderer/src/components/skills/setup/AgentSkillSetupPanel.plan-status.test.tsx": 23334,
"src/renderer/src/components/skills/setup/AgentSkillSetupPanel.remote-availability.test.tsx": 5937,
"src/renderer/src/components/skills/setup/AgentSkillSetupPanel.test.tsx": 2315,
"src/renderer/src/components/settings/agents/AgentsPane.test.tsx": 3230,
"src/renderer/src/components/settings/accounts/providers/AntigravityAccountsSection.test.tsx": 2722,
"src/renderer/src/components/settings/appearance/chat/AppearanceChatContrastControls.draft.test.tsx": 795,
@@ -5506,10 +5451,8 @@
"src/renderer/src/components/settings/browser/BrowserSitePermissionsSetting.test.tsx": 739,
"src/renderer/src/components/settings/browser/BrowserUserAgentSetting.test.tsx": 2325,
"src/renderer/src/components/settings/chat/ChatNamingSetting.test.tsx": 2454,
"src/renderer/src/components/settings/chat/ChatPermissionSetting.test.tsx": 834,
"src/renderer/src/components/settings/chat/ChatSettingsSection.navigation.test.tsx": 2540,
"src/renderer/src/components/settings/chat/ChatSettingsSection.test.tsx": 2718,
"src/renderer/src/lib/skill-runtime-command.test.ts": 1670,
"src/renderer/src/lib/skills/runtime/skill-runtime-command.test.ts": 1670,
"src/renderer/src/components/settings/virtual-machines/recipes/EphemeralVmsPane.test.tsx": 1025,
"src/renderer/src/components/settings/accounts/sign-in/HostAccountSignInPrompt.test.tsx": 309,
"src/renderer/src/components/settings/agents/launch/HostAgentLaunchSettings.test.tsx": 7498,
@@ -5520,7 +5463,6 @@
"src/renderer/src/components/settings/advanced/network/NodeDistMirrorDialog.test.tsx": 2412,
"src/renderer/src/components/plugins/list/PluginsSettingsSection.lifecycle.test.tsx": 5402,
"src/renderer/src/components/settings/runtime-environments/pairing/RuntimePairingGeneratorForm.test.tsx": 461,
"src/renderer/src/components/settings/session-history/SessionHistorySettingsPane.test.tsx": 1474,
"src/renderer/src/components/settings/appearance/fonts/FontAutocomplete.test.tsx": 1202,
"src/renderer/src/components/settings/form-controls/SettingsFormControls.segmented-control.test.tsx": 730,
"src/renderer/src/components/settings/shortcuts/ShortcutFilterRail.test.ts": 273,
@@ -5535,12 +5477,9 @@
"src/renderer/src/components/settings/TerminalAppearanceSection.ghostty.test.ts": 549,
"src/renderer/src/components/settings/terminal/appearance/TerminalContrastSetting.test.tsx": 798,
"src/renderer/src/components/settings/terminal/session-management/TerminalLimitSetting.test.tsx": 1015,
"src/renderer/src/components/settings/terminal/TerminalPane.section-lifetime.test.tsx": 1237,
"src/renderer/src/components/settings/terminal/appearance/TerminalSettingsPreview.lifecycle.test.tsx": 595,
"src/renderer/src/components/settings/developer-permissions/TerminalTccAttributionNotice.test.tsx": 401,
"src/renderer/src/components/settings/voice/VoiceMicrophoneSetting.test.tsx": 957,
"src/renderer/src/components/settings/voice/VoicePane.test.tsx": 1042,
"src/renderer/src/components/settings/voice/VoiceSpeechModelSection.test.tsx": 503,
"src/renderer/src/components/settings/accounts/sign-in/account-add-cancel-target.test.ts": 217,
"src/renderer/src/components/settings/accounts/usage/accounts-pane-opencode-credentials.test.ts": 519,
"src/renderer/src/lib/agent-session/launch/agent-settings-update-queue.test.ts": 249,
@@ -5550,7 +5489,6 @@
"src/renderer/src/components/settings/developer-permissions/developer-permissions-search.test.ts": 308,
"src/renderer/src/components/settings/shortcuts/keybinding-diagnostic-text.test.ts": 522,
"src/renderer/src/components/settings/shortcuts/keybinding-title-copy.test.ts": 227,
"src/renderer/src/lib/agent-session/launch/native-chat-provider-choice.test.tsx": 3738,
"src/renderer/src/components/settings/notifications/notification-host-pending-save.test.tsx": 2029,
"src/renderer/src/components/settings/repository/RepositoryJiraProjectSection.test.tsx": 463,
"src/renderer/src/components/settings/repository/RepositoryPaneDraftInput.test.tsx": 314,
@@ -5572,10 +5510,7 @@
"src/renderer/src/components/settings/runtime-environments/catalog/use-runtime-environment-mutation-actions.test.tsx": 2069,
"src/renderer/src/components/session-search/use-session-search-status.test.tsx": 477,
"src/renderer/src/components/settings/accounts/usage/use-zcode-plan-credentials.test.tsx": 880,
"src/renderer/src/lib/directory-path-suggestion-filter.scan-budget.test.ts": 68,
"src/renderer/src/lib/directory-path-suggestion-filter.test.ts": 51,
"src/renderer/src/components/setup-guide/SetupGuideModal.mount-gating.test.tsx": 2348,
"src/renderer/src/components/shared/MacFolderAccessFixDialog.test.tsx": 1356,
"src/renderer/src/components/shared/kill-all-terminal-surfaces.test.ts": 1925,
"src/renderer/src/components/shared/useDaemonActions.test.tsx": 332,
"src/renderer/src/components/sidebar/host-admission/ssh/AddRemoteHostDialog.config-picker.test.tsx": 1178,
@@ -5597,7 +5532,6 @@
"src/renderer/src/components/sidebar/SeveredTerminalDaemonBanner.test.tsx": 574,
"src/renderer/src/components/sidebar/Sidebar.test.tsx": 833,
"src/renderer/src/components/sidebar/SidebarAgentsList.test.tsx": 2985,
"src/renderer/src/components/sidebar/sidebar-feedback/SidebarFeedbackDialog.test.tsx": 2529,
"src/renderer/src/components/sidebar/StalledTerminalDaemonBanner.test.tsx": 320,
"src/renderer/src/components/sidebar/workspace-board/viewport/WorkspaceKanbanLaneGrid.test.tsx": 360,
"src/renderer/src/components/sidebar/workspace-card/surface/WorktreeCard.single-reference-details.test.tsx": 2032,
@@ -5690,22 +5624,17 @@
"src/renderer/src/components/sidebar/workspace-actions/parenting/worktree-parent-eligibility.test.ts": 68,
"src/renderer/src/components/sidebar/worktree-list/listing/worktree-sort-label-ordering.test.ts": 114,
"src/renderer/src/components/sidebar/wsl-host-offer-move.test.ts": 227,
"src/renderer/src/components/skills/SkillFreshnessUpdateDialog.test.tsx": 913,
"src/renderer/src/components/skills/SkillInstallDialog.test.tsx": 4429,
"src/renderer/src/components/skills/SkillInstallManagementDialog.test.tsx": 3300,
"src/renderer/src/components/skills/SkillSharedLinkRow.test.tsx": 817,
"src/renderer/src/components/skills/installation/SkillInstallManagementDialog.test.tsx": 3300,
"src/renderer/src/components/skills/sharing/SkillSharedLinkRow.test.tsx": 817,
"src/renderer/src/components/skills/SkillsPage.test.tsx": 3822,
"src/renderer/src/components/skills/skill-agent-filter.test.ts": 274,
"src/renderer/src/components/skills/skill-share-link.test.ts": 55,
"src/renderer/src/components/skills/skill-share-package-selection.test.ts": 49,
"src/renderer/src/components/skills/skills-filter.test.ts": 204,
"src/renderer/src/components/skills/catalog/skill-agent-filter.test.ts": 274,
"src/renderer/src/components/sparse/sparse-directory-entry-input.test.ts": 207,
"src/renderer/src/components/stats/usage-daily-chart.test.tsx": 333,
"src/renderer/src/components/stats/usage-overview-model.test.ts": 364,
"src/renderer/src/components/status-bar/ClaudeSwitcherMenu.owner-lifetime.test.tsx": 2317,
"src/renderer/src/components/status-bar/NativeChatResumeStatusSegment.test.tsx": 2452,
"src/renderer/src/components/status-bar/PortsStatusSegment.render-stability.test.tsx": 2161,
"src/renderer/src/components/status-bar/SkillUpdateStatusSegment.test.tsx": 594,
"src/renderer/src/components/status-bar/SshStatusSegment.large-host-list.test.tsx": 1838,
"src/renderer/src/components/status-bar/StatusBarUsageChangeNotices.test.tsx": 785,
"src/renderer/src/components/status-bar/UpdateStatusSegment.test.tsx": 2484,
@@ -5726,7 +5655,6 @@
"src/renderer/src/components/status-bar/use-resource-pty-actions.test.tsx": 879,
"src/renderer/src/components/status-bar/use-resource-usage-derived-model.test.tsx": 742,
"src/renderer/src/components/tab-bar/CopyTabIdMenuItem.test.tsx": 428,
"src/renderer/src/components/tab-bar/NativeChatNamingFailure.test.tsx": 1062,
"src/renderer/src/components/tab-bar/TabBarCreateEntry.search.test.tsx": 3224,
"src/renderer/src/components/tab-bar/TabBarItemRow.test.tsx": 2116,
"src/renderer/src/components/tab-bar/TabBarQuickCommandItem.test.tsx": 384,
@@ -5745,7 +5673,6 @@
"src/renderer/src/components/tab-bar/tab-context-menu.test.tsx": 5620,
"src/renderer/src/components/tab-bar/tab-create-entry-classifier.test.ts": 510,
"src/renderer/src/components/tab-bar/tab-create-entry-local-path.test.ts": 2614,
"src/renderer/src/components/tab-bar/tab-create-entry-preview.test.ts": 882,
"src/renderer/src/components/tab-bar/tab-session-history-switch.test.ts": 2348,
"src/renderer/src/components/tab-bar/tab-strip-growth-scroll.test.tsx": 2164,
"src/renderer/src/components/tab-bar/tab-strip-pointer-activation.test.tsx": 2047,
@@ -5801,7 +5728,6 @@
"src/renderer/src/components/task-page/use-task-page-source-display.test.tsx": 5228,
"src/renderer/src/components/terminal-pane/layout/close/RunningTerminalCloseDialog.test.tsx": 2303,
"src/renderer/src/components/terminal-pane/session-fork/TerminalAgentSessionForkDialog.test.tsx": 266,
"src/renderer/src/components/terminal-pane/titles/TerminalPaneHeaderOverlay.test.tsx": 1104,
"src/renderer/src/components/terminal-pane/account-restart/detached/account-restart-queue-progress.test.ts": 1988,
"src/renderer/src/components/terminal-pane/agent-completion/agent-completion-coordinator-attention-dispatch.test.ts": 301,
"src/renderer/src/components/terminal-pane/agent-completion/agent-completion-coordinator-completion-replay-guard.test.ts": 318,
@@ -5851,7 +5777,6 @@
"src/renderer/src/components/terminal-pane/layout/close/pending-pane-close-confirmation.test.ts": 4695,
"src/renderer/src/components/terminal-pane/layout/close/pending-runtime-pane-close.test.ts": 3487,
"src/renderer/src/components/terminal-pane/layout/close/pending-split-close.test.ts": 4680,
"tests/e2e/terminal-agent-pane-launch.unit.test.ts": 8045,
"src/renderer/src/components/terminal-pane/snapshots/pty-buffer-serializer.test.ts": 53,
"src/renderer/src/components/terminal-pane/pty-connection/agent-identity/pty-connection-command-finished-late-done.test.ts": 10940,
"src/renderer/src/components/terminal-pane/pty-connection/tests/pty-connection-completion-and-notification.test.ts": 18380,
@@ -5899,7 +5824,6 @@
"src/renderer/src/components/terminal-pane/pty-connection/host-ownership/unserved-ssh-pane-bridge.test.ts": 70,
"src/renderer/src/components/terminal-pane/pty-transport/delivery/pty-dispatcher-delivery-resync.test.ts": 85,
"src/renderer/src/components/terminal-pane/pty-transport/delivery/pty-dispatcher-pi-routing.test.ts": 1362,
"src/renderer/src/components/terminal-pane/pty-transport/delivery/pty-dispatcher-push-reattach.test.ts": 108,
"src/renderer/src/components/terminal-pane/pty-transport/input/pty-input-write-queue.test.ts": 787,
"src/renderer/src/components/terminal-pane/pty-transport/delivery/pty-model-restore-channel.test.ts": 44,
"src/renderer/src/components/terminal-pane/pty-transport/output/pty-output-processor-program-activity.test.ts": 160,
@@ -5912,7 +5836,6 @@
"src/renderer/src/components/terminal-pane/pty-transport/shutdown/pty-shutdown-exit-deferral.test.ts": 121,
"src/renderer/src/components/terminal-pane/viewport/pty-size-reassertion.test.ts": 58,
"src/renderer/src/components/terminal-pane/viewport/pty-size-reconcile.test.ts": 68,
"src/renderer/src/components/terminal-pane/pty-transport/ipc/pty-transport-connect-spawn.test.ts": 3186,
"src/renderer/src/components/terminal-pane/pty-transport/ipc/pty-transport-detach-attach-handoff.test.ts": 1199,
"src/renderer/src/components/terminal-pane/pty-transport/ipc/pty-transport-eager-buffer-replay.test.ts": 1636,
"src/renderer/src/components/terminal-pane/pty-transport/ipc/pty-transport-handler-suspension.test.ts": 1243,
@@ -5937,7 +5860,6 @@
"src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-query-reply-immediate.test.ts": 1556,
"src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/recovery/remote-runtime-pty-recovery-state.test.ts": 85,
"src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-activation-inventory-fallback.test.ts": 1311,
"src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-attach-subscription.test.ts": 2346,
"src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-handoff.test.ts": 14353,
"src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-create-outcome-recovery.test.ts": 2041,
"src/renderer/src/components/terminal-pane/pty-transport/remote-runtime/remote-runtime-pty-transport-empty-initial-snapshot.test.ts": 1491,
@@ -5986,13 +5908,9 @@
"src/renderer/src/components/terminal-pane/input/paste/terminal-bracketed-paste.test.ts": 57,
"src/renderer/src/components/terminal-pane/emulation/terminal-capability-replies.test.ts": 131,
"src/renderer/src/components/terminal-pane/clipboard/terminal-clipboard-paste.test.ts": 76,
"src/renderer/src/components/terminal-pane/parking/retention/terminal-cold-park-timer-rearm.test.tsx": 701,
"src/renderer/src/components/terminal-pane/input/file-drop/terminal-drop-handler.test.ts": 638,
"src/renderer/src/components/terminal-pane/input/file-drop/terminal-drop-image-path.test.ts": 61,
"src/renderer/src/components/terminal-pane/input/file-drop/terminal-drop-internal-handler.test.ts": 800,
"src/renderer/src/components/terminal-pane/input/file-drop/terminal-drop-path-writer.test.ts": 245,
"src/renderer/src/components/terminal-pane/input/file-drop/terminal-drop-runtime-catalog-owner.test.ts": 872,
"src/renderer/src/components/terminal-pane/input/file-drop/terminal-drop-source-owner.test.ts": 622,
"src/renderer/src/components/terminal-pane/viewport/terminal-fit-restore.test.ts": 59,
"src/renderer/src/components/terminal-pane/links/handles/terminal-handle-links.test.ts": 527,
"src/renderer/src/components/terminal-pane/parking/retention/terminal-hidden-view-parking.test.ts": 325,
@@ -6025,7 +5943,6 @@
"src/shared/workspace-layout/terminal-layout-pty-ownership-depth.test.ts": 82,
"src/renderer/src/components/terminal-pane/layout/terminal-layout-pty-ownership.test.ts": 99,
"src/renderer/src/components/terminal-pane/links/terminal-link-click-fallback.test.ts": 985,
"src/renderer/src/components/terminal-pane/links/files/terminal-link-file-open-routing.test.ts": 970,
"src/renderer/src/components/terminal-pane/links/terminal-link-open-beside-source.test.ts": 947,
"src/renderer/src/components/terminal-pane/links/http/terminal-link-osc-url-routing.test.ts": 986,
"src/renderer/src/components/terminal-pane/links/terminal-link-paired-click-fallback.test.ts": 989,
@@ -6043,14 +5960,11 @@
"src/renderer/src/components/terminal-pane/layout/reconciliation/terminal-pane-frame-reconciliation.test.ts": 903,
"src/renderer/src/components/terminal-pane/host-state/terminal-pane-host-state-memo.test.ts": 4534,
"src/renderer/src/components/terminal-pane/mounting/terminal-pane-mount-bootstrap.test.ts": 1723,
"src/renderer/src/components/terminal-pane/render-health/recovery/terminal-pane-recovery-unsettled-fallback.test.ts": 831,
"src/renderer/src/components/terminal-pane/render-health/recovery/terminal-pane-recovery.test.ts": 949,
"src/renderer/src/components/terminal-pane/layout/split/terminal-pane-split-request-routing.test.ts": 2015,
"src/renderer/src/components/terminal-pane/pty-transport/recovery/terminal-pane-surface-ownership.test.ts": 426,
"src/renderer/src/components/terminal-pane/layout/detach/terminal-pane-tab-detach-main-commit.test.ts": 2090,
"src/renderer/src/components/terminal-pane/layout/detach/terminal-pane-tab-detach-main-move.test.ts": 2147,
"src/renderer/src/components/terminal-pane/parking/verdict/terminal-park-verdict-flip-telemetry.test.ts": 123,
"src/renderer/src/components/terminal-pane/parking/verdict/terminal-park-verdict-worktree-driver-loop.test.tsx": 735,
"src/renderer/src/components/terminal-pane/parking/output-watchers/terminal-parked-tab-watchers.test.ts": 473,
"src/renderer/src/components/terminal-pane/parking/output-watchers/terminal-parked-watcher-reconciliation.test.ts": 243,
"src/renderer/src/components/terminal-pane/parking/output-watchers/terminal-parked-watcher-registry.test.ts": 245,
@@ -6172,8 +6086,7 @@
"src/renderer/src/hooks/editor-external-watch-overflow.test.ts": 1023,
"src/renderer/src/hooks/editor-external-watch-selected-host.test.ts": 1988,
"src/renderer/src/hooks/editor-runtime-file-watch.test.ts": 58,
"src/renderer/src/hooks/installed-agent-skill-discovery-cache.test.ts": 53,
"src/renderer/src/hooks/installed-agent-skill-discovery.test.ts": 74,
"src/renderer/src/hooks/ipc-events/agent-status-pane-routing-index-retention.test.ts": 4878,
"src/renderer/src/hooks/ipc-events/agent-status-pending-retry-gate.test.ts": 466,
"src/renderer/src/hooks/ipc-events/agent-status-terminal-title-tab-write.test.ts": 2247,
@@ -6210,7 +6123,7 @@
"src/renderer/src/hooks/runtime-project-refresh-scheduler.test.ts": 65,
"src/renderer/src/hooks/shortcut-label-cache.test.tsx": 2021,
"src/renderer/src/hooks/unpaired-device-auth-notification.test.ts": 41,
"src/renderer/src/hooks/use-bundled-skill-install.test.tsx": 553,
"src/renderer/src/hooks/use-document-dark-theme.test.tsx": 350,
"src/renderer/src/hooks/use-list-multi-selection.test.tsx": 545,
"src/renderer/src/hooks/use-monaco-theme.test.tsx": 323,
@@ -6236,9 +6149,8 @@
"src/renderer/src/hooks/useEphemeralVmRecipeOptions.test.tsx": 415,
"src/renderer/src/hooks/useGitHubSlugMetadata.assignee-reuse.test.tsx": 233,
"src/renderer/src/hooks/useImmediateMutation.test.ts": 618,
"src/renderer/src/hooks/useInstalledAgentSkills.react.test.tsx": 2093,
"src/renderer/src/hooks/skills/discovery/useInstalledAgentSkills.react.test.tsx": 2093,
"src/renderer/src/hooks/useIpcEvents-agent-status-connection-attribution.test.ts": 5510,
"src/renderer/src/hooks/useIpcEvents-agent-status-hook-titles.test.ts": 5621,
"src/renderer/src/hooks/useIpcEvents-agent-status-pane-teardown.test.ts": 5268,
"src/renderer/src/hooks/useIpcEvents-agent-status-queue-ordering.test.ts": 3680,
"src/renderer/src/hooks/useIpcEvents-agent-status-snapshot-hydration.test.ts": 7403,
@@ -6252,11 +6164,9 @@
"src/renderer/src/hooks/useIpcEvents-cmd-j-digit-chord.test.ts": 2703,
"src/renderer/src/hooks/useIpcEvents-lifecycle.test.ts": 2326,
"src/renderer/src/hooks/useIpcEvents-new-workspace-shortcut.test.ts": 72,
"src/renderer/src/hooks/useIpcEvents-rate-limit-hydration.test.ts": 1597,
"src/renderer/src/hooks/useIpcEvents-runtime-environment-selectors.test.ts": 530,
"src/renderer/src/hooks/useIpcEvents-session-tab-close-request.test.ts": 2252,
"src/renderer/src/hooks/useIssueMetadata.test.tsx": 344,
"src/renderer/src/hooks/useMacTccAttributionSeveredNotice.test.tsx": 2358,
"src/renderer/src/hooks/useMetadataListRequest.test.tsx": 266,
"src/renderer/src/hooks/useModalReturnFocus.test.tsx": 1938,
"src/renderer/src/hooks/clipboard/primary-selection/usePrimarySelectionPaste.terminal-native-paste.test.tsx": 388,
@@ -6268,7 +6178,7 @@
"src/renderer/src/hooks/useReadOnScreenAttention.mixed-workspace.test.ts": 1725,
"src/renderer/src/hooks/useReadOnScreenAttention.test.ts": 1915,
"src/renderer/src/hooks/useRetiredWorktreeNames.test.ts": 593,
"src/renderer/src/hooks/useSkillFreshness.test.tsx": 554,
"src/renderer/src/hooks/useUnreadDockBadge.test.ts": 2126,
"src/renderer/src/hooks/virtualized-hidden-row-measurement.test.ts": 199,
"src/renderer/src/hooks/worktree-change-refresh-queue.test.ts": 59,
@@ -6292,8 +6202,6 @@
"src/renderer/src/lib/agent-session/hibernation/agent-hibernation-pane-age.test.ts": 41,
"src/renderer/src/lib/agent-session/hibernation/agent-hibernation-planner.test.ts": 341,
"src/renderer/src/lib/agent-session/hibernation/agent-hibernation-runtime-liveness-race.test.ts": 1914,
"src/renderer/src/lib/agent-session/launch/new-tab/agent-launch-caller-prompt-transport.test.ts": 1326,
"src/renderer/src/lib/agent-session/launch/new-tab/agent-launch-caller-routing.test.ts": 1266,
"src/renderer/src/lib/agent-session/launch/pane/agent-launch-pane-verdict-application.test.ts": 1869,
"src/renderer/src/lib/agent-session/launch/routing/agent-launch-route-input.test.ts": 1659,
"src/renderer/src/lib/agent-session/launch/routing/agent-launch-route-registered-agents.test.ts": 1749,
@@ -6347,7 +6255,7 @@
"src/renderer/src/lib/browser/navigation/browser-link-profile-isolation.test.ts": 2511,
"src/renderer/src/lib/browser/palette/browser-page-palette-activation.test.ts": 1983,
"src/renderer/src/lib/browser/palette/browser-palette-page-entries.test.ts": 480,
"src/renderer/src/lib/bundled-skill-target.test.ts": 54,
"src/renderer/src/lib/skills/runtime/bundled-skill-target.test.ts": 54,
"src/renderer/src/lib/source-control/checks/check-run-details-log-normalization-performance.test.ts": 846,
"src/renderer/src/lib/source-control/checks/check-run-details-log-normalization.test.ts": 75,
"src/renderer/src/lib/claude-account-switch-sessions.test.ts": 782,
@@ -6364,7 +6272,6 @@
"src/renderer/src/lib/source-control/review/comments/comment-body-submit-state.test.ts": 55,
"src/renderer/src/lib/composer-submit-cancellation.test.ts": 41,
"src/renderer/src/lib/workspace/ownership/connection-context.test.ts": 1817,
"src/renderer/src/lib/crash-diagnostics.test.ts": 176,
"src/renderer/src/lib/create-untitled-markdown.test.ts": 566,
"src/renderer/src/lib/desktop-created-server-tabs.test.ts": 1933,
"src/renderer/src/lib/workspace/ownership/direct-ssh-target-scope.test.ts": 48,
@@ -6373,21 +6280,14 @@
"src/renderer/src/lib/browser/documents/doc-preview-grants.test.ts": 2094,
"src/renderer/src/lib/editor/file-operations/editor-file-operation-owner.test.ts": 2848,
"src/renderer/src/lib/emulator-error-copy.test.ts": 415,
"src/renderer/src/lib/ensure-hooks-confirmed.test.ts": 799,
"src/renderer/src/runtime/repo-hooks/ensure-hooks-confirmed.test.ts": 799,
"src/renderer/src/lib/simulator/tabs/ensure-simulator-tab-behavior.test.ts": 3601,
"src/renderer/src/runtime/hosts/platform/execution-host-facts.test.ts": 1796,
"src/renderer/src/lib/feature-education-telemetry.test.ts": 52,
"src/renderer/src/lib/feedback-image-attachments.test.ts": 557,
"src/renderer/src/lib/file-search-selection.test.ts": 51,
"src/renderer/src/lib/find-query-bounds.test.ts": 45,
"src/renderer/src/components/sidebar/sidebar-feedback/feedback-image-attachments.test.ts": 557,
"src/renderer/src/lib/files/search/file-search-selection.test.ts": 51,
"src/renderer/src/lib/finished-agent-resume-resurrection.test.ts": 1872,
"src/renderer/src/lib/focus-terminal-tab-surface.split-focus.test.ts": 331,
"src/renderer/src/lib/foreground-terminal-tabs.test.ts": 55,
"src/shared/new-workspace/github-links.test.ts": 76,
"src/renderer/src/components/terminal-pane/parking/retention/foreground-terminal-tabs.test.ts": 55,
"src/renderer/src/lib/work-items/github/github-source-runtime-context.test.ts": 62,
"src/shared/new-workspace/gitlab-links.test.ts": 56,
"src/renderer/src/lib/hook-command-delayed-delivery.test.ts": 1904,
"src/renderer/src/lib/host-chat-program.test.ts": 2419,
"src/renderer/src/lib/host-mirror-handle-gap-drain.test.ts": 1706,
"src/renderer/src/lib/host-mirror-handle-gap-resume.test.ts": 1811,
"src/renderer/src/lib/host-mirrored-pane-resume-replay.test.ts": 1814,
@@ -6409,10 +6309,7 @@
"src/renderer/src/lib/workspace/creation/work-item/launch-work-item-direct-accepted.test.tsx": 2754,
"src/renderer/src/lib/workspace/creation/work-item/launch-work-item-direct-defaults.test.ts": 1594,
"src/renderer/src/lib/workspace/creation/work-item/launch-work-item-direct-setup.test.tsx": 3435,
"src/renderer/src/lib/lazy-chunk-recovery-reload.test.ts": 246,
"src/renderer/src/lib/lazy-with-retry.never-landed-reload.test.ts": 194,
"src/renderer/src/lib/lazy-with-retry.preload.test.tsx": 208,
"src/renderer/src/lib/lazy-with-retry.test.ts": 282,
"src/renderer/src/app-shell/startup/lazy-with-retry.never-landed-reload.test.ts": 194,
"src/renderer/src/lib/work-items/linear/linear-board-drag-payload.test.ts": 58,
"src/renderer/src/lib/work-items/linear/linear-issue-workspace-attachment.test.ts": 99,
"src/renderer/src/lib/link-source-local-open-owner.test.ts": 433,
@@ -6432,37 +6329,25 @@
"src/renderer/src/lib/monaco-languages/textmate-token-provider.test.ts": 245,
"src/renderer/src/lib/monaco-languages/zig-highlighting.test.ts": 7984,
"src/renderer/src/lib/agent-session/resume/cli-fork/native-chat-cli-fork-preparation.test.ts": 2773,
"src/renderer/src/lib/new-workspace.test.ts": 4534,
"src/renderer/src/lib/non-secure-context-crypto.repro.test.ts": 1691,
"src/renderer/src/lib/onboarding-folder-cancellation.test.ts": 2486,
"src/renderer/src/lib/open-agent-message-sender.test.ts": 1497,
"src/renderer/src/lib/simulator/launch/open-mobile-emulator-tab.test.ts": 545,
"src/renderer/src/lib/orca-yaml-trust-prompt-slot-eviction.test.ts": 884,
"src/renderer/src/lib/order-empty-query-worktrees.test.ts": 62,
"src/renderer/src/lib/os-file-drop-cancellation-guard.test.ts": 204,
"src/renderer/src/lib/os-file-drop-preload-integration.test.tsx": 353,
"src/renderer/src/lib/files/drop/os-file-drop-cancellation-guard.test.ts": 204,
"src/renderer/src/lib/paired-runtime-closed-tab-resume.test.ts": 2008,
"src/renderer/src/lib/pane-manager/pane-close-focus-successor.test.ts": 1123,
"src/renderer/src/lib/pane-manager/pane-container-listener-lifecycle.test.ts": 312,
"src/renderer/src/lib/pane-manager/pane-cursor-blink-suspension.test.ts": 429,
"src/renderer/src/lib/pane-manager/pane-drag-window-blur.test.ts": 618,
"src/renderer/src/lib/pane-manager/pane-fit-resize-observer.test.ts": 94,
"src/renderer/src/lib/pane-manager/pane-fit.test.ts": 166,
"src/renderer/src/lib/pane-manager/pane-inline-image-recovery.test.ts": 61,
"src/renderer/src/lib/pane-manager/pane-inline-images.test.ts": 74,
"src/renderer/src/lib/pane-manager/pane-layout-geometry-apply.test.ts": 897,
"src/renderer/src/lib/pane-manager/pane-manager-registry.test.ts": 69,
"src/renderer/src/lib/pane-manager/pane-manager-spawn-placement.test.ts": 766,
"src/renderer/src/lib/pane-manager/pane-overlay-focus.test.ts": 622,
"src/renderer/src/lib/pane-manager/pane-scroll.test.ts": 153,
"src/renderer/src/lib/pane-manager/pane-terminal-foreground-render-settle.test.ts": 43,
"src/renderer/src/lib/pane-manager/pane-terminal-mouse-wheel.test.ts": 88,
"src/renderer/src/lib/pane-manager/pane-terminal-output-queue-chunks.test.ts": 44,
"src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-ack-credit.test.ts": 173,
"src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-disposed-writes.test.ts": 121,
"src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-queue-retention.test.ts": 301,
"src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-synchronized-frames.test.ts": 213,
"src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.test.ts": 236,
"src/renderer/src/lib/pane-manager/pane-tree-equalization-parity.test.ts": 182,
"src/renderer/src/lib/pane-manager/pane-tree-equalization-scaling.test.ts": 239,
"src/renderer/src/lib/pane-manager/pane-tree-match-moves.test.ts": 662,
@@ -6479,7 +6364,6 @@
"src/renderer/src/lib/pane-manager/terminal-indic-cluster-ranges.test.ts": 50,
"src/renderer/src/lib/pane-manager/terminal-kitty-file-reader.test.ts": 83,
"src/renderer/src/lib/pane-manager/terminal-ligatures-addon.test.ts": 59,
"src/renderer/src/lib/pane-manager/terminal-link-provider-guard.test.ts": 48,
"src/renderer/src/lib/pane-manager/terminal-linkifier-hover-reset-on-write.test.ts": 59,
"src/renderer/src/lib/pane-manager/terminal-mouse-encoding-tracker.test.ts": 95,
"src/renderer/src/lib/pane-manager/terminal-mouse-report-finite-coordinates.test.ts": 272,
@@ -6497,30 +6381,24 @@
"src/renderer/src/lib/pane-manager/xterm-instance-disposed.test.ts": 57,
"src/renderer/src/lib/pane-manager/xterm-parse-barrier.test.ts": 154,
"src/renderer/src/lib/pane-manager/xterm-user-scrolling-contract.test.ts": 1266,
"src/renderer/src/lib/pane-manager/xterm-write-callback-guard.test.ts": 53,
"src/renderer/src/lib/pane-spawn-deferral-drain.test.ts": 61,
"src/renderer/src/lib/parked-terminal-host-hydration.test.ts": 274,
"src/renderer/src/components/terminal-pane/session-restore/spawn-deferral/pane-spawn-deferral-drain.test.ts": 61,
"src/renderer/src/components/terminal-pane/mounting/parked-terminal-host-hydration.test.ts": 274,
"src/renderer/src/lib/text-input/paste/paste-payload-metadata.test.ts": 53,
"src/renderer/src/lib/path-head-elision.test.ts": 53,
"src/renderer/src/lib/path-display/path-head-elision.test.ts": 53,
"src/renderer/src/lib/path.test.ts": 55,
"src/renderer/src/lib/plugin-command-keybindings.test.ts": 1909,
"src/renderer/src/lib/plugin-command-replacements.test.ts": 95,
"src/renderer/src/lib/plugin-icons/plugin-icon-svg.test.ts": 231,
"src/renderer/src/lib/plugin-monaco-token-rules.test.ts": 51,
"src/renderer/src/lib/editor/appearance/plugin-monaco-token-rules.test.ts": 51,
"src/renderer/src/lib/source-control/review/pr-bot-author-overrides.test.ts": 349,
"src/renderer/src/lib/source-control/review/comments/pr-comment-thread-resolution.test.ts": 48,
"src/renderer/src/lib/project-clone-url-prefill.test.ts": 61,
"src/renderer/src/lib/project-host-workspace-target.jira-mapping.test.ts": 76,
"src/renderer/src/lib/project-host-workspace-target.test.ts": 92,
"src/renderer/src/lib/react-commit-cascade-observer.react185.test.tsx": 285,
"src/renderer/src/lib/react-commit-cascade-observer.test.ts": 189,
"src/renderer/src/lib/react-commit-cascade-telemetry.test.ts": 159,
"src/renderer/src/lib/react-error-boundary-reporting.test.ts": 55,
"src/renderer/src/lib/workspace/creation/project-clone-url-prefill.test.ts": 61,
"src/renderer/src/lib/workspace/creation/project-host-workspace-target.jira-mapping.test.ts": 76,
"src/renderer/src/lib/workspace/creation/project-host-workspace-target.test.ts": 92,
"src/renderer/src/lib/editor/file-operations/remap-open-editor-tabs-for-path-change.test.ts": 2178,
"src/renderer/src/lib/rename-user-named-tab-access.test.ts": 2804,
"src/renderer/src/lib/renderer-agent-status-observation-ingress.test.ts": 81,
"src/renderer/src/lib/renderer-app-platform.test.ts": 197,
"src/renderer/src/lib/renderer-memory-profile.test.ts": 70,
"src/renderer/src/diagnostics/memory/renderer-memory-profile.test.ts": 70,
"src/renderer/src/lib/repo-search.test.ts": 54,
"src/renderer/src/lib/repo-slug-cache.test.ts": 52,
"src/renderer/src/lib/repo-slug-index.test.ts": 2240,
@@ -6543,7 +6421,6 @@
"src/renderer/src/lib/script-textarea-rows.test.ts": 93,
"src/renderer/src/lib/session-write-subscriber-acknowledgment.test.ts": 1823,
"src/renderer/src/lib/session-write-subscriber.test.ts": 1882,
"src/renderer/src/lib/shutdown-checkpoint-guard.test.ts": 209,
"src/renderer/src/lib/sidebar-ended-creations.test.ts": 1889,
"src/renderer/src/lib/sidebar-pending-layer.test.ts": 338,
"src/renderer/src/lib/sidebar-shadow/workspace-activity-shadow.test.ts": 1728,
@@ -6552,10 +6429,9 @@
"src/renderer/src/lib/simulator/tabs/simulator-pane-shutdown-scheduler.test.ts": 1679,
"src/renderer/src/lib/simulator/tabs/simulator-tab-layout-owner.test.ts": 2398,
"src/renderer/src/lib/simulator/tabs/simulator-tab-palette-activation.test.ts": 1903,
"src/renderer/src/lib/sleeping-agent-session-launch-windows-quoting.test.ts": 547,
"src/renderer/src/lib/source-control/remote/source-control-remote-error.test.ts": 340,
"src/renderer/src/lib/ssh-config-passive-sync.test.ts": 1513,
"src/renderer/src/lib/state-collection-byte-estimate.test.ts": 86,
"src/renderer/src/diagnostics/memory/state-collection-byte-estimate.test.ts": 86,
"src/renderer/src/lib/agent-session/launch/structured-agent-session-launch-draft.test.ts": 1042,
"src/renderer/src/lib/agent-session/launch/structured-agent-session-launch-prerequisites.test.ts": 2427,
"src/renderer/src/lib/agent-session/options/structured-agent-session-selection-lifetime.test.tsx": 2947,
@@ -6567,7 +6443,7 @@
"src/renderer/src/lib/syntax-highlighting/textmate-core.test.ts": 60,
"src/renderer/src/lib/tab-agent-status-index.test.ts": 210,
"src/renderer/src/lib/task-provider-error-copy.test.ts": 363,
"src/renderer/src/lib/terminal-app-theme.test.ts": 301,
"src/renderer/src/app-shell/appearance/terminal-app-theme.test.ts": 301,
"src/renderer/src/lib/terminal-input-activity-coalescing.test.ts": 43,
"src/renderer/src/lib/text-input/paste/text-control-paste-ownership.test.ts": 217,
"src/renderer/src/lib/text-input/paste/text-control-paste.test.ts": 222,
@@ -6579,17 +6455,10 @@
"src/renderer/src/lib/use-tab-agent-observed-signal-dispatch.test.tsx": 1971,
"src/renderer/src/lib/use-tab-agent-opencode-native-title.test.ts": 2093,
"src/renderer/src/lib/use-tab-agent.test.ts": 1961,
"src/renderer/src/lib/user-opened-local-path.test.ts": 325,
"src/renderer/src/lib/wake-sleeping-agents-live-unmounted-pane.test.ts": 2432,
"src/renderer/src/lib/window-visibility-interval.test.ts": 48,
"src/renderer/src/lib/window-visibility-timeout-poller.test.ts": 207,
"src/renderer/src/lib/windows-terminal-capabilities-race.test.ts": 450,
"src/renderer/src/lib/windows-terminal-capabilities.test.ts": 585,
"src/renderer/src/lib/windows-terminal-capability-reprobe.test.ts": 198,
"src/renderer/src/runtime/hosts/capabilities/windows-terminal-capabilities-race.test.ts": 450,
"src/renderer/src/lib/workspace/activation/workspace-activation-path-gate.test.ts": 2107,
"src/renderer/src/lib/browser/workspace/workspace-browser-tab-open-placement.test.ts": 1996,
"src/renderer/src/lib/workspace-doc-address-input.test.ts": 470,
"src/renderer/src/lib/workspace-file-drag.test.ts": 70,
"src/renderer/src/lib/workspace-ports/workspace-port-scan-debounce.test.ts": 57,
"src/renderer/src/lib/workspace/persistence/workspace-session-host-contention.test.ts": 265,
"src/renderer/src/lib/workspace/persistence/workspace-session-host-emptied-fields.test.ts": 279,
@@ -6679,7 +6548,6 @@
"src/renderer/src/runtime/workspace-layout/server/server-layout-frames.test.ts": 2053,
"src/renderer/src/runtime/native-chat/status/structured-agent-session-status-feed-lifecycle.test.ts": 62,
"src/renderer/src/runtime/native-chat/status/structured-agent-session-status-feed.test.ts": 82,
"src/renderer/src/runtime/native-chat/tab-retirement/structured-agent-session-tab-retirement.test.ts": 2115,
"src/renderer/src/runtime/native-chat/status/structured-agent-session-turn-completion-feed.test.ts": 118,
"src/renderer/src/runtime/native-chat/host-capabilities/structured-chat-host-permission.test.ts": 523,
"src/renderer/src/runtime/web-session-tabs-sync/placement/structured-conversation-tab-replacement.test.ts": 2029,
@@ -6696,11 +6564,6 @@
"src/renderer/src/runtime/browser/creation/web-runtime-browser-creation-placement.test.ts": 1927,
"src/renderer/src/runtime/browser/creation/web-runtime-browser-link-open.test.ts": 2217,
"src/renderer/src/runtime/browser/creation/web-runtime-browser-link-session.test.ts": 1964,
"src/renderer/src/runtime/browser/creation/web-runtime-session-browser-create-split-placement.test.ts": 949,
"src/renderer/src/runtime/web-session-tabs-sync/closing/web-runtime-session-tab-activate-close.test.ts": 1426,
"src/renderer/src/runtime/hosted-terminals/actions/web-runtime-session-terminal-pane-delegation.test.ts": 1341,
"src/renderer/src/runtime/hosted-terminals/creation/web-runtime-session-terminal-workspace-routing.test.ts": 768,
"src/renderer/src/runtime/web-session-tabs-sync/inventory/web-runtime-session.test.ts": 743,
"src/renderer/src/runtime/web-session-tabs-sync/closing/web-session-close-replay.test.ts": 1119,
"src/renderer/src/runtime/web-session-tabs-sync/placement/web-session-open-files-batch-equivalence.test.ts": 1478,
"src/renderer/src/runtime/web-session-tabs-sync/identity/web-session-restored-placeholder-identity.test.ts": 985,
@@ -6709,7 +6572,6 @@
"src/renderer/src/runtime/web-session-tabs-sync/subscriptions/web-session-tabs-stream-end-resubscribe.test.tsx": 2282,
"src/renderer/src/runtime/web-session-tabs-sync/agent-status/web-session-tabs-sync-agent-status.test.ts": 987,
"src/renderer/src/runtime/web-session-tabs-sync/placement/web-session-tabs-sync-browser-tabs.test.ts": 925,
"src/renderer/src/runtime/web-session-tabs-sync/subscriptions/web-session-tabs-sync-completion-on-reveal.test.tsx": 2249,
"src/renderer/src/runtime/web-session-tabs-sync/identity/web-session-tabs-sync-cross-runtime-worktree.test.ts": 964,
"src/renderer/src/runtime/web-session-tabs-sync/placement/web-session-tabs-sync-editor-ownership.test.ts": 1902,
"src/renderer/src/runtime/web-session-tabs-sync/placement/web-session-tabs-sync-editor-tabs.test.ts": 1073,
@@ -6803,7 +6665,6 @@
"src/renderer/src/store/slices/browser/pages/browser-workspace-doc-location.test.ts": 2007,
"src/renderer/src/store/slices/browser/browser.test.ts": 2001,
"src/renderer/src/store/slices/worktrees/teardown/bulk-worktree-purge-terminal-maps-leak.test.ts": 1727,
"src/renderer/src/store/workspaces/creation/active-workspace-create-actions.test.ts": 1879,
"src/renderer/src/store/terminals/codex-restart-notice-lifecycle.test.ts": 1685,
"src/renderer/src/store/slices/tabs/content-tabs-from-main-frames.test.ts": 2025,
"src/renderer/src/store/slices/tabs/content-tabs-paired-server-workspace.test.ts": 1923,
@@ -7994,7 +7855,7 @@
"tests/e2e/ai-vault-account-stream.unit.test.ts": 4506,
"tests/e2e/ai-vault-manual-name-clear.unit.test.ts": 21206,
"tests/e2e/automation-chat-reopen.unit.test.ts": 6262,
"tests/e2e/automation-save-fallback.unit.test.ts": 9065,
"tests/e2e/claude-agent-row-view.unit.test.ts": 1491,
"tests/e2e/codex-child-approval-activity-row.unit.test.ts": 14886,
"tests/e2e/completed-worker-retirement-resume.unit.test.ts": 38857,
@@ -8030,7 +7891,7 @@
"tests/e2e/host-agent-launch-preferences-work.unit.test.ts": 3882,
"tests/e2e/host-agent-launch-settings-hook-bookkeeping.unit.test.ts": 21972,
"tests/e2e/host-cold-park-remote-subscriber.unit.test.ts": 5314,
"tests/e2e/journal-restored-consumers.unit.test.ts": 33933,
"tests/e2e/launch-agent-background-session-remote.unit.test.ts": 1016,
"tests/e2e/launch-agent-in-new-tab-web-runtime.unit.test.ts": 14288,
"tests/e2e/linear-cli-recovery.unit.test.ts": 7592,
@@ -8042,7 +7903,7 @@
"tests/e2e/native-chat-rewind-boundary.unit.test.ts": 2977,
"tests/e2e/native-chat-ssh-transcript.unit.test.ts": 1166,
"tests/e2e/native-chat-startup-option-pickers.unit.test.ts": 9377,
"tests/e2e/native-chat-transcript-file-order.unit.test.ts": 1066,
"tests/e2e/native-chat-transcript-reasoning.unit.test.ts": 3341,
"tests/e2e/nested-agent-idle-owner.unit.test.ts": 2469,
"tests/e2e/omp-child-session-recovery.unit.test.ts": 2205,
@@ -8077,12 +7938,9 @@
"tests/e2e/structured-session-tab-close-regressions.unit.test.ts": 32432,
"tests/e2e/sustained-agent-typing-load-scripts.unit.test.ts": 2169,
"tests/e2e/task-page-linear-refresh-authority.unit.test.ts": 5548,
"tests/e2e/task-page-linear-refresh-transport.unit.test.ts": 2019,
"tests/e2e/terminal-agent-fork-draft-ownership.unit.test.ts": 30195,
"tests/e2e/terminal-binding-crash-hydration.unit.test.ts": 1769,
"tests/e2e/terminal-foreground-confirmation.unit.test.ts": 644,
"tests/e2e/terminal-perf-presentation.unit.test.ts": 60,
"tests/e2e/web-runtime-session-terminal-host-authority.unit.test.ts": 1061,
"tests/e2e/worktree-status-warning.unit.test.ts": 2331,
"tests/tools/branch-line-total-renderer.test.mjs": 1222,
"tests/tools/omp-close-lifecycle.test.mjs": 403,
@@ -106,6 +106,19 @@ function parseManifest(bundleDir) {
}
}
}
if (manifest.catalogs !== undefined && !Array.isArray(manifest.catalogs)) {
throw failure('manifest catalogs is not a list')
}
for (const catalog of manifest.catalogs ?? []) {
if (typeof catalog !== 'object' || catalog === null || typeof catalog.locale !== 'string') {
throw failure('manifest catalog entry has no locale')
}
assertSafeRelativePath(catalog.path)
if (typeof catalog.sha256 !== 'string' || !SHA256_PATTERN.test(catalog.sha256)) {
throw failure(`manifest catalog ${catalog.path} has no sha256 digest`)
}
assertInteger(catalog.byteLength, `catalogs[${catalog.path}].byteLength`)
}
if (!manifest.assets.some((asset) => asset.path === manifest.entrypoint)) {
throw failure(`manifest entrypoint ${manifest.entrypoint} is not one of its assets`)
}
@@ -126,6 +139,11 @@ function parseManifest(bundleDir) {
return manifest
}
// Assets go to the phone; catalogs stay on the host, which serves one on request.
function servedFiles(manifest) {
return [...manifest.assets, ...(manifest.catalogs ?? [])]
}
function listBundleFiles(directory, prefix = '') {
const found = []
for (const entry of readdirSync(directory, { withFileTypes: true })) {
@@ -164,7 +182,7 @@ function assertNoUnlistedFiles(bundleDir, manifest) {
const listed = new Set([
'manifest.json',
...darkBuildMarkerFileNames(),
...manifest.assets.map((asset) => asset.path),
...servedFiles(manifest).map((file) => file.path),
...brotliCopies(manifest).map((asset) => brotliPath(asset))
])
const strays = listBundleFiles(bundleDir).filter((path) => !listed.has(path))
@@ -180,7 +198,7 @@ function assertNoUnlistedFiles(bundleDir, manifest) {
function assertMobileWebBundleBuilt(bundleDir = MOBILE_WEB_BUNDLE_DIR) {
const manifest = parseManifest(bundleDir)
assertNoUnlistedFiles(bundleDir, manifest)
for (const asset of manifest.assets) {
for (const asset of servedFiles(manifest)) {
const assetPath = join(bundleDir, asset.path)
const size = statSize(bundleDir, asset.path)
if (size !== asset.byteLength) {
@@ -6,10 +6,11 @@ import process from 'node:process'
// TypeScript 7 is a native CLI; AST consumers still need the legacy JavaScript API.
import ts from 'typescript-api'
import { isTestOnlySourcePath } from '../../static-analysis/process-boundaries/test-only-source-path.mjs'
import { isVisibleObjectKey } from './localization-visible-object-keys.mjs'
const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts'])
const SKIP_PATH_PARTS = new Set(['.git', 'dist', 'node_modules', 'out', '__snapshots__', 'assets'])
const LOCALIZATION_CALL_NAMES = new Set(['t', 'translate'])
const LOCALIZATION_CALL_NAMES = new Set(['t', 'translate', 'translateMain'])
const CLASS_PROPERTY_NAMES = new Set(['className', 'classNames'])
// Why: the children of these elements are CSS or code, never copy a translator should see.
const NON_COPY_JSX_ELEMENTS = new Set(['style', 'script'])
@@ -35,25 +36,6 @@ const USER_VISIBLE_JSX_ATTRIBUTES = new Set([
'toggleDescription',
'tooltip'
])
const USER_VISIBLE_OBJECT_KEYS = new Set([
'accessibilityHint',
'accessibilityLabel',
'ariaLabel',
'badge',
'description',
'emptyText',
'error',
'headerTitle',
'helperText',
'keywords',
'label',
'message',
'placeholder',
'subtitle',
'title',
'toggleDescription',
'tooltip'
])
const USER_VISIBLE_FUNCTION_NAMES = new Set([
'alert',
'confirm',
@@ -331,7 +313,7 @@ function isInsideNonCopyJsxElement(node) {
return Boolean(element && NON_COPY_JSX_ELEMENTS.has(element.openingElement.tagName.getText()))
}
function classifyStringNode(node) {
function classifyStringNode(node, includeNativeDialogFields = false) {
if (
hasAncestorObjectPropertyName(node, CLASS_PROPERTY_NAMES) ||
isInsideNonCopyJsxElement(node)
@@ -369,12 +351,15 @@ function classifyStringNode(node) {
}
const objectPropertyName = nearestObjectPropertyName(node)
if (objectPropertyName && !USER_VISIBLE_OBJECT_KEYS.has(objectPropertyName)) {
if (objectPropertyName && !isVisibleObjectKey(objectPropertyName, includeNativeDialogFields)) {
return undefined
}
const ancestorObjectPropertyName = nearestAncestorObjectPropertyName(node)
if (ancestorObjectPropertyName && !USER_VISIBLE_OBJECT_KEYS.has(ancestorObjectPropertyName)) {
if (
ancestorObjectPropertyName &&
!isVisibleObjectKey(ancestorObjectPropertyName, includeNativeDialogFields)
) {
return undefined
}
@@ -407,7 +392,12 @@ function areaForFile(relativePath) {
return `renderer/${parts[0] ?? 'root'}`
}
export function collectLocalizationCandidates(filePath, sourceText, root = process.cwd()) {
export function collectLocalizationCandidates(
filePath,
sourceText,
root = process.cwd(),
options = {}
) {
const sourceKind =
filePath.endsWith('.tsx') || filePath.endsWith('.jsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS
const sourceFile = ts.createSourceFile(
@@ -450,7 +440,7 @@ export function collectLocalizationCandidates(filePath, sourceText, root = proce
ts.isNoSubstitutionTemplateLiteral(node) ||
ts.isTemplateExpression(node)
) {
const kind = classifyStringNode(node)
const kind = classifyStringNode(node, options.includeNativeDialogFields)
if (kind) {
for (const part of stringParts(node)) {
pushReport(node, kind, part.text, part.dynamic)
@@ -521,6 +511,7 @@ function parseArgs(argv) {
allowlistPath: path.join('config', 'localization-coverage-allowlist.json'),
check: false,
format: 'summary',
includeNativeDialogFields: false,
outputPath: null,
sourceRoots: []
}
@@ -531,6 +522,8 @@ function parseArgs(argv) {
options.format = 'json'
} else if (arg === '--markdown') {
options.format = 'markdown'
} else if (arg === '--native-dialog-fields') {
options.includeNativeDialogFields = true
} else if (arg === '--check') {
options.check = true
} else if (arg === '--allowlist') {
@@ -616,7 +609,7 @@ export async function main(root = process.cwd(), argv = process.argv.slice(2)) {
for (const filePath of files) {
const sourceText = await fs.readFile(filePath, 'utf8')
reports.push(...collectLocalizationCandidates(filePath, sourceText, root))
reports.push(...collectLocalizationCandidates(filePath, sourceText, root, options))
}
if (options.check) {
@@ -0,0 +1,59 @@
import { describe, expect, it, vi } from 'vitest'
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { collectLocalizationCandidates, main } from './audit-localization-coverage.mjs'
const path = '/repo/src/main/startup/dialog.ts'
const source = `
const options = {
message: translateMain('message', 'Translated message'),
detail: 'Untranslated detail',
buttons: ['Proceed', translateMain('cancel', 'Cancel')],
body: 'Untranslated body',
buttonLabel: 'Retry',
checkboxLabel: 'Remember choice'
}
`
describe('native dialog localization coverage', () => {
it('finds untranslated native dialog fields while ignoring translated copy', () => {
const candidates = collectLocalizationCandidates(path, source, '/repo', {
includeNativeDialogFields: true
})
expect(candidates.map(({ kind, text }) => [kind, text])).toEqual([
['object-property:detail', 'Untranslated detail'],
['object-property:buttons', 'Proceed'],
['object-property:body', 'Untranslated body'],
['object-property:buttonLabel', 'Retry'],
['object-property:checkboxLabel', 'Remember choice']
])
})
it('does not change the existing renderer scan by default', () => {
expect(collectLocalizationCandidates(path, source, '/repo')).toEqual([])
})
it('fails the opt-in check for a new native dialog literal', async () => {
const root = await mkdtemp(join(tmpdir(), 'orca-native-localization-'))
const report = vi.spyOn(console, 'error').mockImplementation(() => {})
try {
await mkdir(join(root, 'src'))
await writeFile(join(root, 'src', 'dialog.ts'), "const options = { detail: 'Raw detail' }")
await writeFile(join(root, 'allowlist.json'), '[]')
await expect(
main(root, [
'--check',
'--native-dialog-fields',
'--source-root',
'src',
'--allowlist',
'allowlist.json'
])
).resolves.toBe(1)
} finally {
report.mockRestore()
await rm(root, { recursive: true, force: true })
}
})
})
@@ -0,0 +1,33 @@
const USER_VISIBLE_OBJECT_KEYS = new Set([
'accessibilityHint',
'accessibilityLabel',
'ariaLabel',
'badge',
'description',
'emptyText',
'error',
'headerTitle',
'helperText',
'keywords',
'label',
'message',
'placeholder',
'subtitle',
'title',
'toggleDescription',
'tooltip'
])
const NATIVE_DIALOG_OBJECT_KEYS = new Set([
'detail',
'buttons',
'body',
'buttonLabel',
'checkboxLabel'
])
export function isVisibleObjectKey(name, includeNativeDialogFields) {
return (
USER_VISIBLE_OBJECT_KEYS.has(name) ||
(includeNativeDialogFields && NATIVE_DIALOG_OBJECT_KEYS.has(name))
)
}
@@ -32,7 +32,7 @@ import { View } from 'react-native'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { MobileNativeChatQueuedMessages } from './src/session/native-chat/input/MobileNativeChatQueuedMessages'
import { MobileNativeChatComposer } from './src/session/native-chat/composer/MobileNativeChatComposer'
import { mobileQueuedMessageCards } from './src/session/mobile-structured-queued-message-cards'
import { mobileQueuedMessageCards } from './src/session/native-chat/input/mobile-structured-queued-message-cards'
import { activateMobileUiLocale, translate } from './src/i18n/mobile-i18n'
const [mode, locale] = location.hash.slice(1).split(':')
@@ -19,7 +19,7 @@ export function stageOrcadMobileWebBundle(outputDir, bundleDir = MOBILE_WEB_BUND
// The release template refuses the variable itself.
assertNoDarkOutputOfOffSwitches([bundleDir])
assertMobileWebBundleBuilt(bundleDir)
// The guard refuses unlisted files, so the tree is the manifest, its assets and a dark marker.
// The guard refuses unlisted files, so the tree is the manifest, its assets and catalogs, and a dark marker.
cpSync(bundleDir, join(outputDir, ...ORCAD_MOBILE_WEB_DIR.split('/')), { recursive: true })
}
@@ -18,7 +18,7 @@ const { sortJiraIssues } = await jiti.import(
'../../../../src/renderer/src/components/jira-issue-sorter.ts'
)
const { sortAutomationListViewItems } = await jiti.import(
'../../../../src/renderer/src/components/automations/automation-list-view.ts'
'../../../../src/renderer/src/components/automations/listing/automation-list-view.ts'
)
const automationLocale = 'en-US'
@@ -4,7 +4,7 @@ import { performance } from 'node:perf_hooks'
import { transform } from 'esbuild'
import { buildCounterbalancedSchedule } from '../measurement/counterbalanced-benchmark-schedule.mjs'
const path = 'src/renderer/src/components/skills/skill-source-inventory.ts'
const path = 'src/renderer/src/components/skills/catalog/skill-source-inventory.ts'
const arms = {}
for (const [name, source] of [
['baseline', readFileSync(0, 'utf8')],
@@ -48,6 +48,7 @@ const RATCHET_INPUTS = {
'check-repo-host-read-ratchet.mjs': ['src/cli/', 'src/main/', 'src/shared/'],
'check-runtime-electron-ratchet.mjs': ['src/main/', 'src/cli/', 'src/shared/'],
'check-tab-map-seam-ratchet.mjs': ['src/renderer/src/'],
'check-terminal-parking-ownership-ratchet.mjs': ['src/renderer/src/'],
'check-unproven-pty-retirement-ratchet.mjs': ['src/main/'],
'check-workspace-record-read-ratchet.mjs': ['src/renderer/src/'],
'check-worktree-id-composer-ratchet.mjs': ['src/', 'mobile/src/']
@@ -102,7 +103,33 @@ export const VERIFY_CHECKS = [
'config/scripts/localization/source-extraction/audit-localization-coverage.mjs',
'--check'
],
inputs: ['src/renderer/src/', LOCALES]
inputs: [
'src/renderer/src/',
LOCALES,
'config/scripts/localization/source-extraction/audit-localization-coverage',
'config/scripts/localization/source-extraction/localization-visible-object-keys'
]
},
{
name: 'startup and worktree localization coverage',
args: [
'config/scripts/localization/source-extraction/audit-localization-coverage.mjs',
'--check',
'--native-dialog-fields',
'--source-root',
'src/main/startup',
'--source-root',
'src/shared/worktree',
'--allowlist',
'config/startup-worktree-localization-allowlist.json'
],
inputs: [
'src/main/startup/',
'src/shared/worktree/',
'config/scripts/localization/source-extraction/audit-localization-coverage',
'config/scripts/localization/source-extraction/localization-visible-object-keys',
'config/startup-worktree-localization-allowlist.json'
]
},
{
name: 'mobile localization',
@@ -145,7 +172,8 @@ export const VERIFY_CHECKS = [
'mobile/web-entry/',
'config/mobile-localization-coverage-allowlist.json',
'config/scripts/audit-localization-coverage',
'config/scripts/localization/source-extraction/audit-localization-coverage'
'config/scripts/localization/source-extraction/audit-localization-coverage',
'config/scripts/localization/source-extraction/localization-visible-object-keys'
]
},
{
@@ -18,6 +18,7 @@ export const LINT_STEPS = [
'check:test-file-runners',
'verify:localization-catalogs',
'verify:localization-coverage',
'verify:startup-worktree-localization-coverage',
'verify:mobile-localization',
'verify:mobile-localization-coverage',
'lint:ast-grep-gates',
@@ -40,6 +41,7 @@ export const LINT_STEPS = [
'check:ts-nocheck-ratchet',
'check:workspace-record-read-ratchet',
'check:tab-map-seam-ratchet',
'check:terminal-parking-ownership-ratchet',
'check:unproven-pty-retirement-ratchet',
'check:daemon-blocking-call-ratchet',
'check:main-sync-fs-ratchet',
@@ -299,7 +299,7 @@ function renderEvents({ events, eventContribution, typeText }, { services, frame
const { REFERENCE_TYPE_TEXT, zodTypeText } = typeText
const workerEvents = eventContribution.PLUGIN_EVENT_NAMES.map(
(name) =>
`- ${code(name)} (capability ${code(eventContribution.PLUGIN_EVENT_CAPABILITY[name])}; since ${eventContribution.PLUGIN_EVENT_SINCE[name]}): ${events.PLUGIN_EVENT_SUMMARIES[name]}\n ${code(zodTypeText(events.PLUGIN_EVENT_PAYLOAD_SCHEMAS[name], 'output', REFERENCE_TYPE_TEXT))}`
`- ${code(name)} (${eventContribution.PLUGIN_EVENT_CAPABILITY[name] ? `capability ${code(eventContribution.PLUGIN_EVENT_CAPABILITY[name])}` : 'no capability'}; since ${eventContribution.PLUGIN_EVENT_SINCE[name]}): ${events.PLUGIN_EVENT_SUMMARIES[name]}\n ${code(zodTypeText(events.PLUGIN_EVENT_PAYLOAD_SCHEMAS[name], 'output', REFERENCE_TYPE_TEXT))}`
)
return document(
'Events',
@@ -1,39 +0,0 @@
import { describe, expect, it } from 'vitest'
import { parseListedFiles } from '../../../../build-plugins/react-compiler-uncompiled-files.mjs'
import { findStaleUncompiledFiles } from './check-react-compiler-compat-ratchet.mjs'
describe('uncompiled file list', () => {
it('reads paths from list, count and hazard lines, dropping notes', () => {
const text = [
'# header',
'src/renderer/src/a.tsx',
'src/renderer/src/b.tsx # why it stays uncompiled',
'2 src/renderer/src/c.ts',
'1 src/renderer/src/d.ts#useD:store-read-in-memo-block',
''
].join('\n')
expect(parseListedFiles(text)).toEqual([
'src/renderer/src/a.tsx',
'src/renderer/src/b.tsx',
'src/renderer/src/c.ts',
'src/renderer/src/d.ts'
])
})
it('reports listed paths that no longer name a renderer file', () => {
const files = new Set([
'src/renderer/src/kept.tsx',
'src/renderer/src/notification-test-delivery.ts'
])
expect(
findStaleUncompiledFiles(
new Set([
'src/renderer/src/kept.tsx',
'src/renderer/src/notification-test-delivery.ts',
'src/renderer/src/renamed.tsx'
]),
(rel) => files.has(rel)
)
).toEqual(['src/renderer/src/renamed.tsx'])
})
})
@@ -1,87 +0,0 @@
import { describe, expect, it } from 'vitest'
import { CONSUMERS, consumerViolations, writerViolations } from './check-tab-map-seam-ratchet.mjs'
const BRIDGE =
'src/renderer/src/components/native-chat/session/status/StructuredAgentSessionStatusBridge.tsx'
const rules = CONSUMERS.get(BRIDGE)
describe('tab-map seam guard', () => {
it('rejects an ordinary handler reaching the cold path or walking a map', () => {
const text = [
'const reconcileFromSnapshot = () => getStructuredAgentSessionTabs(source)',
'const reconcile = () => reconcileFromSnapshot()',
'const ordinary = () => { reconcileFromSnapshot(); Object.keys(source) }'
].join('\n')
expect(consumerViolations('bridge.tsx', text, rules)).toEqual([
'bridge.tsx:3 reconcileFromSnapshot() outside a named cold path',
'bridge.tsx:3 Object.keys outside a named cold path'
])
})
it('rejects an unreviewed value import, not type imports', () => {
const text = [
"import { wholeMap } from './new-walk'",
"import type { Walk } from './new-walk'"
].join('\n')
expect(consumerViolations('bridge.tsx', text, rules)).toEqual([
'bridge.tsx:1 imports wholeMap; repaired consumers import only reviewed helpers'
])
})
it('follows a raw record through a local binding unless it is marked full resync', () => {
const text = [
'function raw(state) {',
' const next = { ...state.unifiedTabsByWorktree, [id]: tabs }',
' set({ unifiedTabsByWorktree: next })',
'}',
'function annotated(state) {',
' const next = { ...state.unifiedTabsByWorktree, [id]: tabs }',
' markFullResync(next, "hydration")',
' set({ unifiedTabsByWorktree: next })',
'}'
].join('\n')
expect(writerViolations('writer.ts', text)).toEqual([
'writer.ts:3 raw unifiedTabsByWorktree record; build it with a replace helper or draft, or mark it full resync'
])
})
it('follows aliases of cold helpers and enumerators', () => {
const text = [
'const reconcileFromSnapshot = () => getStructuredAgentSessionTabs(source)',
'const reconcile = () => reconcileFromSnapshot()',
'const resync = reconcileFromSnapshot',
'const keys = Object.keys'
].join('\n')
expect(consumerViolations('bridge.tsx', text, rules)).toEqual([
'bridge.tsx:3 reconcileFromSnapshot() outside a named cold path',
'bridge.tsx:4 Object.keys outside a named cold path'
])
})
it('follows aliased raw records and requires annotation before publication', () => {
const raw =
'raw unifiedTabsByWorktree record; build it with a replace helper or draft, or mark it full resync'
const text = [
'function aliased(state) {',
' const next = { ...state.unifiedTabsByWorktree, [id]: tabs }',
' const unifiedTabsByWorktree = next',
' set({ unifiedTabsByWorktree })',
'}',
'function late(state) {',
' const next = { ...state.unifiedTabsByWorktree, [id]: tabs }',
' set({ unifiedTabsByWorktree: next })',
' markFullResync(next, "hydration")',
'}',
'function early(state) {',
' const next = { ...state.unifiedTabsByWorktree, [id]: tabs }',
' const alias = next',
' markFullResync(alias, "hydration")',
' set({ unifiedTabsByWorktree: alias })',
'}'
].join('\n')
expect(writerViolations('writer.ts', text)).toEqual([
`writer.ts:4 ${raw}`,
`writer.ts:8 ${raw}`
])
})
})
@@ -0,0 +1,475 @@
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { pathToFileURL } from 'node:url'
import ts from 'typescript-api'
const RENDERER = 'src/renderer/src/'
const WATCHERS = `${RENDERER}components/terminal-pane/parking/output-watchers/`
const ROOTS = new Map([
[
`${RENDERER}components/terminal/terminal-provider-snapshot-bound-pty-ids.ts`,
['createTerminalProviderSnapshotBoundPtyIdsSelector']
],
[`${RENDERER}components/use-terminal-watcher-effects.ts`, ['useTerminalWatcherEffects']],
[`${RENDERER}components/use-terminal-parking-pass.ts`, ['useTerminalParkingPass']],
[
`${RENDERER}components/terminal-parking-pass-candidates.ts`,
['collectTerminalParkingPassCandidates']
],
[`${RENDERER}components/terminal/split-group-mount.ts`, ['anyMountedWorktreeHasLayout']],
[
`${RENDERER}components/terminal-pane/parking/lifecycle/use-parked-terminal-watcher-synchronization.ts`,
['useParkedTerminalWatcherSynchronization']
],
[
`${RENDERER}components/terminal-pane/parking/lifecycle/use-terminal-tab-cold-parking.ts`,
['useTerminalTabColdParking']
],
[`${WATCHERS}terminal-parked-tab-watchers.ts`, ['syncParkedTerminalTabWatchers']],
[`${WATCHERS}parked-terminal-tab-coverage.ts`, ['canWatcherCoverParkedTerminalTab']],
[
`${RENDERER}lib/agent-session/resume/resume-sleeping-agent-session.ts`,
['resumeSleepingAgentSessionsForWorktree']
]
])
const HOT_MAPS = new Set([
'tabsByWorktree',
'unifiedTabsByWorktree',
'terminalLayoutsByTabId',
'sleepingAgentSessionsByPaneKey'
])
const OWNER_ROOTS = [
{
rel: 'store/terminals/terminal-parking-input-owner.ts',
name: 'acceptTerminalParkingInputs',
inputs: []
},
{
rel: 'store/terminals/terminal-parking-input-owner.ts',
name: 'publishAcceptedTerminalParkingInputs',
inputs: []
},
{
rel: 'store/terminals/terminal-parking-input-owner.ts',
name: 'invalidateTerminalParkingPtyInputs',
inputs: []
},
{
rel: 'store/terminals/terminal-tab-owner-index.ts',
name: 'update',
inputs: ['tabsByWorktree']
},
{ rel: 'store/terminals/sleeping-agent-record-index.ts', name: 'update', inputs: ['records'] },
{
rel: 'store/terminals/sleeping-agent-record-index.ts',
name: 'acceptTransition',
inputs: ['previous', 'next']
},
{
rel: 'lib/workspace/tabs/worktree-tab-bucket-projection.ts',
name: 'project',
inputs: ['source']
}
]
const KEY_DIFFS = new Set([
`${RENDERER}store/terminals/terminal-parking-input-owner.ts#diffUnknownParkingRecordKeys`,
`${RENDERER}store/terminals/terminal-tab-owner-index.ts#diffUnknownTabOwnerKeys`,
`${RENDERER}store/terminals/sleeping-agent-record-index.ts#diffUnknownSleepingRecordKeys`,
`${RENDERER}lib/workspace/tabs/worktree-tab-bucket-projection.ts#diffUnknownProjectionKeys`
])
const COLD_RESYNCS = new Set([
`${RENDERER}store/terminals/terminal-parking-input-owner.ts#resyncTerminalParkingWorkspaceIds`,
`${RENDERER}store/terminals/sleeping-agent-record-index.ts#resyncSleepingAgentRecordKeys`
])
function isColdResyncCall(node, source) {
for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
if (
ts.isConditionalExpression(ancestor) &&
ancestor.condition.getText(source) === 'previous' &&
node.pos >= ancestor.whenFalse.pos
) {
return true
}
if (
ts.isBinaryExpression(ancestor) &&
ancestor.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken &&
ancestor.left.getText(source) === 'keys'
) {
return true
}
if (
ts.isIfStatement(ancestor) &&
node.pos >= ancestor.thenStatement.pos &&
node.end <= ancestor.thenStatement.end
) {
const condition = ancestor.expression.getText(source).replace(/\s+/g, ' ')
if (
condition === 'ptyIds === undefined' ||
condition.includes(
'previous.settings?.terminalSshViewParking !== state.settings?.terminalSshViewParking'
)
) {
return true
}
}
}
return false
}
const STORE_BOUNDARIES = new Set([`${RENDERER}store/index.ts`, `${RENDERER}store.ts`])
// Resource delivery and startup recovery are separate entry points, not parking input discovery.
const EFFECT_BOUNDARIES = new Set([
`${RENDERER}components/terminal/terminal-provider-snapshot-bound-pty-ids.ts#resyncUnknownSnapshot`,
`${RENDERER}components/terminal/use-terminal-startup-activation.ts#useTerminalStartupActivation`,
`${WATCHERS}terminal-parked-pty-watcher.ts#startParkedPtyWatcher`,
`${RENDERER}components/terminal-pane/parking/capture/parked-terminal-buffer-capture.ts#captureParkedTerminalBuffers`,
`${RENDERER}lib/agent-session/resume/sleeping-agent-session-launch.ts#launchSleepingAgentSession`,
`${RENDERER}lib/host-mirror-handle-gap-wait.ts#parkUntilHostMirrorHandleLands`,
`${RENDERER}runtime/web-session-tabs-sync/hydration/host-session-mirror-hydration.ts#parkUntilHostSessionMirrorHydrates`
])
const ENUMERATORS = new Set(['entries', 'values', 'keys', 'assign'])
const ADDRESSED_FIELDS = new Set([
'terminalLayoutsByTabId',
'ptyIdsByTabId',
'lastKnownRelayPtyIdByTabId',
'runtimePaneTitlesByTabId',
'pendingStartupByTabId',
'sleepingAgentSessionsByPaneKey'
])
const ORDINARY_WRITERS = [
'store/terminals/terminal-layout-state.ts',
'store/terminals/terminal-pane-hibernation.ts',
'store/terminals/terminal-pty-bindings.ts',
'store/terminals/terminal-pty-release.ts',
'store/terminals/terminal-orphan-cleanup.ts',
'store/terminals/terminal-startup-queues.ts',
'store/terminals/terminal-tab-presentation.ts',
'store/terminals/terminal-shutdown-state.ts',
'store/terminals/window-tab-start-in-main.ts',
'store/slices/agent-status/agent-status-live-reducer.ts',
'store/slices/agent-status/agent-status-launch-actions.ts',
'store/slices/agent-status/agent-status-provider-session-actions.ts',
'store/slices/agent-status/agent-status-recovery-actions.ts',
'store/slices/agent-status/agent-status-authority-actions.ts'
].map((rel) => RENDERER + rel)
const RECORD_TRANSITIONS = new Set([
'replaceRecordEntries',
'removePaneKeys',
'removePaneKeysByTabPrefix'
])
function collectUnaddressedWrites(root) {
const violations = []
for (const rel of ORDINARY_WRITERS) {
const { source, aliases, assignments } = readModule(root, rel)
function addressed(value, field, seen = new Set()) {
if (ts.isCallExpression(value) && ts.isIdentifier(value.expression)) {
return RECORD_TRANSITIONS.has(value.expression.text)
}
if (ts.isPropertyAccessExpression(value)) {
return value.name.text === field
}
if (ts.isConditionalExpression(value)) {
return addressed(value.whenTrue, field, seen) && addressed(value.whenFalse, field, seen)
}
if (ts.isIdentifier(value)) {
if (seen.has(value.text)) {
return false
}
seen.add(value.text)
const alias = aliases.get(value.text)
return (
alias !== undefined &&
[alias, ...(assignments.get(value.text) ?? [])].every((replacement) =>
addressed(replacement, field, new Set(seen))
)
)
}
return false
}
function visit(node) {
if (ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node)) {
const field =
ts.isIdentifier(node.name) || ts.isStringLiteral(node.name) ? node.name.text : null
const value = ts.isPropertyAssignment(node) ? node.initializer : node.name
if (ADDRESSED_FIELDS.has(field) && !addressed(value, field)) {
const line = source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1
violations.push(`${rel}:${line}: ${field} write needs complete record-key provenance.`)
}
}
ts.forEachChild(node, visit)
}
visit(source)
}
return violations
}
function resolveImport(root, from, specifier) {
const base = specifier.startsWith('@/')
? RENDERER + specifier.slice(2)
: specifier.startsWith('.')
? path.posix.join(path.posix.dirname(from), specifier)
: null
if (!base) {
return null
}
return (
[base, `${base}.ts`, `${base}.tsx`, `${base}/index.ts`].find(
(candidate) =>
fs.existsSync(path.join(root, candidate)) &&
fs.statSync(path.join(root, candidate)).isFile()
) ?? null
)
}
function readModule(root, rel) {
const source = ts.createSourceFile(
rel,
fs.readFileSync(path.join(root, rel), 'utf8'),
ts.ScriptTarget.Latest,
true
)
const imports = new Map()
const functions = new Map()
const aliases = new Map()
const assignments = new Map()
function visit(node) {
if (
ts.isBinaryExpression(node) &&
ts.isIdentifier(node.left) &&
node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
node.operatorToken.kind <= ts.SyntaxKind.LastAssignment
) {
const values = assignments.get(node.left.text) ?? []
values.push(node.right)
assignments.set(node.left.text, values)
}
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
const target = resolveImport(root, rel, node.moduleSpecifier.text)
const bindings = node.importClause?.namedBindings
if (target && bindings && ts.isNamedImports(bindings)) {
for (const binding of bindings.elements) {
imports.set(binding.name.text, {
rel: target,
name: binding.propertyName?.text ?? binding.name.text
})
}
}
}
if (
(ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) &&
node.name &&
node.body
) {
functions.set(node.name.text, node)
}
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
aliases.set(node.name.text, node.initializer)
if (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer)) {
functions.set(node.name.text, node.initializer)
}
}
ts.forEachChild(node, visit)
}
visit(source)
return { source, imports, functions, aliases, assignments }
}
export function collectParkingOwnershipViolations(root = process.cwd()) {
const modules = new Map()
const pending = [...ROOTS].flatMap(([rel, names]) =>
names.map((name) => ({ rel, name, tainted: new Set() }))
)
for (const { rel, name, inputs } of OWNER_ROOTS) {
pending.push({ rel: RENDERER + rel, name, tainted: new Set(inputs) })
}
const visited = new Set()
const violations = new Set()
while (pending.length) {
const { rel, name, tainted } = pending.pop()
if (STORE_BOUNDARIES.has(rel) || EFFECT_BOUNDARIES.has(`${rel}#${name}`)) {
continue
}
const identity = `${rel}#${name}#${[...tainted].sort().join(',')}`
if (visited.has(identity)) {
continue
}
visited.add(identity)
let mod = modules.get(rel)
if (!mod) {
modules.set(rel, (mod = readModule(root, rel)))
}
const fn = mod.functions.get(name)
if (!fn) {
continue
}
function hotMap(node, seen = new Set()) {
if (ts.isPropertyAccessExpression(node)) {
if (HOT_MAPS.has(node.name.text)) {
return true
}
if (ts.isIdentifier(node.expression)) {
const value = mod.aliases.get(node.expression.text)
if (value && ts.isObjectLiteralExpression(value)) {
const property = value.properties.find(
(candidate) =>
ts.isPropertyAssignment(candidate) &&
candidate.name.getText(mod.source) === node.name.text
)
if (property && ts.isPropertyAssignment(property)) {
return hotMap(property.initializer, seen)
}
}
}
return false
}
if (ts.isElementAccessExpression(node)) {
return (
ts.isStringLiteral(node.argumentExpression) && HOT_MAPS.has(node.argumentExpression.text)
)
}
if (ts.isIdentifier(node)) {
if (HOT_MAPS.has(node.text) || tainted.has(node.text)) {
return true
}
if (seen.has(node.text)) {
return false
}
seen.add(node.text)
const alias = mod.aliases.get(node.text)
return alias ? hotMap(alias, seen) : false
}
if (
ts.isParenthesizedExpression(node) ||
ts.isAsExpression(node) ||
ts.isNonNullExpression(node)
) {
return hotMap(node.expression, seen)
}
if (
ts.isBinaryExpression(node) &&
node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken
) {
return hotMap(node.left, seen)
}
return false
}
function report(node) {
const line = mod.source.getLineAndCharacterOfPosition(node.getStart(mod.source)).line + 1
violations.add(
`${rel}:${line}: ${name} enumerates a whole parking input; use the addressed owner or a named source resync.`
)
}
function visit(node) {
if (
ts.isFunctionDeclaration(node) &&
node.name &&
EFFECT_BOUNDARIES.has(`${rel}#${node.name.text}`)
) {
return
}
if (ts.isForInStatement(node) && hotMap(node.expression)) {
report(node)
}
if (ts.isCallExpression(node)) {
const callee = node.expression
if (
ts.isPropertyAccessExpression(callee) &&
ts.isIdentifier(callee.expression) &&
callee.expression.text === 'Object' &&
ENUMERATORS.has(callee.name.text) &&
node.arguments.some((arg) => hotMap(arg)) &&
!(callee.name.text === 'keys' && KEY_DIFFS.has(`${rel}#${name}`)) &&
!COLD_RESYNCS.has(`${rel}#${name}`)
) {
report(node)
}
if (ts.isIdentifier(callee)) {
const target = mod.imports.get(callee.text) ?? { rel, name: callee.text }
if (
COLD_RESYNCS.has(`${target.rel}#${target.name}`) &&
!isColdResyncCall(node, mod.source)
) {
report(node)
}
if (!STORE_BOUNDARIES.has(target.rel)) {
let targetModule = modules.get(target.rel)
if (!targetModule) {
modules.set(target.rel, (targetModule = readModule(root, target.rel)))
}
const called = targetModule.functions.get(target.name)
const parameters = new Set()
called?.parameters.forEach((parameter, index) => {
if (
ts.isIdentifier(parameter.name) &&
node.arguments[index] &&
hotMap(node.arguments[index])
) {
parameters.add(parameter.name.text)
}
})
if (called) {
pending.push({ ...target, tainted: parameters })
}
}
}
}
ts.forEachChild(node, visit)
}
visit(fn.body)
}
return [...violations].sort()
}
function collectPublicCoverageViolations(root) {
const rel = `${WATCHERS}parked-terminal-tab-coverage.ts`
const { source } = readModule(root, rel)
const privateNames = new Set([
'fallbackParkedPaneCandidates',
'resolveParkedTerminalPaneCandidates',
'capturedCoversFallback'
])
const violations = []
for (const statement of source.statements) {
const exported =
ts.canHaveModifiers(statement) &&
ts.getModifiers(statement)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)
const leaksDeclaration =
exported && ts.isFunctionDeclaration(statement) && privateNames.has(statement.name?.text)
const leaksNamedExport =
ts.isExportDeclaration(statement) &&
statement.exportClause &&
ts.isNamedExports(statement.exportClause) &&
statement.exportClause.elements.some((element) =>
privateNames.has(element.propertyName?.text ?? element.name.text)
)
if (leaksDeclaration || leaksNamedExport) {
violations.push(`${rel}: raw coverage derivation must stay private to its owner.`)
}
}
return violations
}
export function main(root = process.cwd()) {
const violations = [
...collectParkingOwnershipViolations(root),
...collectUnaddressedWrites(root),
...collectPublicCoverageViolations(root)
]
for (const violation of violations) {
console.error(violation)
}
if (violations.length) {
return 1
}
console.log(
'terminal-parking-ownership: addressed consumer paths contain no whole input enumeration'
)
return 0
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
process.exitCode = main()
}
@@ -0,0 +1,16 @@
import { TestRunner } from 'vitest'
export default class SharedWorkerSetupRunner extends TestRunner {
importFile(filepath, source) {
if (source === 'setup') {
// Warm transforms can key setup roots by a different URL than Vitest invalidates.
const modules = this.moduleRunner.evaluatedModules
for (const node of modules.getModulesByFile(filepath) ?? []) {
if (node.evaluated) {
modules.invalidateModule(node)
}
}
}
return super.importFile(filepath, source)
}
}
@@ -27,8 +27,6 @@ const NODE_RUNTIME_INCLUDE = [
'src/main/browser/extensions/native-messaging/browser-extension-native-host-process-backpressure.test.ts',
'src/main/daemon/providers/lifecycle/testing/daemon-init-provider-installation.test.ts',
'src/renderer/src/components/terminal-pane/pty-transport/input/pty-input-write-queue.test.ts',
'src/renderer/src/lib/react-commit-cascade-observer.test.ts',
'src/renderer/src/lib/react-commit-cascade-telemetry.test.ts',
// Asserts payloads are collected; Bun's GC scans the stack conservatively and may keep one alive.
'src/shared/ws-inbound-message-queue.test.ts',
'src/renderer/src/lib/agent-session/history/title-sync/ai-vault-tab-title-sync-inputs.test.ts',
@@ -137,7 +135,7 @@ const NODE_RUNTIME_INCLUDE = [
'src/main/gitlab/gitlab-known-host-retirement.test.ts',
'src/main/window/main-window-updater-deferred-setup.test.ts',
'src/renderer/src/components/terminal-pane/host-state/terminal-pane-host-state-memo.test.ts',
'src/renderer/src/components/automations/use-automation-runs-dashboard-payload.test.tsx',
'src/renderer/src/components/automations/runs/use-automation-runs-dashboard-payload.test.tsx',
'src/main/native-chat/agent-session-journal/**/*.test.ts',
'src/main/native-chat/agent-session-wire/host/testing/structured-agent-session-terminal-writer-acquire.test.ts',
'src/main/runtime/agent-session/records/agent-session-conversation-name-store.test.ts',
@@ -169,7 +167,14 @@ const NODE_RUNTIME_INCLUDE = [
'src/main/claude/claude-agent-sdk-contract-pins.test.ts',
'config/scripts/orcad-distribution/server-bundle/profile-state-worker-smoke.test.mjs',
'config/scripts/desktop-distribution/runtime-verification/process-host-dev-rebuild.test.ts',
'config/scripts/test-runtime/node-worker/vitest-node-runtime-pool.test.ts'
'config/scripts/test-runtime/node-worker/vitest-node-runtime-pool.test.ts',
// Reopen WAL-backed row caches read-only, which Bun on macOS cannot.
'src/main/agent-hooks/server/testing/identity/server-hook-session-binding.test.ts',
'tests/e2e/helpers/persisted-profile-state.unit.test.ts',
'src/main/runtime/push/notifications/mobile-notification-dismissal-store.test.ts',
'src/main/runtime/push/notifications/mobile-notification-dismissal-read-failure.test.ts',
'src/main/ipc/notifications-subject-retirement.test.ts',
'src/main/ipc/notifications-external-acknowledgement.test.ts'
]
/** `filters` limits the SQLite import scan to the tests a filtered CLI run can select. */
@@ -25,7 +25,7 @@ const LAYOUT_ORACLE_PREFIXES = [
'src/renderer/src/store/workspace-layout/workspace-layout-cache',
'src/renderer/src/components/terminal-pane/',
'src/renderer/src/components/agent-session-continuation/AgentSessionContinuationMenuItem.tsx',
'src/renderer/src/components/orchestration-crew/WorkerAutoCloseBanner.tsx',
'src/renderer/src/components/terminal-pane/worker-lifetime/WorkerAutoCloseBanner.tsx',
'src/renderer/src/attention/orca-window-foreground-focus.ts',
'src/renderer/src/attention/notification-workspace-labels.ts',
'src/renderer/src/lib/agent-session/identity/pane-agent-session-id.ts',
@@ -0,0 +1,26 @@
[
{
"filePath": "src/main/startup/main-process-quit.ts",
"kind": "object-property:message",
"text": "before-quit allowed for update install",
"dynamic": false,
"count": 1,
"reason": "Updater lifecycle diagnostics are not displayed as UI copy."
},
{
"filePath": "src/main/startup/main-process-quit.ts",
"kind": "object-property:message",
"text": "will-quit cleanup for update install; daemonTeardown=disconnect",
"dynamic": false,
"count": 1,
"reason": "Updater lifecycle diagnostics are not displayed as UI copy."
},
{
"filePath": "src/main/startup/profile-state-recovery-preflight.ts",
"kind": "object-property:message",
"text": "Invalid profile-state recovery launch.",
"dynamic": false,
"count": 1,
"reason": "Existing recovery detail is user-visible and remains untranslated; tracked in issue 32207."
}
]
+1 -2
View File
@@ -23,8 +23,7 @@
"../src/cli/runtime/runtime-access-denied.ts",
"../src/cli/runtime/runtime-pid-liveness.ts",
"../src/renderer/src/store/slices/worktrees/create/worktree-create-payload.ts",
"../src/renderer/src/lib/orca-hook-trust.ts",
"../src/renderer/src/lib/sha256.ts",
"../src/renderer/src/runtime/repo-hooks/orca-hook-trust.ts",
"../src/renderer/src/components/native-chat/native-chat-resolution-receipt.ts",
"../src/renderer/src/components/native-chat/structured-agent-question-projection.ts",
"../src/renderer/src/components/native-chat/subagents/native-chat-subagent-delegation.ts",
-1
View File
@@ -3,7 +3,6 @@
"exclude": [
"../src/renderer/src/lib/agent-session/launch/new-tab/launch-agent-in-new-tab-main-surface.test.ts",
"../src/renderer/src/components/task-page/linear/task-page-linear-detail-query.test.tsx",
"../tests/e2e/terminal-agent-pane-launch.unit.test.ts",
"../src/renderer/src/runtime/provider-accounts/observations/provider-account-observation.test-fixture.ts",
"../src/shared/**/*.test.ts",
"../src/shared/**/*.test.tsx",
-1
View File
@@ -5,7 +5,6 @@
"../src/main/runtime/orchestration/terminal-authority/structured-sessions/structured-chat-coordinator-mail.test.ts",
"../src/main/runtime/rpc/methods/orchestration/orchestration-check-ambiguous-caller.test.ts",
"../src/renderer/src/components/task-page/linear/task-page-linear-detail-query.test.tsx",
"../tests/e2e/terminal-agent-pane-launch.unit.test.ts",
"../src/renderer/src/runtime/observed-provider-accounts.test.tsx",
"../src/renderer/src/components/status-bar/ClaudeSwitcherMenu.observation.test.tsx",
"../src/renderer/src/runtime/provider-accounts/observations/provider-account-observation.test-fixture.ts",
-1
View File
@@ -13,7 +13,6 @@
"exclude": [
"../src/renderer/src/lib/agent-session/launch/new-tab/launch-agent-in-new-tab-main-surface.test.ts",
"../src/renderer/src/components/task-page/linear/task-page-linear-detail-query.test.tsx",
"../tests/e2e/terminal-agent-pane-launch.unit.test.ts",
"../src/renderer/src/runtime/provider-accounts/observations/provider-account-observation.test-fixture.ts"
],
"compilerOptions": {
+6 -1
View File
@@ -126,10 +126,12 @@ const bunProject = {
}
// Files whose state the per-file reset can undo share one worker's module cache instead of
// re-importing thousands of modules each. ORCA_VITEST_ISOLATE_EVERY_FILE=1 turns it off.
// Windows workers that keep native addons (process tree, ConPTY) across files die of heap
// corruption (0xC0000374); production code loads them lazily, so the file scan can't see them.
const runtimeProject = process.versions.bun ? bunProject : nodeProject
const nodeRuntimeFiles = new Set(nodeRuntimeInclude.map((file) => file.replaceAll('\\', '/')))
const sharedWorkerFiles =
process.env.ORCA_VITEST_ISOLATE_EVERY_FILE === '1'
process.env.ORCA_VITEST_ISOLATE_EVERY_FILE === '1' || process.platform === 'win32'
? []
: filesSafeToShareWorker(
process.cwd(),
@@ -151,6 +153,9 @@ const sharedProjects =
name: `${runtimeProject.test.name}-shared`,
include: sharedWorkerFiles,
isolate: false,
runner: resolve(
'config/scripts/test-runtime/execution-policy/vitest-shared-worker-setup-runner.mjs'
),
setupFiles: [
...runtimeProject.test.setupFiles,
resolve('config/scripts/test-runtime/environment/vitest-shared-worker-file-reset.mjs')
-1
View File
@@ -17,7 +17,6 @@ const contracts = [
'src/main/codex-usage/scanner-incremental-append.test.ts',
'src/main/usage/jsonl-file-reader.test.ts',
'src/main/usage/jsonl-file-snapshot.test.ts',
'src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler-queue-retention.test.ts',
'config/scripts/static-analysis/oxlint-contracts/app-store-performance-plugin.test.mjs',
'config/scripts/static-analysis/oxlint-contracts/quadratic-buffer-concat-plugin.test.mjs',
'src/main/claude-usage/scanner-incremental-append.test.ts',
+3 -3
View File
@@ -22,7 +22,7 @@
1 src/renderer/src/components/sidebar/worktree-list/navigation/pending-reveal-inputs.ts # reads a passed-in copy
1 src/renderer/src/components/sidebar/worktree-list/navigation/use-pending-reveal.ts # reads a passed-in copy
2 src/renderer/src/components/sidebar/worktree-list/viewport/VirtualizedWorktreeViewport.tsx # reads a passed-in copy
2 src/renderer/src/components/skills/skill-install-workspace-choices.ts # reads a passed-in copy
2 src/renderer/src/components/skills/installation/skill-install-workspace-choices.ts # reads a passed-in copy
1 src/renderer/src/components/terminal-pane/links/files/terminal-worktree-path-link.ts # P5b-2 must clear: loosely typed state param
1 src/renderer/src/components/workspace-space/space-window-state.ts # reads a passed-in copy
1 src/renderer/src/hooks/composer-state/composer-source-state.ts # P5b-2 must clear: composer store snapshot
@@ -36,10 +36,10 @@
1 src/renderer/src/runtime/hosts/wsl/local-project-runtime-workspace.ts # P5b-2 must clear: optional state field defaults to empty
1 src/renderer/src/lib/resolved-worktree-execution-host.ts # P5b-2 must clear: loosely typed state param
2 src/renderer/src/lib/workspace/ownership/runtime-session-mirror-owners.ts # P5b-2 must clear: optional state fields default to empty
2 src/renderer/src/lib/terminal-worktree-route.ts # P5b-2 must clear: the unattributed-twin check reads both catalogs directly
2 src/renderer/src/lib/workspace/ownership/terminal-worktree-route.ts # P5b-2 must clear: the unattributed-twin check reads both catalogs directly
3 src/renderer/src/lib/typing-latency/diagnostic-summary.ts # P5b-2 must clear: optional state field
2 src/renderer/src/lib/unread-badge-count.ts # reads a passed-in copy
1 src/renderer/src/lib/workspace-directory.ts # P5b-2 must clear: optional state field defaults to empty
1 src/renderer/src/lib/workspace/paths/workspace-directory.ts # P5b-2 must clear: optional state field defaults to empty
2 src/renderer/src/lib/workspace/ownership/workspace-terminal-host-authority.ts # P5b-2 must clear: optional state fields
1 src/renderer/src/lib/workspace/activation/initial-surface/worktree-initial-terminal-seeding.ts # P5b-2 must clear: treats an undefined field as a narrow adapter
1 src/renderer/src/lib/workspace/activation/worktree-activation-pty-inventory.ts # P5b-2 must clear: loosely typed state param
+4 -4
View File
@@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="113" height="20" role="img" aria-label="downloads: 163m">
<title>downloads: 163m</title>
<svg xmlns="http://www.w3.org/2000/svg" width="113" height="20" role="img" aria-label="downloads: 168m">
<title>downloads: 168m</title>
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
@@ -15,7 +15,7 @@
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
<text x="37" y="15" fill="#010101" fill-opacity=".3">downloads</text>
<text x="37" y="14">downloads</text>
<text x="93.5" y="15" fill="#010101" fill-opacity=".3">163m</text>
<text x="93.5" y="14">163m</text>
<text x="93.5" y="15" fill="#010101" fill-opacity=".3">168m</text>
<text x="93.5" y="14">168m</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 943 B

After

Width:  |  Height:  |  Size: 943 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 385 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 KiB

After

Width:  |  Height:  |  Size: 395 KiB

+1 -1
View File
@@ -53,5 +53,5 @@ pnpm dev:fresh [--keep] [--seed-from[=<profile dir>]] # temporary profile, rem
- Slow main-process rebuilds: set `ORCA_DEV_MAIN_SOURCEMAPS=0` before `pnpm dev` when you don't need main-process source maps.
- Renderer misbehaving in dev: set `ORCA_DEV_BUNDLED=0` to turn off bundled dev mode, and report it.
- Test passes alone but fails after others: add `// @orca-isolated-worker` to the file; `ORCA_VITEST_ISOLATE_EVERY_FILE=1` isolates every file to confirm a leak.
- Test passes alone but fails after others: add `// @orca-isolated-worker` to the file; `ORCA_VITEST_ISOLATE_EVERY_FILE=1` isolates every file to confirm a leak. Windows always isolates every file, because native addons kept across files corrupt the heap.
- Slow typechecks: where supported, `pnpm tc` keeps incremental caches in `.cache/typecheck/`; delete it to force a fresh check.
+2 -4
View File
@@ -249,11 +249,9 @@ Associez-la à l'app de bureau pour surveiller et piloter vos agents depuis votr
- **Discord :** Rejoignez la communauté sur **[Discord](https://discord.gg/fzjDKHxv8Q)**.
- **Twitter / X :** Suivez **[@orca_build](https://x.com/orca_build)** pour les news et annonces.
- **WeChat :** Scannez pour rejoindre un groupe WeChat de la communauté Orca. Si un groupe est complet, rejoignez-en un autre.
- **WeChat :** Scannez pour rejoindre un groupe WeChat de la communauté Orca.
<img src="../assets/wechat-qr-group14.jpg" alt="QR code du groupe WeChat de la communauté Orca" width="160" />
<img src="../assets/wechat-qr-group15.jpg" alt="QR code du groupe WeChat de la communauté Orca" width="160" />
<img src="../assets/wechat-qr-group16.jpg" alt="QR code du groupe WeChat de la communauté Orca" width="160" />
<img src="../assets/wechat-qr.jpg" alt="QR code du groupe WeChat de la communauté Orca" width="160" />
- **Feedback &amp; idées :** On ship vite. Il manque quelque chose ? [Demandez une feature](https://github.com/stablyai/orca/issues).
- **Confidentialité :** Voir la [doc confidentialité &amp; télémétrie](https://www.onorca.dev/docs/telemetry) pour ce qu'Orca collecte en anonyme et comment désactiver la télémétrie.
+2 -4
View File
@@ -244,11 +244,9 @@ yay -S stably-orca-bin
- **Discord:** **[Discord](https://discord.gg/fzjDKHxv8Q)** 커뮤니티에 참여하세요.
- **Twitter / X:** 업데이트와 공지는 **[@orca_build](https://x.com/orca_build)** 를 팔로우하세요.
- **WeChat:** QR 코드를 스캔해 Orca 커뮤니티 WeChat 그룹에 참여하세요. 한 그룹이 가득 찼다면 다른 그룹에 참여하세요.
- **WeChat:** QR 코드를 스캔해 Orca 커뮤니티 WeChat 그룹에 참여하세요.
<img src="../assets/wechat-qr-group14.jpg" alt="Orca 커뮤니티 WeChat 그룹 QR 코드" width="160" />
<img src="../assets/wechat-qr-group15.jpg" alt="Orca 커뮤니티 WeChat 그룹 QR 코드" width="160" />
<img src="../assets/wechat-qr-group16.jpg" alt="Orca 커뮤니티 WeChat 그룹 QR 코드" width="160" />
<img src="../assets/wechat-qr.jpg" alt="Orca 커뮤니티 WeChat 그룹 QR 코드" width="160" />
- **피드백과 아이디어:** 우리는 빠르게 출시합니다. 필요한 기능이 있나요? [새 기능을 요청](https://github.com/stablyai/orca/issues)하세요.
- **개인정보 보호:** Orca가 수집하는 익명 사용 데이터와 수집 거부 방법은 [개인정보 및 텔레메트리 문서](https://www.onorca.dev/docs/telemetry)를 참고하세요.
+2 -4
View File
@@ -245,11 +245,9 @@ yay -S stably-orca-bin
- **Discord:** Присоединяйтесь к сообществу в **[Discord](https://discord.gg/fzjDKHxv8Q)**.
- **Twitter / X:** Подпишитесь на **[@orca_build](https://x.com/orca_build)**, чтобы узнавать об обновлениях и анонсах.
- **WeChat:** Отсканируйте QR-код, чтобы присоединиться к группе сообщества Orca в WeChat. Если одна группа заполнена, присоединяйтесь к другой.
- **WeChat:** Отсканируйте QR-код, чтобы присоединиться к группе сообщества Orca в WeChat.
<img src="../assets/wechat-qr-group14.jpg" alt="QR-код группы сообщества Orca в WeChat" width="160" />
<img src="../assets/wechat-qr-group15.jpg" alt="QR-код группы сообщества Orca в WeChat" width="160" />
<img src="../assets/wechat-qr-group16.jpg" alt="QR-код группы сообщества Orca в WeChat" width="160" />
<img src="../assets/wechat-qr.jpg" alt="QR-код группы сообщества Orca в WeChat" width="160" />
- **Отзывы и идеи:** Мы быстро выпускаем обновления. Чего-то не хватает? [Предложите новую возможность](https://github.com/stablyai/orca/issues).
- **Конфиденциальность:** В [документации о конфиденциальности и телеметрии](https://www.onorca.dev/docs/telemetry) описано, какие анонимные данные об использовании собирает Orca и как отказаться от их сбора.
+2 -4
View File
@@ -242,11 +242,9 @@ Ajanlarınızı telefonunuzdan izlemek ve yönlendirmek için masaüstü uygulam
- **Discord:** Topluluğa **[Discord](https://discord.gg/fzjDKHxv8Q)** üzerinden katılın.
- **Twitter / X:** Güncellemeler ve duyurular için **[@orca_build](https://x.com/orca_build)** hesabını takip edin.
- **WeChat:** Orca topluluğunun bir WeChat grubuna katılmak için karekodu okutun. Bir grup doluysa başka bir gruba katılabilirsiniz.
- **WeChat:** Orca topluluğunun bir WeChat grubuna katılmak için karekodu okutun.
<img src="../assets/wechat-qr-group14.jpg" alt="Orca topluluğunun WeChat grubu karekodu" width="160" />
<img src="../assets/wechat-qr-group15.jpg" alt="Orca topluluğunun WeChat grubu karekodu" width="160" />
<img src="../assets/wechat-qr-group16.jpg" alt="Orca topluluğunun WeChat grubu karekodu" width="160" />
<img src="../assets/wechat-qr.jpg" alt="Orca topluluğunun WeChat grubu karekodu" width="160" />
- **Geri bildirim ve fikirler:** Hızlı sürüm çıkarıyoruz. Eksik bulduğunuz bir şey mi var? [Yeni bir özellik isteyin](https://github.com/stablyai/orca/issues).
- **Gizlilik:** Orca'nın hangi anonim kullanım verisini topladığını ve bunu nasıl kapatacağınızı [gizlilik ve telemetri dokümanlarında](https://www.onorca.dev/docs/telemetry) bulabilirsiniz.
+2 -4
View File
@@ -242,11 +242,9 @@ yay -S stably-orca-bin
- **Discord:** 加入 **[Discord](https://discord.gg/fzjDKHxv8Q)** 社区。
- **Twitter / X:** 关注 **[@orca_build](https://x.com/orca_build)** 获取更新和公告。
- **微信:** 扫码加入 Orca 社区微信群。如果一个群已满,可以加入其他群。
- **微信:** 扫码加入 Orca 社区微信群。
<img src="../assets/wechat-qr-group14.jpg" alt="Orca 社区微信群二维码" width="160" />
<img src="../assets/wechat-qr-group15.jpg" alt="Orca 社区微信群二维码" width="160" />
<img src="../assets/wechat-qr-group16.jpg" alt="Orca 社区微信群二维码" width="160" />
<img src="../assets/wechat-qr.jpg" alt="Orca 社区微信群二维码" width="160" />
- **反馈与想法:** 我们发布很快。缺少什么功能?[提交功能请求](https://github.com/stablyai/orca/issues)。
- **隐私:** 查看[隐私与遥测文档](https://www.onorca.dev/docs/telemetry),了解 Orca 收集哪些匿名使用数据以及如何退出。
+15 -5
View File
@@ -2,7 +2,7 @@
<!-- Generated by `pnpm generate:plugin-schemas` from src/shared/plugins. Do not edit. -->
Host API 1.5 (`"pluginApi": 1`). Workers call a method with `await orca.host.call(name, params)`; panels and custom editors call the same methods through `window.orca.host.call(name, params)`. Each method lists the surfaces allowed to call it; any other surface gets `not_permitted`. `orca.host.has(name)` answers synchronously whether this Orca offers a method on the current surface, and `orca.api` is `{ major, minor }`. See [Plugin authoring](./plugin-authoring.md) for the frame API.
Host API 1.6 (`"pluginApi": 1`). Workers call a method with `await orca.host.call(name, params)`; panels and custom editors call the same methods through `window.orca.host.call(name, params)`. Each method lists the surfaces allowed to call it; any other surface gets `not_permitted`. `orca.host.has(name)` answers synchronously whether this Orca offers a method on the current surface, and `orca.api` is `{ major, minor }`. See [Plugin authoring](./plugin-authoring.md) for the frame API.
Every call is checked against the capabilities the user approved, and params are validated before Orca runs anything: params are strict, so a field this Orca does not know is refused with `invalid_params` instead of being dropped. Mutations are recorded in the audit log as `plugin:<publisher>.<id>`.
@@ -29,7 +29,7 @@ Methods whose capability is `none` need no capability but still need the plugin
| [`files.saveAs`](#filessaveas) | none | panel, editor | yes | 1.1 | Offers data (a string or ArrayBuffer, up to 64 MiB) in Orca's save dialog; the plugin never learns the path. Needs a click or key press in the frame; desktop app only. |
| [`plugin.request`](#pluginrequest) | none | panel, editor | no | 1.2 | Asks the plugin's own worker to run the handler it registered with `orca.panels.handle(name)`, starting the worker if needed; resolves with what the handler returned. `unknown_method` when no handler has that name. |
| [`agents.list`](#agentslist) | `agent:read` | worker, panel, editor | no | 1.2 | The agents in the focused workspace: their terminal, title, agent kind and status. `agent.status.changed` reports each change after. |
| [`events.subscribe`](#eventssubscribe) | none | panel, editor | no | 1.2 | Delivers a workspace or agent event to this panel as `orca.on(event)`; needs the event's read capability. The frame APIs call it for you. |
| [`events.subscribe`](#eventssubscribe) | none | panel, editor | no | 1.2 | Delivers a workspace, agent or editor event to this panel as `orca.on(event)`; needs the event's read capability, when it has one. The frame APIs call it for you. |
| [`statusBar.update`](#statusbarupdate) | none | worker | no | 1.2 | Changes one of the plugin's `contributes.statusBarItems` (named by `id`): fields given replace their current value, `visible: false` hides it. Desktop app only; lasts until Orca restarts, and is hidden while the plugin is disabled. A plugin with `main` and status bar items has its worker started when it is enabled and kept running while it stays enabled, so a worker can poll and update its items without an event or a hook. |
| [`appearance.read`](#appearanceread) | `appearance:write` | worker | no | 1.4 | Reads the current desktop appearance revision for a conditional update. |
| [`appearance.apply`](#appearanceapply) | `appearance:write` | worker | yes | 1.4 | Publishes owned app/editor and terminal colors and selects them in the desktop. Returns publication, not renderer acknowledgement. |
@@ -41,6 +41,7 @@ Methods whose capability is `none` need no capability but still need the plugin
| [`agent.launch`](#agentlaunch) | `agent:launch` | worker | yes | 1.3 | Starts an agent's CLI in a new terminal of a workspace, in the background, and returns as soon as it starts; a `prompt` is sent once the agent is ready. |
| [`editor.open`](#editoropen) | `workspace:write` | worker | yes | 1.3 | Opens a file of a workspace in an editor tab of Orca's window; desktop app only. `workspace.open` shows the workspace. |
| [`commands.run`](#commandsrun) | `workspace:write` | worker | yes | 1.3 | Runs one of Orca's built-in actions in its window, never a plugin's replacement of it; desktop app only. |
| [`editor.readTypography`](#editorreadtypography) | none | worker, panel, editor | no | 1.6 | The font family, size, weight, line height, ligatures, word wrap and minimap Orca's file editor uses now, editor zoom included. `editor.typography.changed` reports each change after. |
### `workspace.readContext`
@@ -180,10 +181,10 @@ The agents in the focused workspace: their terminal, title, agent kind and statu
### `events.subscribe`
Delivers a workspace or agent event to this panel as `orca.on(event)`; needs the event's read capability. The frame APIs call it for you.
Delivers a workspace, agent or editor event to this panel as `orca.on(event)`; needs the event's read capability, when it has one. The frame APIs call it for you.
```ts
(params: { event: "workspace.created" | "workspace.removed" | "agent.status.changed" | "workspace.changed" }) => { ok: true }
(params: { event: "workspace.created" | "workspace.removed" | "agent.status.changed" | "workspace.changed" | "editor.typography.changed" }) => { ok: true }
```
### `statusBar.update`
@@ -274,9 +275,17 @@ Runs one of Orca's built-in actions in its window, never a plugin's replacement
(params: { action: "worktree.history.back" | "worktree.history.forward" | "sidebar.left.toggle" | "sidebar.sleepingWorkspaces.toggle" | "floatingWorkspace.maximize" | "tab.rename" | "workspace.rename" | "workspace.openBoard" | "view.tasks" | "sidebar.right.toggle" | "sidebar.explorer.toggle" | "sidebar.search.toggle" | "sidebar.sourceControl.toggle" | "sidebar.checks.toggle" | "sidebar.ports.toggle" }) => { ran: true }
```
### `editor.readTypography`
The font family, size, weight, line height, ligatures, word wrap and minimap Orca's file editor uses now, editor zoom included. `editor.typography.changed` reports each change after.
```ts
(params?: {}) => { fontFamily: string; cssFontFamily: string; fontSize: number; fontWeight: string; lineHeight: number; fontLigatures: boolean; wordWrap: "on" | "off"; minimap: boolean }
```
## Events
A worker receives the events its manifest lists in `contributes.events` through `orca.events.on(event, handler)`, which returns `{ dispose() }`. Each needs `main`, the `trusted-worker` profile and its domain's capability (`workspace:read` or `agent:read`). `orca plugin types` writes the payloads as `PluginEvents` in `orca/host`.
A worker receives the events its manifest lists in `contributes.events` through `orca.events.on(event, handler)`, which returns `{ dispose() }`. Each needs `main`, the `trusted-worker` profile and its domain's capability (`workspace:read` or `agent:read`; editor events need none). `orca plugin types` writes the payloads as `PluginEvents` in `orca/host`.
| Event | Payload | Summary |
| --- | --- | --- |
@@ -284,6 +293,7 @@ A worker receives the events its manifest lists in `contributes.events` through
| `workspace.removed` | `{ workspaceId: string; kind: "worktree" \| "folder"; path: string }` | A worktree workspace was removed. |
| `agent.status.changed` | `{ workspaceId: string \| null; paneKey: string; terminalId?: string; title?: string; state: "working" \| "blocked" \| "waiting" \| "done"; receivedAt: number; mainAgent?: { state: "working" \| "blocked" \| "waiting" \| "done"; outcome?: "success" \| "failure" \| "cancellation" \| "superseded" \| "interruption" \| "unconfirmed"; stateStartedAt: number } }` | An agent in a terminal pane changed status; `workspaceId` is null when Orca cannot tie the pane to a workspace, and `terminalId` names its terminal when Orca lists it. |
| `workspace.changed` | `{ context: { branch: string; displayName: string; terminals: { id: string; title?: string }[] } \| null }` | The focused workspace changed, or its terminals were opened, closed or renamed; `context` is what `workspace.readContext` now answers. Each new subscriber first gets the current context. |
| `editor.typography.changed` | `{ fontFamily: string; cssFontFamily: string; fontSize: number; fontWeight: string; lineHeight: number; fontLigatures: boolean; wordWrap: "on" \| "off"; minimap: boolean }` | What Orca's file editor uses changed (a setting or the editor zoom); the payload is what `editor.readTypography` now answers. Needs no capability. |
## Hooks
+3 -3
View File
@@ -32,7 +32,7 @@ Add `"$schema": "https://raw.githubusercontent.com/stablyai/orca/main/docs/schem
| `contributes` | object | no | 1.4.160 | What the plugin adds to Orca. Every point defaults to empty. |
| `contributes.panels` | [PanelContribution](#panelcontribution)[] | no | 1.4.160 | Sidebar panels. At most 64. |
| `contributes.commands` | [CommandContribution](#commandcontribution)[] | no | 1.4.160 | Command palette entries. At most 256. |
| `contributes.events` | [EventContribution](#eventcontribution)[] | no | 1.4.160 | Events delivered to the plugin worker. At most 4. |
| `contributes.events` | [EventContribution](#eventcontribution)[] | no | 1.4.160 | Events delivered to the plugin worker. At most 5. |
| `contributes.hooks` | [HookContribution](#hookcontribution)[] | no | 1.4.248 | Hooks the worker runs before or after Orca acts; Orca waits only on plugins that list one. At most 8. |
| `contributes.languagePacks` | [LanguagePackContribution](#languagepackcontribution)[] | no | 1.4.160 | Interface translations. At most 16. |
| `contributes.keybindings` | [KeybindingContribution](#keybindingcontribution)[] | no | 1.4.160 | Keyboard shortcuts for contributed commands. At most 256. |
@@ -85,11 +85,11 @@ A command palette entry. Requires the `declarative` execution profile or higher.
## EventContribution
An event subscription. Requires `main`, the `trusted-worker` execution profile and the capability of the event's domain: `workspace:read` for workspace events, `agent:read` for agent events.
An event subscription. Requires `main`, the `trusted-worker` execution profile and the capability of the event's domain: `workspace:read` for workspace events, `agent:read` for agent events. Editor events need no capability.
| Field | Type | Required | Since | Description |
| --- | --- | --- | --- | --- |
| `on` | `"workspace.created"` \| `"workspace.removed"` \| `"agent.status.changed"` \| `"workspace.changed"` | yes | 1.4.160 | Event delivered to the plugin worker. |
| `on` | `"workspace.created"` \| `"workspace.removed"` \| `"agent.status.changed"` \| `"workspace.changed"` \| `"editor.typography.changed"` | yes | 1.4.160 | Event delivered to the plugin worker. |
## HookContribution
@@ -208,9 +208,9 @@
},
"events": {
"default": [],
"description": "Events delivered to the plugin worker. At most 4.",
"description": "Events delivered to the plugin worker. At most 5.",
"x-orca-since": "1.4.160",
"maxItems": 4,
"maxItems": 5,
"type": "array",
"items": {
"$ref": "#/$defs/EventContribution"
@@ -736,7 +736,8 @@
"workspace.created",
"workspace.removed",
"agent.status.changed",
"workspace.changed"
"workspace.changed",
"editor.typography.changed"
],
"description": "Event delivered to the plugin worker.",
"x-orca-since": "1.4.160",
@@ -744,7 +745,8 @@
"workspace.created": "1.4.245",
"workspace.removed": "1.4.245",
"agent.status.changed": "1.4.160",
"workspace.changed": "1.4.246"
"workspace.changed": "1.4.246",
"editor.typography.changed": "1.4.256"
}
}
},
@@ -752,7 +754,7 @@
"on"
],
"additionalProperties": false,
"description": "An event subscription. Requires `main`, the `trusted-worker` execution profile and the capability of the event's domain: `workspace:read` for workspace events, `agent:read` for agent events."
"description": "An event subscription. Requires `main`, the `trusted-worker` execution profile and the capability of the event's domain: `workspace:read` for workspace events, `agent:read` for agent events. Editor events need no capability."
},
"HookContribution": {
"type": "object",
+1 -1
View File
@@ -27,7 +27,7 @@ import {
} from '../src/notifications/push-receive'
import { startPushTokenSync } from '../src/notifications/push-registration'
import { ensureDesktopNotificationChannel } from '../src/notifications/desktop-notification-channel'
import { loadHostCatalog } from '../src/transport/hosts/host-store'
import { loadHostCatalog } from '../src/host-catalog/host-store'
import { usePairingLinks } from '../src/navigation/use-pairing-links'
import { consumeInitialLaunchIntent } from '../src/navigation/initial-launch-intent'
import { recoverMobileRelayPairing } from '../src/pairing/mobile-relay-pairing-recovery'
+2 -2
View File
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from
import { View, Text, Pressable } from 'react-native'
import { useLocalSearchParams, useRouter } from 'expo-router'
import * as Clipboard from 'expo-clipboard'
import { loadHosts } from '../src/transport/hosts/host-store'
import { loadHosts } from '../src/host-catalog/host-store'
import { connectionLogStore } from '../src/diagnostics/connection/persisted-connection-log-store'
import { useRpcClientContext } from '../src/transport/connection/client-context'
import { useHostClient } from '../src/transport/connection/host-client-hooks'
@@ -22,7 +22,7 @@ import { connectionDiagnosticsScreenStyles as styles } from '../src/diagnostics/
import { useMobileColorScheme } from '../src/theme/use-mobile-color-scheme'
import { NATIVE_APP_LOG_ID } from '../src/diagnostics/native-render-log'
import type { ConnectionLogEntry } from '../src/diagnostics/connection/connection-log-types'
import type { HostProfile } from '../src/transport/hosts/host-profile'
import type { HostProfile } from '../src/host-catalog/host-profile'
import { pressedStyle } from '../src/theme/mobile-theme'
// Keep empty snapshot identity stable or useSyncExternalStore rerenders forever.
+4 -4
View File
@@ -18,7 +18,7 @@ import { NavBar } from '../../../src/components/nav-bar/NavBar'
import { NavBarButton } from '../../../src/components/nav-bar/NavBarButton'
import { themedStyleSheet } from '../../../src/theme/themed-styles'
import { useMobileColorScheme } from '../../../src/theme/use-mobile-color-scheme'
import { loadHosts, updateHostNameAndEndpoint } from '../../../src/transport/hosts/host-store'
import { loadHosts, updateHostNameAndEndpoint } from '../../../src/host-catalog/host-store'
import { displayHostEndpoint } from '../../../src/transport/hosts/host-endpoint'
import { resolveHostEndpointEdit } from '../../../src/transport/hosts/host-endpoint-edit'
import {
@@ -26,12 +26,12 @@ import {
usePrimeHosts,
useRefreshHostClient
} from '../../../src/transport/connection/host-client-hooks'
import { removeHostAndCloseClient } from '../../../src/transport/hosts/host-removal-lifecycle'
import { useHostDisplay } from '../../../src/transport/hosts/use-host-display'
import { removeHostAndCloseClient } from '../../../src/host-catalog/host-removal-lifecycle'
import { useHostDisplay } from '../../../src/host-descriptors/use-host-display'
import { leaveHostRoute } from '../../../src/host-route-exit'
import { ConfirmModal } from '../../../src/components/ConfirmModal'
import { ListGroup, ListRow } from '../../../src/components/list/ListGroup'
import type { HostProfile } from '../../../src/transport/hosts/host-profile'
import type { HostProfile } from '../../../src/host-catalog/host-profile'
import { hostOs } from '../../../src/platform/host-os'
import { translate } from '../../../src/i18n/mobile-i18n'
import { useMobileTranslation } from '../../../src/i18n/use-mobile-translation'
+2 -2
View File
@@ -12,8 +12,8 @@ import { colors, spacing } from '../src/theme/mobile-theme'
import { useMobileColorScheme } from '../src/theme/use-mobile-color-scheme'
import type { Translate } from '../src/i18n/mobile-i18n'
import { useMobileTranslation } from '../src/i18n/use-mobile-translation'
import { loadHosts, peekHosts } from '../src/transport/hosts/host-store'
import type { HostProfile } from '../src/transport/hosts/host-profile'
import { loadHosts, peekHosts } from '../src/host-catalog/host-store'
import type { HostProfile } from '../src/host-catalog/host-profile'
import { useFocusedSettingsHostClients } from '../src/settings/settings-host-client-connections'
import type { RpcClient } from '../src/transport/rpc/rpc-client-contract'
import { LIST_LEADING_INSET, ListRow } from '../src/components/list/ListGroup'
+2 -2
View File
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from 'react'
import { useRouter } from 'expo-router'
import { loadHosts, peekHosts } from '../src/transport/hosts/host-store'
import type { HostProfile } from '../src/transport/hosts/host-profile'
import { loadHosts, peekHosts } from '../src/host-catalog/host-store'
import type { HostProfile } from '../src/host-catalog/host-profile'
import { useFocusedSettingsHostClients } from '../src/settings/settings-host-client-connections'
import VoiceSettingsScreen from '../src/settings/voice-settings-screen'
import { nativeVoiceSettingsOperations } from '../src/settings/native-voice-settings-operations'
+37 -25
View File
@@ -16601,7 +16601,7 @@
"id": "notifications-push-registered",
"operation": "notifications.push-registration",
"family": "notifications.push-registration",
"sites": ["mobile/src/notifications/push-registration.ts"],
"sites": ["mobile/src/notifications/mobile-push-registration-operations.ts"],
"schedules": [],
"steps": [
{
@@ -16650,7 +16650,7 @@
"id": "notifications-push-gateway-rejected",
"operation": "notifications.push-registration",
"family": "notifications.push-registration",
"sites": ["mobile/src/notifications/push-registration.ts"],
"sites": ["mobile/src/notifications/mobile-push-registration-operations.ts"],
"schedules": [],
"steps": [
{
@@ -18029,7 +18029,7 @@
"id": "native-chat-write-accepted",
"operation": "nativeChat.terminal-write",
"family": "nativeChat.terminal-write",
"sites": ["mobile/src/session/native-chat/mobile-native-chat-send.ts"],
"sites": ["mobile/src/session/native-chat/composer/mobile-native-chat-send.ts"],
"schedules": [],
"steps": [
{
@@ -18065,7 +18065,7 @@
"id": "native-chat-write-rejected",
"operation": "nativeChat.terminal-write",
"family": "nativeChat.terminal-write",
"sites": ["mobile/src/session/native-chat/mobile-native-chat-send.ts"],
"sites": ["mobile/src/session/native-chat/composer/mobile-native-chat-send.ts"],
"schedules": [],
"steps": [
{
@@ -18101,7 +18101,7 @@
"id": "native-chat-write-delivery-unknown",
"operation": "nativeChat.terminal-write",
"family": "nativeChat.terminal-write",
"sites": ["mobile/src/session/native-chat/mobile-native-chat-send.ts"],
"sites": ["mobile/src/session/native-chat/composer/mobile-native-chat-send.ts"],
"schedules": [],
"steps": [
{
@@ -18133,7 +18133,7 @@
"id": "native-chat-write-clear-line",
"operation": "nativeChat.terminal-write",
"family": "nativeChat.terminal-write",
"sites": ["mobile/src/session/native-chat/mobile-native-chat-send.ts"],
"sites": ["mobile/src/session/native-chat/composer/mobile-native-chat-send.ts"],
"schedules": [],
"steps": [
{
@@ -18169,7 +18169,7 @@
"id": "native-chat-write-typed-command",
"operation": "nativeChat.terminal-write",
"family": "nativeChat.terminal-write",
"sites": ["mobile/src/session/native-chat/mobile-native-chat-send.ts"],
"sites": ["mobile/src/session/native-chat/composer/mobile-native-chat-send.ts"],
"schedules": [],
"steps": [
{
@@ -18840,7 +18840,9 @@
"id": "structured-launch-unsupported",
"operation": "agentSession.structured-launch",
"family": "agentSession.structured-launch",
"sites": ["mobile/src/session/mobile-structured-agent-session-launch.ts"],
"sites": [
"mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-launch.ts"
],
"schedules": [],
"steps": [
{
@@ -18870,7 +18872,9 @@
"id": "structured-launch-support-refused",
"operation": "agentSession.structured-launch",
"family": "agentSession.structured-launch",
"sites": ["mobile/src/session/mobile-structured-agent-session-launch.ts"],
"sites": [
"mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-launch.ts"
],
"schedules": [],
"steps": [
{
@@ -18900,7 +18904,9 @@
"id": "structured-launch-created",
"operation": "agentSession.structured-launch",
"family": "agentSession.structured-launch",
"sites": ["mobile/src/session/mobile-structured-agent-session-launch.ts"],
"sites": [
"mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-launch.ts"
],
"schedules": [],
"steps": [
{
@@ -18951,7 +18957,9 @@
"id": "structured-launch-definitive-refusal",
"operation": "agentSession.structured-launch",
"family": "agentSession.structured-launch",
"sites": ["mobile/src/session/mobile-structured-agent-session-launch.ts"],
"sites": [
"mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-launch.ts"
],
"schedules": [],
"steps": [
{
@@ -19003,7 +19011,9 @@
"id": "structured-launch-replays-dropped-create",
"operation": "agentSession.structured-launch",
"family": "agentSession.structured-launch",
"sites": ["mobile/src/session/mobile-structured-agent-session-launch.ts"],
"sites": [
"mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-launch.ts"
],
"schedules": [],
"steps": [
{
@@ -19443,7 +19453,7 @@
"id": "native-chat-stop-accepted",
"operation": "session.native-chat-stop",
"family": "session.native-chat-stop",
"sites": ["mobile/src/session/native-chat/use-mobile-native-chat-stop.ts"],
"sites": ["mobile/src/session/native-chat/composer/use-mobile-native-chat-stop.ts"],
"schedules": [],
"steps": [
{
@@ -19504,7 +19514,7 @@
"id": "native-chat-stop-both-rejected",
"operation": "session.native-chat-stop",
"family": "session.native-chat-stop",
"sites": ["mobile/src/session/native-chat/use-mobile-native-chat-stop.ts"],
"sites": ["mobile/src/session/native-chat/composer/use-mobile-native-chat-stop.ts"],
"schedules": [],
"steps": [
{
@@ -19562,7 +19572,7 @@
"id": "native-chat-stop-delivery-unknown",
"operation": "session.native-chat-stop",
"family": "session.native-chat-stop",
"sites": ["mobile/src/session/native-chat/use-mobile-native-chat-stop.ts"],
"sites": ["mobile/src/session/native-chat/composer/use-mobile-native-chat-stop.ts"],
"schedules": [],
"steps": [
{
@@ -19962,7 +19972,7 @@
"mobile/src/source-control/review/use-mobile-diff-review-comment-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-git-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-send-actions.ts",
"mobile/src/session/native-chat/mobile-native-chat-stale-input.ts"
"mobile/src/session/native-chat/composer/mobile-native-chat-stale-input.ts"
],
"schedules": [],
"steps": [
@@ -20039,7 +20049,7 @@
"mobile/src/source-control/review/use-mobile-diff-review-comment-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-git-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-send-actions.ts",
"mobile/src/session/native-chat/mobile-native-chat-stale-input.ts"
"mobile/src/session/native-chat/composer/mobile-native-chat-stale-input.ts"
],
"schedules": [],
"steps": [
@@ -20117,7 +20127,7 @@
"mobile/src/source-control/review/use-mobile-diff-review-comment-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-git-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-send-actions.ts",
"mobile/src/session/native-chat/mobile-native-chat-stale-input.ts"
"mobile/src/session/native-chat/composer/mobile-native-chat-stale-input.ts"
],
"schedules": [],
"steps": [
@@ -20152,7 +20162,7 @@
"mobile/src/source-control/review/use-mobile-diff-review-comment-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-git-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-send-actions.ts",
"mobile/src/session/native-chat/mobile-native-chat-stale-input.ts"
"mobile/src/session/native-chat/composer/mobile-native-chat-stale-input.ts"
],
"schedules": [],
"steps": [
@@ -20188,7 +20198,7 @@
"mobile/src/source-control/review/use-mobile-diff-review-comment-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-git-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-send-actions.ts",
"mobile/src/session/native-chat/mobile-native-chat-stale-input.ts"
"mobile/src/session/native-chat/composer/mobile-native-chat-stale-input.ts"
],
"schedules": [],
"steps": [
@@ -20224,7 +20234,7 @@
"mobile/src/source-control/review/use-mobile-diff-review-comment-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-git-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-send-actions.ts",
"mobile/src/session/native-chat/mobile-native-chat-stale-input.ts"
"mobile/src/session/native-chat/composer/mobile-native-chat-stale-input.ts"
],
"schedules": [],
"steps": [
@@ -20280,7 +20290,7 @@
"mobile/src/source-control/review/use-mobile-diff-review-comment-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-git-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-send-actions.ts",
"mobile/src/session/native-chat/mobile-native-chat-stale-input.ts"
"mobile/src/session/native-chat/composer/mobile-native-chat-stale-input.ts"
],
"schedules": [],
"steps": [
@@ -24889,7 +24899,7 @@
"mobile/src/source-control/review/use-mobile-diff-review-comment-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-git-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-send-actions.ts",
"mobile/src/session/native-chat/mobile-native-chat-stale-input.ts"
"mobile/src/session/native-chat/composer/mobile-native-chat-stale-input.ts"
],
"schedules": [],
"steps": [
@@ -24944,7 +24954,7 @@
"mobile/src/source-control/review/use-mobile-diff-review-comment-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-git-actions.ts",
"mobile/src/source-control/review/use-mobile-diff-review-send-actions.ts",
"mobile/src/session/native-chat/mobile-native-chat-stale-input.ts"
"mobile/src/session/native-chat/composer/mobile-native-chat-stale-input.ts"
],
"schedules": [],
"steps": [
@@ -25018,7 +25028,9 @@
"id": "structured-agent-session-created",
"operation": "agentSession.structured-launch",
"family": "agentSession.structured-create",
"sites": ["mobile/src/session/mobile-structured-agent-session-launch.ts"],
"sites": [
"mobile/src/session/native-chat/agent-session/mobile-structured-agent-session-launch.ts"
],
"schedules": [],
"steps": [
{
+1 -1
View File
@@ -3,7 +3,7 @@ import { View, Text, ScrollView, ActivityIndicator } from 'react-native'
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
import { useFocusEffect, useLocalSearchParams } from 'expo-router'
import { ChevronLeft, RefreshCw, User } from 'lucide-react-native'
import { loadHosts, peekHosts } from '../transport/hosts/host-store'
import { loadHosts, peekHosts } from '../host-catalog/host-store'
import { useForceReconnect, useHostClient } from '../transport/connection/host-client-hooks'
import { useHostConnectionInputs } from '../transport/connection/client-context-connection-metrics'
import { classifyConnection, isConnectionOutage } from '../transport/connection/connection-health'
@@ -1,5 +1,5 @@
import { openMobileAgentHistoryChat } from './open-mobile-agent-history-chat'
import { mobileStructuredSessionOpenMessage } from '../session/mobile-structured-session-open-message'
import { mobileStructuredSessionOpenMessage } from '../session/native-chat/agent-session/mobile-structured-session-open-message'
import { prepareMobileHistoryResumeTarget } from './mobile-history-resume-target-preparation'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { View } from 'react-native'
@@ -16,7 +16,7 @@ import type { TuiAgent } from '../../../src/shared/agent-launch/catalog/tui-agen
import { parseWslUncPath } from '../../../src/shared/wsl-paths'
import { resolveWindowsShellStartupFamily } from '../../../src/shared/windows-terminal-shell'
import { RESUME_RPC_TIMEOUT_MS } from './ai-vault-resume-preparation'
import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender'
import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port'
import { interpretOrThrowRefusalMessage } from '../transport/rpc/rpc-refusal-message'
import {
reviewTerminalCreateRun,
@@ -78,7 +78,7 @@ export function buildMobileAiVaultResumeLaunch(args: {
}
export async function resumeAiVaultSessionInTerminal(
client: RpcOperationSender,
client: UnvalidatedRpcRequestPort,
worktreeId: string,
launch: MobileAiVaultResumeLaunch & {
clientMutationId?: string
@@ -6,7 +6,7 @@ import {
} from '../../../src/shared/ai-vault/resume/ai-vault-resume-preparation'
import { LOCAL_EXECUTION_HOST_ID } from '../../../src/shared/execution-host'
import { aiVaultResumePreparationRun } from './mobile-ai-vault-resume-operations'
import type { RpcOperationSender } from '../transport/rpc/rpc-operation-sender'
import type { UnvalidatedRpcRequestPort } from '../transport/rpc/unvalidated-rpc-request-port'
import { translate } from '../i18n/mobile-i18n'
// Why: without an explicit timeout, a socket drop mid-resume parks the request
@@ -14,7 +14,7 @@ import { translate } from '../i18n/mobile-i18n'
export const RESUME_RPC_TIMEOUT_MS = 30_000
export async function prepareMobileAiVaultSessionResume(
client: RpcOperationSender,
client: UnvalidatedRpcRequestPort,
session: AiVaultSession
): Promise<AiVaultSession> {
// Why: per-account repinning runs on the serving host, whose account
@@ -4,7 +4,6 @@ import '../files/mobile-file-preview-operations'
import { aiVaultResumePreparationSchema } from './mobile-ai-vault-resume-reply-schema'
import '../browser/mobile-session-browser-reply-schema'
import '../files/session-documents/mobile-file-tap-reply-schema'
import '../transport/rpc/rpc-uninterpreted-reply-schemas'
// Legacy resume reads unsupported-method refusals raw to fall back to shared home.
export const aiVaultResumePreparationRun = bindDeferredRpcOperation(
@@ -4,7 +4,7 @@ import type { RpcClient } from '../transport/rpc/rpc-client-contract'
import {
openMobileStructuredSession,
mobileStructuredSessionHref
} from '../session/open-mobile-structured-session'
} from '../session/native-chat/agent-session/open-mobile-structured-session'
/** Consume native opens and ambiguous failures; only an explicit pre-effect refusal resumes in a terminal. */
export async function openMobileAgentHistoryChat(args: {
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'
import { useQueryClient } from '@tanstack/react-query'
import { mobileStructuredSessionStatusFeed } from '../session/mobile-structured-session-status-feed'
import { mobileStructuredSessionStatusFeed } from '../session/native-chat/agent-session/mobile-structured-session-status-feed'
import type { AgentSessionConversationNames } from '../../../src/shared/agent-session/agent-session-conversation-names'
import { useForceReconnect } from '../transport/connection/host-client-hooks'
import { useWorkspaceClient } from '../worktree/use-workspace-client'
+2 -2
View File
@@ -12,8 +12,8 @@ import { triggerMediumImpact } from '../platform/haptics'
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/hosts/host-profile'
import { useHostDisplay } from '../transport/hosts/use-host-display'
import type { HostProfile } from '../host-catalog/host-profile'
import { useHostDisplay } from '../host-descriptors/use-host-display'
import { agentStateLabel, formatTimeAgo, type AgentDotState } from '../worktree/agent-row-display'
import { isDisclosureOpen, toggleDisclosure } from '../worktree/agent-child-disclosure'
import { areStructurallyEqual } from '../worktree/structural-equality'
+17 -17
View File
@@ -4,6 +4,7 @@ import type { ConnectionState } from '../transport/connection/connection-state'
import type { HostWorktreeInfo } from '../worktree/home-worktree-info'
import { buildAgentList, type AgentListSection } from './agent-list-model'
import {
selectActiveAgents,
selectAgentsNeedingYou,
type AgentListInput,
type AgentListRow,
@@ -79,24 +80,22 @@ const statusView = (showWorkers: boolean, unreadOnly = false) => ({
describe('buildAgentList by status', () => {
it('buckets agents by what they ask of the user, newest first, and folds idle ones', () => {
const list = buildAgentList(
input({
mac: {
state: 'connected',
info: {
agents: entries([
agent('asking', 'waiting'),
agent('older-run', 'working', { stateStartedAt: NOW - 5 * MINUTE }),
agent('newer-run', 'working', { stateStartedAt: NOW - 2 * MINUTE }),
agent('finished', 'done'),
// Silent past the 30-minute freshness window.
agent('quiet', 'working', { updatedAt: NOW - 31 * MINUTE })
])
}
const host = input({
mac: {
state: 'connected',
info: {
agents: entries([
agent('asking', 'waiting'),
agent('older-run', 'working', { stateStartedAt: NOW - 5 * MINUTE }),
agent('newer-run', 'working', { stateStartedAt: NOW - 2 * MINUTE }),
agent('finished', 'done'),
// Silent past the 30-minute freshness window.
agent('quiet', 'working', { updatedAt: NOW - 31 * MINUTE })
])
}
}),
statusView(true)
)
}
})
const list = buildAgentList(host, statusView(true))
expect(shape(list.sections)).toEqual([
{ kind: 'needs-you', rows: ['asking'], workers: [[]], idle: undefined },
{
@@ -107,6 +106,7 @@ describe('buildAgentList by status', () => {
},
{ kind: 'done', rows: ['finished'], workers: [[]], idle: ['quiet'] }
])
expect(panes(selectActiveAgents(host))).toEqual(['asking', 'newer-run', 'older-run'])
})
it('nests workers under their coordinator and still lists the one that needs the user', () => {
+9 -2
View File
@@ -21,7 +21,14 @@ function oneLine(text: string | null | undefined): string {
return text?.replace(/\s+/g, ' ').trim() ?? ''
}
function lastMessage(agent: RuntimeWorktreeAgentRow): string {
// A worktree agent row or a session tab's AgentStatusEntry, which leaves these optional.
type AgentPreviewSource = Pick<RuntimeWorktreeAgentRow, 'prompt'> & {
lastAssistantMessage?: string | null
toolName?: string | null
toolInput?: string | null
}
function lastMessage(agent: AgentPreviewSource): string {
const message = agent.lastAssistantMessage
return message ? oneLine(withoutNativeChatVisualDirectiveLines(message)) : ''
}
@@ -31,7 +38,7 @@ function lastMessage(agent: RuntimeWorktreeAgentRow): string {
* (`group` null) shows no tool, since the tool call is long over.
*/
export function agentListPreview(
agent: RuntimeWorktreeAgentRow,
agent: AgentPreviewSource,
group: AgentStatusGroup | null,
title: string
): string {
+12
View File
@@ -159,6 +159,18 @@ export function selectAgentsNeedingYou(input: AgentListInput): AgentListRow[] {
.sort(byStatusThenRecent)
}
/** Live agents that need the user or are working, workers included: Feed's Active rows. */
export function selectActiveAgents(input: AgentListInput): AgentListRow[] {
return input.hosts
.flatMap((host) => everyRow(hostAgents(input, host.id).roots))
.filter((row) => {
const group = agentStatusGroup(row.dotState)
return !row.stale && (group === 'needs-you' || group === 'working')
})
.map(asTopLevel)
.sort(byStatusThenRecent)
}
export function countAgentsNeedingYouByHost(
rows: readonly AgentListRow[]
): Readonly<Record<string, number>> {
@@ -6,7 +6,7 @@ import { TransientToast, useTransientToast } from '../components/TransientToast'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { useClipboardWriter } from '../platform/clipboard'
import { useOpenMobileSession } from '../session/use-open-mobile-session'
import type { HostProfile } from '../transport/hosts/host-profile'
import type { HostProfile } from '../host-catalog/host-profile'
import type { AgentActivityChange, AgentListRow } from './agent-list-rows'
import type { AgentListRowHandlers } from './MobileAgentListRows'
import type { AgentActivityFailure } from './use-agent-activity-writes'
@@ -9,8 +9,8 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { firstParam } from '../navigation/route-param-reader'
import { useRouteHandoff } from '../navigation/route-handoff'
import { useHostClient } from '../transport/connection/host-client-hooks'
import { useHostDisplay } from '../transport/hosts/use-host-display'
import { useHostProfile } from '../transport/hosts/use-host-profile'
import { useHostDisplay } from '../host-descriptors/use-host-display'
import { useHostProfile } from '../host-catalog/use-host-profile'
import { useHostProtocolGates } from '../components/host-protocol-gates-context'
import { NavBar, NavBarTitleButton } from '../components/nav-bar/NavBar'
import { NavBarButton } from '../components/nav-bar/NavBarButton'
@@ -12,7 +12,8 @@ import type { ExecutionHostId } from '../../../src/shared/execution-host'
import type { RpcClient } from '../transport/rpc/rpc-client-contract'
import { hostStatusProbe } from '../transport/connection/host-status-probe-operations'
import { hostConnectionEpoch, readHostStatusReply } from '../query/host-status-query'
import { defineHostQuery, useHostEventRouter, useHostQuery } from '../query/host-query'
import { defineHostQuery, useHostQuery } from '../query/host-query'
import { useHostEventRouter } from '../query/host-event-router'
import { hostQueryKey, type HostQueryTarget } from '../query/host-query-key'
import { isHostNotConnectedError } from '../query/host-query-errors'
import { readViewBase } from '../query/host-read-client'
@@ -4,7 +4,6 @@ import '../files/mobile-file-preview-operations'
import { browserTabCreatedSchema } from './mobile-session-browser-reply-schema'
import '../agent-history/mobile-ai-vault-resume-reply-schema'
import '../files/session-documents/mobile-file-tap-reply-schema'
import '../transport/rpc/rpc-uninterpreted-reply-schemas'
export const sessionBrowserTabCreate = bindDeferredRpcOperation(
defineRpcOperation({
+2 -80
View File
@@ -1,13 +1,6 @@
import { probe, publishLayout, mounted, positions } from './drag-reorder-test-harness.test-fixture'
import { createElement } from 'react'
import { act, create } from 'react-test-renderer'
import Animated, { useAnimatedRef, useSharedValue } from 'react-native-reanimated'
import { probe, mounted, positions } from './drag-reorder-test-harness.test-fixture'
import { act } from 'react-test-renderer'
import { describe, expect, it, vi } from 'vitest'
import { DragReorderList } from './DragReorderList'
import { GripVertical } from 'lucide-react-native'
import { queuedMessageStyles } from '../session/native-chat/input/mobile-native-chat-queued-message-styles'
import { spacing } from '../theme/mobile-theme'
describe('drag completion and cancellation', () => {
it('restores original positions and writes nothing when an active drag is cancelled', async () => {
@@ -204,74 +197,3 @@ describe('late gesture callbacks', () => {
expect(fixture.onReorder).not.toHaveBeenCalled()
})
})
it('stops scrolling at the viewport edge when rows are replaced', async () => {
const fixture = await mounted()
probe.viewport = { pageY: 0, height: 100 }
await act(async () => {
fixture.gesture.start!({ absoluteY: 90, translationY: 0 }, true)
probe.frame!({ timeSincePreviousFrame: 16 })
})
expect(probe.scroll).toHaveBeenCalledTimes(1)
await fixture.updateItems(['b', 'c'])
await act(async () => probe.frame!({ timeSincePreviousFrame: 16 }))
expect(probe.scroll).toHaveBeenCalledTimes(1)
expect(probe.frameActive).toHaveBeenLastCalledWith(false)
await act(async () => fixture.root.unmount())
})
it('keeps measured rows still on hold, restores cancellation, and drops by moved ID plus neighbor', async () => {
const move = vi.fn()
function Harness() {
return createElement(DragReorderList<string>, {
items: ['a', 'b', 'c'],
itemKey: (item) => item,
rowPresentation: 'plain',
renderRow: (item, handle) => createElement('Text', null, item, handle),
onMove: move,
scrollRef: useAnimatedRef<Animated.ScrollView>(),
scrollOffsetY: useSharedValue(0),
scrollContentHeight: useSharedValue(140)
})
}
let root: ReturnType<typeof create>
await act(async () => {
root = create(createElement(Harness))
})
const handle = root!.root.findAllByType(Animated.View).find((node) => node.props.hitSlop)!
const handleStyle = Object.assign({}, ...handle.props.style)
const gripWidth = handle.findByType(GripVertical).props.size
const width = gripWidth + 2 * handleStyle.paddingHorizontal
const left = queuedMessageStyles.row.paddingLeft - handle.props.hitSlop.left
const right = queuedMessageStyles.row.paddingLeft + width + handle.props.hitSlop.right
expect(handleStyle.paddingHorizontal).toBe(spacing.xs)
expect([left, right]).toEqual([4, 62])
expect(right - Math.max(0, left)).toBe(58)
const rows = root!.root.findAll(
(node) => node.type === Animated.View && typeof node.props.onLayout === 'function'
)
await act(async () =>
rows.forEach((row, index) =>
row.props.onLayout({ nativeEvent: { layout: { height: index === 0 ? 100 : 20 } } })
)
)
await publishLayout()
const gesture = probe.gestures.at(-3)!
const at = (translationY: number) => ({ absoluteY: 100 + translationY, translationY })
await act(async () => {
gesture.start!(at(0), true)
gesture.update!(at(0), true)
gesture.finalize!(at(0), true)
gesture.start!(at(0), true)
gesture.update!(at(35), true)
gesture.finalize!(at(35), false)
})
expect(move).not.toHaveBeenCalled()
await act(async () => {
gesture.start!(at(0), true)
gesture.update!(at(35), true)
gesture.finalize!(at(35), true)
})
expect(move).toHaveBeenCalledExactlyOnceWith({ movedId: 'a', afterId: 'c' })
await act(async () => root!.unmount())
})
@@ -62,9 +62,9 @@ vi.mock('../host-screen/host-list-queries', () => ({
(hostId && cachedRepos.get(hostId)) || null
}))
import '../transport/hosts/host-descriptor-store'
import '../host-descriptors/host-descriptor-store'
import { FakeSession } from '../transport/connection/mobile-endpoint-supervisor-test-fakes'
import { markRpcDeliveryUnknown } from '../transport/rpc/rpc-delivery-ambiguity'
import '../../../src/shared/execution-host'
import type { ExecutionHostId } from '../../../src/shared/execution-host'
@@ -231,72 +231,6 @@ describe('NewWorktreeModal project targets', () => {
expect(runTargetPicker(renderer).props.selectedId).toBe('runtime:env-1')
})
async function submitFromFolder(createReply: () => Promise<RpcResponse>) {
const serverRepo = { ...repos[0]!, path: '/srv/orca' }
const server = requestsOnly(
vi.fn((method: string) =>
method === 'repo.list'
? replied({ repos: [serverRepo] })
: method === 'worktree.create'
? createReply()
: Promise.reject(new Error(`unexpected ${method}`))
)
)
const settle = vi.fn()
await act(async () => {
renderer = create(
createElement(NewWorktreeModal, {
visible: true,
client: requestsOnly(() => new Promise<never>(() => {})),
serverClients: new Map<ExecutionHostId, RpcClient>([['runtime:env-1', server]]),
hostId: 'host-1',
onCreated: () => {},
onClose: () => {},
openExternalUrl: () => {},
folderLaunch: {
repoId: 'repo-1',
executionHostId: 'runtime:env-1',
name: 'payments',
parentWorkspace: 'folder:f-1',
submit: { begin: () => ({ settle }), presenting: () => true }
}
})
)
})
await flushUpdates()
const form = renderer.root.find((node) => typeof node.props.onCreate === 'function')
await act(async () => form.props.onCreate())
await flushUpdates()
const creates = vi
.mocked(server.sendRequest)
.mock.calls.filter(([method]) => method === 'worktree.create')
return { creates: creates.map(([, params]) => params), settle }
}
it('creates from a folder attached to that folder, on its host', async () => {
const { creates, settle } = await submitFromFolder(() =>
replied({ worktree: { id: 'repo-1::/srv/w/payments', displayName: 'payments' } })
)
expect(creates).toEqual([
expect.objectContaining({
repo: 'id:repo-1',
parentWorkspace: 'folder:f-1',
parentWorkspaceOrigin: 'manual'
})
])
expect(settle).toHaveBeenCalledWith('created')
})
it("doesn't try another name when a folder create's reply is lost", async () => {
const { creates, settle } = await submitFromFolder(() =>
Promise.reject(markRpcDeliveryUnknown(new Error('connection lost')))
)
expect(creates).toHaveLength(1)
expect(settle).toHaveBeenCalledWith('uncertain')
})
it('ignores a stale repo list after the client changes', async () => {
let resolveOldList: ((value: unknown) => void) | undefined
const oldList = new Promise((resolve) => {
+1 -1
View File
@@ -10,7 +10,7 @@ import { deriveRepoSlug, type PasteRepoCandidate } from '../tasks/smart-source-p
import { useMobileComposerSource } from '../tasks/use-mobile-composer-source'
import { useNewWorktreeRuntimeCapabilities } from '../tasks/worktree-create-capability'
import { useWorktreeCreateBasePrefetch } from '../tasks/use-worktree-create-base-prefetch'
import { useHostDescriptor } from '../transport/hosts/host-descriptor-store'
import { useHostDescriptor } from '../host-descriptors/host-descriptor-store'
import { isSameMobileWorkspaceRepo } from '../worktree/new-workspace-dialog-repo-selection'
import {
buildRetiredWorktreeNamesRefreshKey,
+7 -2
View File
@@ -84,6 +84,8 @@ export function SwipeRowActions({
})
// Web has no worklet plugin; mapper hooks need this explicit dependency list.
const rowStyle = useAnimatedStyle(() => ({ transform: [{ translateX: offset.value }] }), [offset])
// Hidden at rest, so the action can't show through a rounded group's antialiased corners.
const actionsStyle = useAnimatedStyle(() => ({ opacity: offset.value === 0 ? 0 : 1 }), [offset])
// A tap on an open row closes it rather than pressing the row.
const guardPress = (onPress: () => void) => () => {
// The click a drag release produces belongs to the drag, not to the row.
@@ -124,8 +126,10 @@ export function SwipeRowActions({
}
return (
<View>
{leading ? renderAction(leading, 'leading') : null}
{trailing ? renderAction(trailing, 'trailing') : null}
<Animated.View pointerEvents="box-none" style={[styles.actions, actionsStyle]}>
{leading ? renderAction(leading, 'leading') : null}
{trailing ? renderAction(trailing, 'trailing') : null}
</Animated.View>
{/* pan-y keeps the page scrolling vertically over the row on the web. */}
<GestureDetector gesture={pan} touchAction="pan-y">
<Animated.View style={rowStyle}>{children(guardPress)}</Animated.View>
@@ -144,6 +148,7 @@ const styles = themedStyleSheet(() => ({
justifyContent: 'center',
gap: 2
},
actions: { position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 },
leading: { left: 0 },
trailing: { right: 0 },
actionPressed: { opacity: feedback.pressedOpacity },
@@ -1,7 +1,7 @@
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/hosts/host-profile'
import type { HostProfile } from '../host-catalog/host-profile'
import { useConnectedHostPicker } from './use-connected-host-picker'
function host(id: string, name: string, endpoint: string): HostProfile {
@@ -1,6 +1,6 @@
import { useRef, useState } from 'react'
import { hostEndpointLabel } from '../transport/hosts/host-endpoint-label'
import type { HostProfile } from '../transport/hosts/host-profile'
import type { HostProfile } from '../host-catalog/host-profile'
/** Picker rows for desktops, telling apart two that share a name and endpoint. */
export function hostPickerOptions(hosts: HostProfile[]) {
@@ -44,31 +44,25 @@ afterEach(async () => {
})
describe('useNewWorkspaceRepositories initial repo', () => {
it.each([
{ order: 'desktop first', desktopFirst: true },
{ order: 'server first', desktopFirst: false }
])(
'picks the named host’s copy of a repo id two hosts share ($order)',
async ({ desktopFirst }) => {
const client = desktopListing(desktopFirst ? [repo()] : [])
let picked: MobileWorkspaceRepo | null = null
mounted = hookMount(() => {
picked = useNewWorkspaceRepositories({
client,
hostId: 'host-1',
visible: true,
initialRepo: { id: 'api', executionHostId: SERVER }
}).selectedRepo
})
mounted.mount()
await settleQueryNotifications()
// The desktop's same-id repo must not stand in while the server's listing is on its way.
expect(picked).toBeNull()
it('picks the named host’s copy of a repo id two hosts share', async () => {
const client = desktopListing([repo()])
let picked: MobileWorkspaceRepo | null = null
mounted = hookMount(() => {
picked = useNewWorkspaceRepositories({
client,
hostId: 'host-1',
visible: true,
initialRepo: { id: 'api', executionHostId: SERVER }
}).selectedRepo
})
mounted.mount()
await settleQueryNotifications()
// The desktop's same-id repo must not stand in while the server's listing is on its way.
expect(picked).toBeNull()
servers.repos = new Map([[SERVER, [repo(SERVER)]]])
act(() => mounted?.update())
await settleQueryNotifications()
expect(picked).toMatchObject({ id: 'api', executionHostId: SERVER })
}
)
servers.repos = new Map([[SERVER, [repo(SERVER)]]])
act(() => mounted?.update())
await settleQueryNotifications()
expect(picked).toMatchObject({ id: 'api', executionHostId: SERVER })
})
})
+2 -2
View File
@@ -7,11 +7,11 @@ import type { AESEncryptionKey } from 'expo-crypto'
import { Directory, Paths } from 'expo-file-system'
import { AppState } from 'react-native'
import { createExpoGenerationFileSystem } from '../mobile-web-shell/generation-store-file-system'
import { loadStoredHostProfiles } from '../transport/hosts/host-metadata-store'
import { loadStoredHostProfiles } from '../host-catalog/host-metadata-store'
import {
readPairingKeychainItem,
writePairingKeychainItem
} from '../transport/hosts/pairing-keychain'
} from '../host-credentials/pairing-keychain'
import {
createRootIndex,
DEVICE_STORE_CAPS,
@@ -5,7 +5,7 @@ import type {
MobileConnectionDiagnosticPath
} from './connection/connection-log-types'
import type { ConnectionState } from '../transport/connection/connection-state'
import { normalizeHostAppVersion } from '../transport/hosts/host-app-version'
import { normalizeHostAppVersion } from '../host-descriptors/host-app-version'
import { formatEndpoint } from './host-reachability'
import { diagnoseConnection } from './connection-diagnostics-analysis'
import { redactConnectionLogEntry, redactConnectionLogText } from './connection-log-redaction'
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'
import { resolveDiagnosticsHostId } from './connection-diagnostics-screen-data'
import type { HostProfile } from '../transport/hosts/host-profile'
import type { HostProfile } from '../host-catalog/host-profile'
const host = (id: string): HostProfile => ({
id,
@@ -1,6 +1,6 @@
import type { ConnectionLogStore } from './connection/connection-log-buffer'
import type { ConnectionLogEntry } from './connection/connection-log-types'
import type { HostProfile } from '../transport/hosts/host-profile'
import type { HostProfile } from '../host-catalog/host-profile'
import type { RpcClientContextValue } from '../transport/connection/rpc-client-context-contract'
/** Route identity token: compared by reference to detect navigating away and back, never read. */

Some files were not shown because too many files have changed in this diff Show More